This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
NEVER use major version in changesets. Always use minor or patch:
patch- Bug fixes, internal improvementsminor- New features, enhancements, breaking changes (treated as minor)
This project does not use major version bumps. All breaking changes should be documented but versioned as minor.
ResourceX is a resource management protocol for AI Agents, similar to npm for packages.
Architecture Layers:
-
ARP (Agent Resource Protocol) - Low-level I/O primitives
- Format:
arp:{semantic}:{transport}://{location} - Provides: resolve, deposit, exists, delete
- Format:
-
Core Layer - Primitives and CAS Registry (
@resourcexjs/core)- RXI, RXL, RXM, RXA, RXR primitives
- CASRegistry: Content-addressable storage for resources
- Store interfaces (SPI): RXAStore, RXMStore
- TypeHandlerChain: Resource type system
-
Provider Layer - Platform-specific implementations (
@resourcexjs/node-provider)- NodeProvider: Node.js/Bun platform provider
- FileSystemRXAStore: Blob storage on filesystem
- FileSystemRXMStore: Manifest storage on filesystem
-
ResourceX API - Unified client API (
resourcexjs)setProvider()+createResourceX()- Main entry point- Combines all layers for seamless resource management
-
Server - Registry server (
@resourcexjs/server)- HTTP API for hosting and distributing resources
- Uses CASRegistry with configurable stores
# Install dependencies
bun install
# Build all packages (uses Turborepo)
bun run build
# Run all unit tests
bun run test
# Run a single test file
bun test packages/core/tests/unit/locator/parse.test.ts
# Run BDD tests (Cucumber)
bun run test:bdd
# Run BDD tests with specific tags
cd bdd && bun run test:tags "@resourcex"
# Lint + format check (Biome)
bun run check
# Auto-fix lint + format
bun run check:fix
# Type check
bun run typecheckpackages/
├── arp/ # @resourcexjs/arp - ARP protocol (low-level I/O)
├── core/ # @resourcexjs/core - Primitives, CASRegistry, TypeSystem
├── node-provider/ # @resourcexjs/node-provider - Node.js/Bun provider
├── server/ # @resourcexjs/server - Registry server
└── resourcex/ # resourcexjs - Main client package
// Parse locator string to RXI (Docker-style format)
const rxi = parse("registry.example.com/hello:1.0.0");
// Create manifest from definition
const rxm = manifest({ name: "hello", type: "text", tag: "1.0.0" });
// Create archive from content
const rxa = await archive({ content: Buffer.from("Hello!") });
// Create resource (RXR = RXI + RXM + RXA)
const rxr = resource(rxm, rxa);
// Extract archive to files
const files = await extract(rxa); // Record<string, Buffer>
// Format RXI to string
const locatorStr = format(rxi); // "registry.example.com/hello:1.0.0"
// Wrap raw buffer as RXA
const rxa = wrap(buffer);Content-addressable storage for resources:
import { CASRegistry, MemoryRXAStore, MemoryRXMStore } from "@resourcexjs/core";
const cas = new CASRegistry(new MemoryRXAStore(), new MemoryRXMStore());
await cas.put(rxr); // Store resource
const rxr = await cas.get(rxi); // Get resource
const exists = await cas.has(rxi); // Check existence
await cas.remove(rxi); // Remove resource
const results = await cas.list({ query: "hello" }); // Search
await cas.gc(); // Garbage collect orphaned blobs
await cas.clearCache("registry.example.com"); // Clear cached resourcesFor implementing custom storage backends:
interface RXAStore {
get(digest: string): Promise<Buffer>;
put(data: Buffer): Promise<string>; // Returns digest
has(digest: string): Promise<boolean>;
delete(digest: string): Promise<void>;
list(): Promise<string[]>;
}
interface RXMStore {
get(name: string, tag: string, registry?: string): Promise<StoredRXM | null>;
put(manifest: StoredRXM): Promise<void>;
has(name: string, tag: string, registry?: string): Promise<boolean>;
delete(name: string, tag: string, registry?: string): Promise<void>;
listTags(name: string, registry?: string): Promise<string[]>;
search(options?: RXMSearchOptions): Promise<StoredRXM[]>;
deleteByRegistry(registry: string): Promise<void>;
}Platform-specific implementations:
import { NodeProvider, FileSystemRXAStore, FileSystemRXMStore } from "@resourcexjs/node-provider";
// Use with ResourceX client
import { setProvider, createResourceX } from "resourcexjs";
setProvider(new NodeProvider());
const rx = createResourceX();
// Use directly with server
const rxaStore = new FileSystemRXAStore("./data/blobs");
const rxmStore = new FileSystemRXMStore("./data/manifests");Unified client API. Users only interact with:
- path: Local directory (for add)
- locator: Resource identifier string (e.g.,
hello:1.0.0)
import { createResourceX, setProvider } from "resourcexjs";
import { NodeProvider } from "@resourcexjs/node-provider";
// Configure provider first
setProvider(new NodeProvider());
const rx = createResourceX({
registry: "https://registry.mycompany.com",
});
// Local operations
await rx.add("./my-prompt"); // Add from directory to local storage
await rx.has("hello:1.0.0"); // Check if exists locally
await rx.remove("hello:1.0.0"); // Remove from local
const result = await rx.resolve("hello:1.0.0"); // Resolve locator & execute
const result2 = await rx.ingest("./my-skill"); // Ingest from any source & execute
const results = await rx.search("hello"); // Search local resources
// Remote operations
await rx.push("my-prompt:1.0.0"); // Push to remote registry
await rx.pull("hello:1.0.0"); // Pull from remote to local cache
await rx.clearCache(); // Clear cached remote resources
// Extension
rx.supportType(myCustomType); // Add custom typeconst rx = createResourceX({
path: "~/.resourcex", // Storage path (default: ~/.resourcex)
registry: "https://...", // Central registry URL (required for remote ops)
types: [myType], // Custom types
isolator: "none", // Execution: none | custom
});Docker-style locator format:
[registry/][path/]name[:tag]
// Local: name:tag (no registry)
await rx.resolve("hello:1.0.0");
// Remote: registry/[path/]name:tag (with registry)
await rx.resolve("registry.example.com/hello:1.0.0");
await rx.resolve("localhost:3098/org/hello:1.0.0");Content-addressable storage (CAS):
~/.resourcex/
├── blobs/ # Content-addressable blob storage
│ └── ab/
│ └── sha256:abcd1234... # Archive data (tar.gz)
└── manifests/
├── _local/ # Local resources (no registry)
│ └── my-prompt/
│ └── 1.0.0.json # Manifest with digest reference
└── registry.example.com/ # Cached remote resources
└── hello/
└── 1.0.0.json
rx.resolve("hello:1.0.0")
↓
1. Parse locator (determine if local or remote)
2. Check CASRegistry for resource
3. If not found and registry configured:
- Fetch from remote registry
- Cache locally
4. Get BundledType from TypeHandlerChain
5. Execute resolver
6. Return Executable { execute, schema }
rx.ingest("./my-skill") // directory path
rx.ingest("hello:1.0.0") // or RXL locator string
↓
1. Check if input is a loadable source (SourceLoader.canLoad)
2. If source: add(source) → CAS, then resolve(locator)
3. If locator: resolve(locator) directly
4. All paths go through CAS — no shortcut bypasses storage
interface BundledType {
name: string; // "text", "json", "prompt"
aliases?: string[]; // ["txt"], ["config"]
description: string;
schema?: JSONSchema;
code: string; // Bundled resolver code
}
// Built-in types
text → aliases: [txt, plaintext] → string
json → aliases: [config, manifest] → unknown
binary → aliases: [bin, blob, raw] → Uint8Array
// Register custom type
rx.supportType({
name: "prompt",
description: "AI prompt",
code: `({
async resolve(ctx) {
return new TextDecoder().decode(ctx.files["content"]);
}
})`,
});Low-level I/O primitives:
import { createARP } from "resourcexjs/arp";
const arp = createARP();
const arl = arp.parse("arp:text:file:///path/to/file");
await arl.resolve(); // Get content
await arl.deposit(data); // Write content
await arl.exists(); // Check existence
await arl.delete(); // Delete
await arl.list(); // List directory
await arl.mkdir(); // Create directory- Phase 1: Code Review - Clarify requirements
- Phase 2: BDD - Write
.featurefiles - Phase 3: Implementation - TDD (tests → code)
- Uses Bun as package manager and runtime
- ESM modules only (
"type": "module") - TypeScript with strict mode
- Path aliases: Use
~/instead of../for imports within packages - Keep
.jsextensions in imports for ESM compatibility - Commits follow Conventional Commits (enforced by commitlint via lefthook)
- Unit tests:
packages/*/tests/unit/**/*.test.ts(Bun test) - BDD tests:
bdd/features/**/*.feature+bdd/steps/**/*.steps.ts(Cucumber) - Tags: @arp, @resourcex, @locator, @manifest, @resource-type, @registry
IMPORTANT: BDD tests are end-to-end tests from user perspective:
- Only import from
resourcexjs- Never use internal packages - Only test public API - Never test internal implementation
- User perspective - Test what users would actually do
- Provider setup - Always configure provider before creating client
// ✅ Good
import { createResourceX, setProvider } from "resourcexjs";
import { NodeProvider } from "@resourcexjs/node-provider";
setProvider(new NodeProvider());
const rx = createResourceX();
// ❌ Bad
import { CASRegistry } from "@resourcexjs/core";ResourceXError (base)
├── LocatorError (RXI parsing)
├── ManifestError (RXM validation)
├── ContentError (RXA operations)
└── DefinitionError (RXD validation)
RegistryError (registry operations)
ResourceTypeError (type not found)
ARPError (base)
├── ParseError (URL parsing)
├── TransportError (transport not found)
└── SemanticError (semantic not found)
- Core primitives - parse, manifest, archive, resource, extract
- CASRegistry - Content-addressable storage with deduplication
- Provider architecture - Platform-specific implementations
- ResourceX API - createResourceX with unified interface
- ResourceX Server - HTTP API for hosting resources
- Simplified API - Hide internal objects, users use path/locator only
- S3Storage, R2Storage - Cloud storage backends