# skill-tree

A TypeScript library for managing agent skill versions and evolution. Store, version, serve, sync, and federate reusable skills for AI agents.

## Metrics ownership (as of 0.2.0)

skill-tree does **not** track per-skill usage metrics. The `Skill` shape no
longer carries `metrics` — historical fields like `usageCount`, `successRate`,
`lastUsed`, `feedbackScores` lived on it but were never updated by any code
path in skill-tree, and the snapshots that consumers wrote at publish time
drifted from the live data.

Live usage tracking belongs to systems that observe agents running:
- **cognitive-core** (`playbook.evolution.successCount` / `failureCount`,
  `playbook.confidence`) is the canonical home. `recordSuccess` and
  `recordFailure` mutate it on every trajectory.

If you need ranked loadouts driven by live metrics, query that system
first, then pass the resulting skill IDs to skill-tree's loadout compile
via `include: [...]`. As of 0.2, `include` is a **presence guarantee** —
every ID listed is in the result regardless of other filters, in the
order specified. Combine with `maxSkills: include.length` for "exactly
these N skills" semantics.

skill-tree's `LoadoutCriteria` no longer supports `minSuccessRate` or
`priorityOrder: 'usage' | 'successRate' | 'recent'` — the only
recognized `priorityOrder` value is `'relevance'`, currently a no-op
pending semantic ranking.

See `CHANGELOG.md` for the full migration guide.

## Overview

skill-tree helps you build and maintain a library of reusable skills for AI agents by:

- **Storing skills** in a versioned, searchable format (OpenSkills-compatible)
- **Evolving skills** through versioning, forking, and merging
- **Serving skills** via dynamic loadouts with expand/collapse, profiles, and token budgeting
- **Materializing skills** to agent-discoverable paths (`.claude/skills/`, `.agent/skills/`) with auto-generated AGENTS.md
- **Syncing skills** across agents with git-based multi-agent collaboration
- **Federating skills** across repositories for cross-organization sharing

Inspired by research on skill libraries ([arXiv:2512.17102](https://arxiv.org/abs/2512.17102)), [Claudeception](https://github.com/blader/Claudeception), and [OpenSkills](https://github.com/numman-ali/openskills).

## Installation

```bash
npm install skill-tree
```

## Quick Start

```typescript
import { createSkillBank, type Skill } from 'skill-tree';

// Create a skill bank with filesystem storage
const bank = createSkillBank({
  storage: { basePath: './skills' },
});

await bank.initialize();

// Save a skill
await bank.saveSkill({
  id: 'typescript-esm-import-fix',
  name: 'TypeScript ESM Import Fix',
  version: '1.0.0',
  description: 'Fix TypeScript ES module import errors',
  instructions: 'Add .js extension to all relative imports, even for .ts files.\n\nRun `tsc --noEmit` to verify no import errors.',
  author: 'me',
  tags: ['typescript', 'esm'],
  createdAt: new Date(),
  updatedAt: new Date(),
  status: 'active',
} as Skill);

// Search for skills
const skills = await bank.searchSkills('typescript');

// Create a new version
const updated = await bank.createVersion('typescript-esm-import-fix', {
  solution: 'Add .js extension, or use "moduleResolution": "bundler".',
}, { bumpType: 'minor' });

await bank.shutdown();
```

## Core Concepts

### Skills

A skill represents a reusable piece of knowledge:

```typescript
interface Skill {
  id: string;                    // Unique identifier (kebab-case)
  name: string;                  // Human-readable name
  version: string;               // Semantic version
  description: string;           // Short summary for discovery and matching
  instructions: string;          // Free-form markdown body (the SKILL.md content)
  tags: string[];                // Categorization
  status: SkillStatus;           // 'draft' | 'active' | 'deprecated' | 'experimental'
  // ... namespace, taxonomy, lineage, serving metadata
}
```

### Storage

Two backends:

```typescript
// Filesystem (JSON source of truth + SQLite cache)
const bank = createSkillBank({
  storage: { basePath: './skills' },
});

// Memory (for testing)
const bank = createSkillBank({
  storage: { type: 'memory' },
});
```

Skills are stored in the OpenSkills format:

```
skills/
├── my-skill/
│   ├── SKILL.md           # Skill content (YAML + Markdown)
│   └── .skilltree.json    # Metadata and lineage
└── .versions/
    └── my-skill/
        ├── 1.0.0.json     # Version snapshots
        └── 1.1.0.json
```

## Features

### Versioning

Semantic versioning with full lineage tracking:

```typescript
// Create new version
const v2 = await bank.createVersion('my-skill', updates, {
  bumpType: 'minor',
  changelog: 'Added alternative solution',
});

// Get version history
const history = await bank.getVersionHistory('my-skill');

// Rollback to previous version
const restored = await bank.rollbackSkill('my-skill', '1.0.0');

// Compare versions
const diff = await bank.compareVersions('my-skill', '1.0.0', '2.0.0');

// Fork for a specialized use case
const forked = await bank.forkSkill('my-skill', {
  newId: 'my-skill-react',
  newName: 'My Skill (React variant)',
  reason: 'Specialized for React projects',
});
```

### Serving Layer

Dynamic skill loadouts for agent context windows:

```typescript
const { server } = await bank.createServingLayer({
  maxExpanded: 5,
});

// Orchestrator sets loadout based on task
await server.setLoadoutForTask('Fix authentication bug');

// Or use a built-in profile
await server.setLoadoutFromProfile('debugging');

// Render skills into system prompt
const prompt = server.renderSystemPrompt();

// Agent-side API
const view = server.agentListLoadout();
const skill = server.agentExpandSkill('some-skill-id');
server.agentCollapseSkill('some-skill-id');
await server.agentRequestSkills(['another-skill']);
```

### Multi-Agent Sync

Git-based skill synchronization:

```typescript
import { createDefaultSyncConfig } from 'skill-tree';

const bank = createSkillBank({
  storage: { basePath: './skills' },
  sync: {
    config: createDefaultSyncConfig({
      repoUrl: 'git@github.com:org/skills.git',
      agentId: 'agent-1',
    }),
    pullOnInit: true,
  },
});

await bank.initialize(); // auto-pulls remote changes

// Manual sync
await bank.sync.push();
await bank.sync.pull();
const status = await bank.sync.status();

await bank.shutdown(); // flushes pending sync
```

### Federation

Connect independent skill repositories:

```typescript
// Add a remote
await bank.federation.addRemote('team', {
  url: 'git@github.com:org/team-skills.git',
  access: 'read-write',
});

// Browse remote skills
const remoteSkills = await bank.federation.browse('team');

// Import a skill
const result = await bank.federation.import('team', 'useful-pattern');

// Share a skill
await bank.federation.share('my-skill', 'team');

// Check for upstream updates
const updates = await bank.federation.checkUpstream();
```

### Events

Subscribe to skill bank events:

```typescript
// Simple synchronous listeners
const unsubscribe = bank.on((event) => {
  switch (event.type) {
    case 'skill:created':
      console.log('New skill:', event.skill.name);
      break;
    case 'skill:updated':
      console.log('Updated:', event.skill.name, 'from', event.previousVersion);
      break;
  }
});

// Advanced: async hooks with priority and filtering
bank.getHookRegistry().register({
  event: 'storage:after-save',
  handler: async (context) => {
    console.log('Skill saved to storage');
  },
  priority: 'normal',
});
```

### Materialization

Make skills discoverable by agents following the [Agent Skills standard](https://agentskills.io). Skills stored in `.skilltree/` are symlinked to standard discovery paths and AGENTS.md is auto-regenerated on changes.

```typescript
const bank = createSkillBank({
  storage: { basePath: './my-project' },
  materialization: {
    enabled: true,
    symlinkPaths: ['.claude/skills', '.agent/skills'],
    agentsMdPath: './AGENTS.md',
    agentsMdFormat: 'xml',  // 'xml' | 'markdown' | 'json'
    debounceMs: 500,
  },
});

await bank.initialize();
// Symlinks created, AGENTS.md generated

await bank.saveSkill(mySkill);
// Symlinks updated, AGENTS.md regenerated automatically
```

This creates:
```
.claude/skills/
  my-skill -> ../../.skilltree/skills/my-skill  (symlink)
.agent/skills/
  my-skill -> ../../.skilltree/skills/my-skill  (symlink)
AGENTS.md  (auto-generated with <!-- SKILLTREE_START/END --> markers)
```

Agents activate skills on demand via the CLI:

```bash
# Read a skill to stdout (OpenSkills-compatible progressive disclosure)
skill-tree read my-skill

# Read multiple skills
skill-tree read skill-one,skill-two
```

### AGENTS.md Integration

Bidirectional sync between skill bank and AGENTS.md files:

```typescript
import { createAgentsSync } from 'skill-tree';

const sync = createAgentsSync(bank.getStorage());
const result = await sync.sync('./AGENTS.md', {
  direction: 'bidirectional',
});
```

### Namespace Support

Multi-tier skill trees for team environments:

```typescript
const bank = createSkillBank({
  storage: { basePath: './skills' },
  namespace: {
    agentId: 'agent-1',
    team: 'frontend',
    defaultScope: 'personal',
    defaultVisibility: 'private',
  },
});

// List skills by scope
const personal = await bank.listSkills({ scope: 'personal', owner: 'agent-1' });
const teamSkills = await bank.listSkills({ scope: 'team', team: 'frontend' });
```

## API Reference

### SkillBank

Main orchestrator class, created via `createSkillBank(config)`.

| Method | Description |
|--------|-------------|
| `initialize()` | Initialize storage, federation, and sync |
| `shutdown()` | Clean shutdown (flushes sync) |
| `getSkill(id, version?)` | Get skill by ID |
| `listSkills(filter?)` | List skills with optional filter |
| `searchSkills(query)` | Full-text search |
| `saveSkill(skill)` | Save or update a skill |
| `deleteSkill(id, version?)` | Delete a skill |
| `deprecateSkill(id)` | Mark skill as deprecated |
| `createVersion(id, updates, options?)` | Create new version |
| `forkSkill(id, options)` | Fork a skill |
| `getVersionHistory(id)` | Get version history |
| `getLineage(id)` | Get full lineage |
| `rollbackSkill(id, version)` | Rollback to version |
| `compareVersions(id, vA, vB)` | Diff two versions |
| `on(handler)` | Subscribe to events (returns unsubscribe fn) |
| `off(handler)` | Unsubscribe from events |
| `getStats()` | Get skill bank statistics |
| `exportAll()` | Export all skills |
| `importSkills(skills)` | Bulk import skills |
| `createServingLayer(config?)` | Create a SkillGraphServer |
| `getStorage()` | Access underlying storage adapter |
| `getHookRegistry()` | Access hook registry |
| `sync` | SyncManager accessor (throws if not configured) |
| `federation` | FederationManager accessor (requires basePath) |

### Versioning Utilities

```typescript
import {
  parseVersion,
  compareVersions,
  bumpVersion,
  satisfiesRange,
} from 'skill-tree';

bumpVersion('1.2.3', 'minor');  // '1.3.0'
satisfiesRange('1.5.0', '^1.2.0');  // true
```

## Skill Format

Skills use YAML frontmatter + Markdown body ([Agent Skills](https://agentskills.io) compatible):

```markdown
---
name: typescript-esm-import-fix
description: |
  Fix TypeScript ES module import errors by adding .js extension
version: 1.0.0
author: extracted
status: active
date: 2024-01-15
tags:
  - typescript
  - esm
  - imports
---

TypeScript with ES modules requires explicit .js extensions in imports.

1. Add `.js` extension to relative imports
2. Even for `.ts` files, use `.js` in the import path

Run `tsc` and verify no module resolution errors.
```

## License

MIT
