Use when developing PRPM (Prompt Package Manager) - comprehensive knowledge base covering architecture, format conversion, package types, collections, quality standards, testing, and deployment
Complete knowledge base for developing PRPM - the universal package manager for AI prompts, agents, and rules.
Build the npm/cargo/pip equivalent for AI development artifacts. Enable developers to discover, install, share, and manage prompts across Cursor, Claude Code, Continue, Windsurf, and future AI editors.
prpm install @collection/nextjs-pro gets everything--as claude to force specific formatā ļø NEVER PUSH DIRECTLY TO MAIN ā ļø
PRPM uses a branch-based workflow with CI/CD automation. Direct pushes to main bypass all safety checks and can break production.
ALWAYS follow this workflow:
Create a branch for your changes:
git checkout -b feature/your-feature-name
# or
git checkout -b fix/bug-description
Make commits on your branch using conventional commit format:
git add [files]
git commit -m "feat: add OpenCode format support"
Use conventional commit prefixes for all commits:
| Prefix | Use For | Example |
|---|---|---|
feat: |
New features | feat: add Kiro format support |
fix: |
Bug fixes | fix: resolve publish timeout issue |
docs: |
Documentation | docs: update API reference |
chore: |
Maintenance | chore(release): publish packages |
refactor: |
Code refactoring | refactor: simplify converter logic |
test: |
Test changes | test: add roundtrip tests for Gemini |
perf: |
Performance | perf: optimize search query |
Why this matters:
Bad commits to avoid:
# ā Too vague
git commit -m "fixes"
git commit -m "updates"
git commit -m "changes"
# ā
Clear and descriptive
git commit -m "fix: resolve schema validation for Claude agents"
git commit -m "feat: add support for Droid format subtypes"
Push your branch:
git push origin feature/your-feature-name
Create a Pull Request on GitHub:
After PR approval, merge through GitHub UI:
Why this matters:
If you accidentally pushed to main:
Exception: Only repository admins can push to main for emergency hotfixes (with explicit approval).
Purpose: Knowledge and guidelines for AI assistants
Location: .claude/skills/, .cursor/rules/
Examples: @prpm/pulumi-troubleshooting, @typescript/best-practices
Purpose: Autonomous AI agents for multi-step tasks
Location: .claude/agents/, .cursor/agents/
Examples: @prpm/code-reviewer, @cursor/debugging-agent
Purpose: Specific instructions or constraints for AI behavior
Location: .cursor/rules/, .cursorrules
Examples: @cursor/react-conventions, @cursor/test-first
Purpose: Extensions that add functionality
Location: .cursor/plugins/, .claude/plugins/
Purpose: Reusable prompt templates
Location: .prompts/, project-specific directories
Purpose: Multi-step automation workflows
Location: .workflows/, .github/workflows/
Purpose: Executable utilities and scripts
Location: scripts/, tools/, .bin/
Purpose: Reusable file and project templates
Location: templates/, project-specific directories
Purpose: Model Context Protocol servers
Location: .mcp/servers/
Cursor (.mdc)
ruleType, alwaysApply, descriptionClaude (agent format)
name, descriptiontools (comma-separated), model (sonnet/opus/haiku/inherit)Continue (JSON)
Windsurf
Start at 100 points, deduct for lossy conversions:
Collections are curated bundles of packages that solve specific use cases.
{
"id": "@collection/nextjs-pro",
"name": "Next.js Professional Setup",
"description": "Complete Next.js development setup",
"category": "frontend",
"packages": [
{
"packageId": "react-best-practices",
"required": true,
"reason": "Core React patterns"
},
{
"packageId": "typescript-strict",
"required": true,
"reason": "Type safety"
},
{
"packageId": "tailwind-helper",
"required": false,
"reason": "Styling utilities"
}
]
}
Collections can be installed using multiple identifier formats. The system intelligently resolves collections based on the format provided.
1. Recommended Format: collections/{slug}
prpm install collections/nextjs-pro
prpm install collections/nextjs-pro@2.0.0
name_slug = "nextjs-pro"khaliqgant/nextjs-pro even when searching collections/nextjs-pro2. Explicit Scope: {scope}/{slug} or @{scope}/{slug}
prpm install khaliqgant/nextjs-pro
prpm install @khaliqgant/nextjs-pro
prpm install khaliqgant/nextjs-pro@2.0.0
scope and name_slug combinationkhaliqgant3. Name-Only Format: {slug} (Legacy/Fallback)
prpm install nextjs-pro
prpm install nextjs-pro@1.0.0
scope = "collection", then falls back to cross-scope searchcollections/{slug} for clarityImplementation Location: app/packages/registry/src/routes/collections.ts:485-519
// When scope is 'collection' (default from CLI for collections/* prefix):
if (scope === 'collection') {
// Search across ALL scopes, prioritize by:
// 1. Official collections (official = true)
// 2. Verified authors (verified = true)
// 3. Most downloads
// 4. Most recent
SELECT * FROM collections
WHERE name_slug = $1
ORDER BY official DESC, verified DESC, downloads DESC, created_at DESC
LIMIT 1
} else {
// Explicit scope: exact match only
SELECT * FROM collections
WHERE scope = $1 AND name_slug = $2
ORDER BY created_at DESC
LIMIT 1
}
Implementation Location: app/packages/cli/src/commands/collections.ts:487-504
// Parse collection spec:
// - collections/nextjs-pro ā scope='collection', name_slug='nextjs-pro'
// - khaliqgant/nextjs-pro ā scope='khaliqgant', name_slug='nextjs-pro'
// - @khaliqgant/nextjs-pro ā scope='khaliqgant', name_slug='nextjs-pro'
// - nextjs-pro ā scope='collection', name_slug='nextjs-pro'
const matchWithScope = collectionSpec.match(/^@?([^/]+)\/([^/@]+)(?:@(.+))?$/);
if (matchWithScope) {
[, scope, name_slug, version] = matchWithScope;
} else {
// No scope: default to 'collection'
[, name_slug, version] = collectionSpec.match(/^([^/@]+)(?:@(.+))?$/);
scope = 'collection';
}
Collections support semantic versioning:
# Latest version (default)
prpm install collections/nextjs-pro
# Specific version
prpm install collections/nextjs-pro@2.0.4
# With scope and version
prpm install khaliqgant/nextjs-pro@2.0.4
Registry Behavior:
created_at)When searching across all scopes (collections/* format), the system prioritizes:
Official Collections (official = true)
Verified Authors (verified = true)
Download Count (downloads DESC)
Recency (created_at DESC)
Collection Not Found:
prpm install collections/nonexistent
# ā Failed to install collection: Collection not found
Scope-Specific Not Found:
prpm install wrongscope/nextjs-pro
# ā Failed to install collection: Collection not found
# Suggestion: Try 'collections/nextjs-pro' to search all scopes
Popularity (0-30 points):
Quality (0-30 points):
Trust (0-20 points):
Recency (0-10 points):
Completeness (0-10 points):
// Format converter test
describe('toCursor', () => {
it('preserves data in roundtrip', () => {
const result = toCursor(canonical);
const back = fromCursor(result.content);
expect(back).toEqual(canonical);
});
});
// CLI command test
describe('install', () => {
it('downloads and installs package', async () => {
await handleInstall('test-pkg', { as: 'cursor' });
expect(fs.existsSync('.cursor/rules/test-pkg.md')).toBe(true);
});
});
ā ļø CRITICAL: PRPM uses npm, not pnpm ā ļø
package-lock.json (npm)npm installnpm run <script>npm run <script> --workspace=<package>DO NOT use pnpm:
package-lock.json, not pnpm-lock.yamlCommon commands:
# Install all dependencies
npm install
# Install in specific workspace
npm install --workspace=@pr-pm/cli
# Run tests
npm test
# Build all packages
npm run build
# Run CLI locally
npm run dev --workspace=prpm
.env.example immediatelyAVOID Runtime Dependencies (Dynamic Imports)
ā Bad: Using dynamic imports for runtime dependencies
// BAD - tar-stream is imported dynamically at runtime
const tarStream = await import('tar-stream');
Problems with Dynamic Imports:
ā Good: Declare all dependencies explicitly
// GOOD - Import normally at the top
import * as tarStream from 'tar-stream';
Dependency Guidelines:
dependencies, not devDependenciesALWAYS Update .env.example When Adding New Environment Variables
Environment variables are configuration points. When adding new ones, follow this checklist:
.env.example immediately - Don't wait until latersk-ant-api03-... for API keys)#Example:
# ==============================================================================
# NEW FEATURE SECTION
# ==============================================================================
# Description of what this variable does
# Get from: https://where-to-get-it.com
NEW_FEATURE_API_KEY=your-key-here
# Optional feature flag (default: false)
# ENABLE_NEW_FEATURE=true
Why this matters:
.env.example is the source of truth for configurationFinding missing env vars:
# Search for all process.env usage
grep -rh "process.env\." packages/ --include="*.ts" --include="*.tsx" | \
grep -o "process\.env\.[A-Z_][A-Z0-9_]*" | sort -u
# Compare with .env.example to find gaps
/api/v1/The webapp MUST be deployable as a static site via S3/CloudFront.
Requirements:
output: 'export' configurationgenerateStaticParams() - return empty array [] for client-side only routesCommon Issues:
Dynamic routes in client components ā ļø CANNOT USE BOTH
Page "page" cannot use both "use client" and export function "generateStaticParams()"/playground/shared/[token]/page.tsx with 'use client'/playground/shared/page.tsx with ?token=xxx query paramuseSearchParams() instead of useParams()useSearchParams() in <Suspense> boundary// ā Dynamic route (doesn't work with 'use client')
// /app/shared/[token]/page.tsx
const params = useParams();
const token = params.token;
// ā
Query string with Suspense (works with 'use client')
// /app/shared/page.tsx
import { Suspense } from 'react';
function Content() {
const searchParams = useSearchParams();
const token = searchParams.get('token');
// ... component logic
}
export default function Page() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Content />
</Suspense>
);
}
Server components in static export
'use client' directiveEnvironment variables
Why S3 Static Export?
Publishable Packages:
prpm - CLI (public)@prpm/registry-client - HTTP client (public)Process:
Homebrew Formula:
khaliqgant/homebrew-prpmHOMEBREW_TAP_TOKEN secretVersion Bumping:
# CLI and client together
npm version patch --workspace=prpm --workspace=@prpm/registry-client
# Individual package
npm version minor --workspace=prpm
The publish command is a complex flow. Common issues and fixes:
| Issue | Cause | Fix |
|---|---|---|
Schema not found |
Schemas not copied to dist | Ensure build copies schemas: check tsup.config.ts |
Cannot find module '@pr-pm/types' |
Types not built first | Run npm run build --workspace=@pr-pm/types first |
Type export missing |
tsup not exporting types | Check dts: true in tsup.config.ts |
Version mismatch |
Workspaces out of sync | Bump all related packages together |
Tarball too large |
Including unnecessary files | Check .npmignore or files in package.json |
Authentication failed |
Token expired/missing | Re-run prpm login or check PRPM_TOKEN |
Build Order for Publishing:
# Always build in dependency order
npm run build --workspace=@pr-pm/types
npm run build --workspace=@pr-pm/converters
npm run build --workspace=@pr-pm/registry-client
npm run build --workspace=prpm
Pre-Publish Checklist:
npm testnpm run typechecknpm run buildDebugging Publish Issues:
# Check what will be published
npm pack --workspace=prpm --dry-run
# Verify package contents
tar -tzf prpm-*.tgz
# Check for missing exports
node -e "console.log(require('./packages/cli/dist/index.js'))"
export async function handleCommand(args: Args, options: Options) {
const startTime = Date.now();
try {
const config = await loadUserConfig();
const client = getRegistryClient(config);
const result = await client.fetchData();
console.log('ā
Success');
await telemetry.track({ command: 'name', success: true });
} catch (error) {
console.error('ā Failed:', error.message);
await telemetry.track({ command: 'name', success: false });
process.exit(1);
}
}
Use CLIError instead of process.exit() for testable CLI errors.
// ā BAD - Untestable, tests can't catch process.exit()
if (!packageId) {
console.error('Package ID is required');
process.exit(1);
}
// ā
GOOD - Testable with CLIError
import { CLIError } from '../utils/cli-error.js';
if (!packageId) {
throw new CLIError('Package ID is required', { exitCode: 1 });
}
Why CLIError?
CLIError Usage:
import { CLIError, createError, createSuccess } from '../utils/cli-error.js';
// Simple error
throw new CLIError('Something went wrong');
// With exit code
throw new CLIError('Invalid format specified', { exitCode: 1 });
// With suggestions
throw new CLIError('Package not found', {
exitCode: 1,
suggestion: 'Try running: prpm search <query>'
});
// Helper functions for consistent messaging
createError('Operation failed'); // Returns formatted error
createSuccess('Package installed'); // Returns formatted success
Testing CLI Errors:
import { describe, it, expect } from 'vitest';
import { CLIError } from '../utils/cli-error.js';
describe('install command', () => {
it('throws CLIError for missing package', async () => {
await expect(handleInstall('')).rejects.toThrow(CLIError);
});
it('provides helpful error message', async () => {
try {
await handleInstall('');
} catch (error) {
expect(error).toBeInstanceOf(CLIError);
expect(error.message).toContain('Package ID is required');
}
});
});
server.get('/:id', {
schema: { /* OpenAPI schema */ },
}, async (request, reply) => {
const { id } = request.params;
if (!id) return reply.code(400).send({ error: 'Missing ID' });
const result = await server.pg.query('SELECT...');
return result.rows[0];
});
export function toFormat(pkg: CanonicalPackage): ConversionResult {
const warnings: string[] = [];
let qualityScore = 100;
const content = convertSections(pkg.content.sections, warnings);
const lossyConversion = warnings.some(w => w.includes('not supported'));
if (lossyConversion) qualityScore -= 10;
return { content, format: 'target', warnings, qualityScore, lossyConversion };
}
registry-client.ts, to-cursor.ts)CanonicalPackage, ConversionResult)getPackage, convertToFormat)DEFAULT_REGISTRY_URL)package_id, created_at)package_id, session_id, created_at)PlaygroundRunRequest.package_id, CreditBalance.reset_atSee supporting files in this skill directory for detailed information:
format-conversion.md - Complete format conversion specspackage-types.md - All package types with examplescollections.md - Collections system and examplesquality-ranking.md - Quality and ranking algorithmstesting-guide.md - Testing patterns and standardsdeployment.md - Deployment proceduresRemember: PRPM is infrastructure. It must be rock-solid, fast, and trustworthy like npm or cargo.