Skip to content
Merged
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
15 changes: 11 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Each milestone gets its own feature branch. All commits for that milestone go on

3. **Platform adapters are copiers** — Installing a skill to a platform means copying the skill directory to the platform's expected location. Each adapter knows its platform's directory convention.

4. **Platform auto-detection** — Skern detects which platforms are installed by checking for their config directories/binaries (`~/.claude/`, `~/.codex/` or `~/.agents/`, `~/.config/opencode/`). `--platform all` installs to every detected platform.
4. **Platform auto-detection** — Skern detects which platforms are installed by checking for their config directories/binaries (`~/.claude/`, `~/.codex/` or `~/.agents/`, `~/.config/opencode/`). Each `install`/`uninstall` invocation targets exactly one platform (per #52 D6); `--platform all` is not accepted. Agents specify the platform they are running on, and `skill install`/`skill uninstall` accept multiple skill names per call for batch operations.

5. **JSON output as first-class** — Every command supports `--json` for machine-readable output. Default is human-friendly text. Exit codes are semantic: 0=success, 1=error, 2=validation failure.

Expand All @@ -159,10 +159,15 @@ On subsequent encounters, the agent finds the existing skill via `skern skill se
|---|---|---|
| Overlap warning threshold | Similarity score 0.0–1.0 | Warn at >= 0.6 |
| Overlap block threshold | Similarity score | Block at >= 0.9, require `--force` |
| Skill count warning (project) | Count in `.skern/skills/` | Warn at > 20 |
| Skill count warning (user) | Count in `~/.skern/skills/` | Warn at > 50 |
| Skill count warning (project) | Count in `.skern/skills/` | Warn at >= 20 |
| Skill count warning (user) | Count in `~/.skern/skills/` | Warn at >= 50 |
| Per-platform capacity (project scope) | Count installed at `<platform>/.skern/skills/` (project) | Warn at >= 20 |
| Per-platform capacity (user scope) | Count installed at user-level platform skills dir | Warn at >= 50 |
| Capacity enforcement (install) | `--enforce-budget` | Off by default; refuses install when threshold would be exceeded |
| Deduplication hints | On `skern skill list` | Flag potential duplicates |

Capacity thresholds are defined in `internal/skill/capacity.go`. Every `install`/`uninstall` JSON response carries a `capacity` block (count, threshold, headroom, over-budget flag) so agents can react to capacity pressure without an extra query.

### SKILL.md Format (Agent Skills Spec)

```markdown
Expand Down Expand Up @@ -255,7 +260,7 @@ Names must match `^[a-z0-9]+(-[a-z0-9]+)*$` and be 1-64 characters.

## Current Status

All milestones (M0–M5) are complete. The current release is **v0.1.0**, which added `skill edit`, skill tags/categories, `--force` install, parse warnings in `skill list`, stylistic lint hints, and internal refactors (CommandContext, unified scoring, output split).
Milestones M0–M5 are complete (v0.1.0). M6 is in progress on `feature/m6-dynamic-loading-batch`: dynamic skill loading per #52 — batch install/uninstall, capacity reporting in install/uninstall output, `--enforce-budget` opt-in, `--with-platforms` flag on `skill list`, removal of `--platform all`. This is a **breaking change** to the JSON shape of install/uninstall results (`skills[]` + top-level `platform`/`capacity` instead of `platforms[]`); the next release is v0.2.0.

### Future Roadmap

Expand All @@ -268,4 +273,6 @@ These items are tracked as GitHub issues:
- Remote catalog search in `skern skill search`
- Skill dependency resolution
- WASI/Docker execution backends
- LRU usage tracking for dynamic loading (#52 Phase 3) — state file at `~/.skern/state/usage.json`, `skern skill touch`/`skern skill evict` commands; deferred until the agent-side "skill use" signal is settled
- Skill stats for context optimization (#75) — byte size, cross-platform presence, future token estimation

23 changes: 20 additions & 3 deletions docs/concepts/platform-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,31 @@ Skern auto-detects which platforms are installed on your system. Use `skern plat
skern platform list
```

## Install to All Platforms
## One Platform per Invocation

Use `--platform all` to install a skill to every detected platform at once:
Each `skern skill install` call targets exactly one platform. Agents are expected to specify the platform they are running on — there is no `all` value, and skern does not broadcast skills across platforms automatically.

This design supports the [dynamic skill loading](./registry) model: each agent maintains its own working set of installed skills, sized to its context budget, independent of other platforms.

To deploy a skill across several platforms, loop the call:

```sh
for p in claude-code codex-cli opencode; do
skern skill install code-review --platform "$p"
done
```

## Batch Install/Uninstall

Multiple skills can be installed (or uninstalled) in one call:

```sh
skern skill install code-review --platform all
skern skill install code-review test-runner deploy-checker --platform claude-code
skern skill uninstall stale-a stale-b --platform claude-code
```

Each skill's outcome is reported separately in the JSON output's `skills` array, and a `capacity` block reports the platform's installed-skill count after the batch.

## Platform Status Matrix

View which skills are installed on which platforms:
Expand Down
4 changes: 3 additions & 1 deletion docs/guide/agent-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@ For more comprehensive agent integration, add these to your `AGENTS.md`:
- Before creating a new skill, search with `skern skill search <query>`
- Use `skern skill recommend <query>` to get suggestions before creating
- Always validate skills after creation: `skern skill validate <name>`
- Install to the current platform: `skern skill install <name> --platform all`
- Install to your current platform (the one you are running on), e.g. `skern skill install <name> --platform claude-code`
- Multiple skills can be installed in one call: `skern skill install <a> <b> <c> --platform <p>`
- Watch the `capacity` block in install/uninstall JSON output to manage your platform's working set; uninstall stale skills before adding new ones when capacity is tight
```
12 changes: 10 additions & 2 deletions docs/guide/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,18 @@ Install to Claude Code:
skern skill install code-review --platform claude-code
```

Or install to all detected platforms at once:
Each invocation targets one platform. To install to multiple platforms, loop the call:

```sh
skern skill install code-review --platform all
for p in claude-code codex-cli opencode; do
skern skill install code-review --platform "$p"
done
```

You can also install several skills at once:

```sh
skern skill install code-review test-runner deploy-checker --platform claude-code
```

## 4. List Installed Skills
Expand Down
2 changes: 1 addition & 1 deletion docs/platforms/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Skern supports three agentic development platforms. Each platform has a dedicate
| User-level skills | `~/.claude/skills/` | `~/.agents/skills/` | `~/.config/opencode/skills/` |
| Project-level skills | `.claude/skills/` | `.agents/skills/` | `.opencode/skills/` |
| Auto-detection | Yes | Yes | Yes |
| Install with `--platform all` | Yes | Yes | Yes |
| Batch install (multiple skills, one call) | Yes | Yes | Yes |

## Feature Comparison

Expand Down
28 changes: 20 additions & 8 deletions docs/reference/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ skern skill list [--scope user|project|all] # List skills in registry
skern skill show <name> # Display skill details
skern skill validate <name> # Validate against Agent Skills spec
skern skill remove <name> # Remove skill from registry
skern skill install <name> --platform <p> # Install skill to platform
skern skill uninstall <name> --platform <p> # Remove skill from platform
skern skill install <name>... --platform <p> # Install one or more skills to platform
skern skill uninstall <name>... --platform <p> # Remove one or more skills from platform
skern platform list # List detected platforms
skern platform status # Skill x platform installation matrix
skern completion [bash|zsh|fish] # Generate shell completions
Expand Down Expand Up @@ -119,11 +119,14 @@ skern skill list [--scope user|project|all] [flags]
|------|-------------|
| `--scope` | `user`, `project`, or `all` |
| `--tag` | Filter results to skills with this tag |
| `--with-platforms` | Include `installed_on` per-skill: the detected platforms where the skill is currently installed at the same scope |

Also runs pairwise overlap detection across all listed skills and appends a "Potential duplicates" section when matches are found (score >= 0.6). In `--json` mode, these appear in the `duplicates` array.

Skills that cannot be parsed are reported as parse warnings rather than silently skipped. In text mode these appear as warning lines; in `--json` mode they appear in the `parse_warnings` array.

When `--with-platforms` is set, the JSON output contains an `installed_on` array on every skill — empty for skills not installed on any detected platform. Without the flag the field is omitted entirely so consumers can distinguish "queried, none" from "not queried".

## `skern skill show`

Display full details for a skill, including author provenance and modification history.
Expand Down Expand Up @@ -152,33 +155,42 @@ skern skill remove <name>

## `skern skill install`

Install a skill to one or more platforms.
Install one or more skills to a single platform.

```sh
skern skill install <name> --platform <platform>
skern skill install <name>... --platform <platform>
```

Each invocation targets exactly one platform. Agents are expected to specify the platform they are running on; `--platform all` is no longer accepted. To deploy a skill across multiple platforms, loop the call per platform.

Multiple skill names can be passed in a single call. Each skill's outcome is reported as a separate entry in the `skills` array. A failure on one skill does not abort the batch — the command exits non-zero only when *every* install fails.

The response includes a `capacity` block reporting the platform's installed-skill count after the operation, the threshold for that scope, and remaining headroom. Use it to decide whether to evict stale skills before the next install.

**Flags:**

| Flag | Description |
|------|-------------|
| `--platform` | `claude-code`, `codex-cli`, `opencode`, or `all` (required) |
| `--platform` | `claude-code`, `codex-cli`, or `opencode` (required) |
| `--scope` | `user` or `project` |
| `--force` | Overwrite existing installation |
| `--enforce-budget` | Refuse the operation if it would push the platform's installed-skill count past the per-scope threshold |

## `skern skill uninstall`

Remove a skill from a platform.
Remove one or more skills from a platform.

```sh
skern skill uninstall <name> --platform <platform>
skern skill uninstall <name>... --platform <platform>
```

Mirrors `install` semantics: one platform per call, multiple skills allowed, partial failures are reported per-skill, and the response includes a post-op `capacity` block.

**Flags:**

| Flag | Description |
|------|-------------|
| `--platform` | `claude-code`, `codex-cli`, `opencode`, or `all` (required) |
| `--platform` | `claude-code`, `codex-cli`, or `opencode` (required) |
| `--scope` | `user` or `project` |

## `skern platform list`
Expand Down
48 changes: 48 additions & 0 deletions internal/cli/capacity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package cli

import (
"fmt"

"github.com/devrimcavusoglu/skern/internal/output"
"github.com/devrimcavusoglu/skern/internal/platform"
"github.com/devrimcavusoglu/skern/internal/skill"
)

// buildCapacityReport queries a platform for its currently-installed skills
// at the given scope and returns a CapacityReport ready for inclusion in
// command output. Returns nil if the query fails (capacity is best-effort
// telemetry, not an operation prerequisite).
func buildCapacityReport(p platform.Platform, scope skill.Scope) *output.CapacityReport {
names, err := p.InstalledSkills(scope)
if err != nil {
return nil
}
threshold := skill.PlatformThreshold(scope)
installed := len(names)
return &output.CapacityReport{
Platform: string(p.Name()),
Scope: string(scope),
Installed: installed,
Threshold: threshold,
Headroom: threshold - installed,
OverBudget: installed >= threshold,
}
}

// formatCapacityWarning produces a human-readable line describing capacity
// usage after an operation. Returns "" when there is nothing actionable to
// report (well under threshold).
func formatCapacityWarning(r *output.CapacityReport) string {
if r == nil {
return ""
}
if r.OverBudget {
return fmt.Sprintf("Capacity: %s (%s) at %d/%d skills — over threshold.\n",
r.Platform, r.Scope, r.Installed, r.Threshold)
}
if r.Headroom <= 5 {
return fmt.Sprintf("Capacity: %s (%s) at %d/%d skills — %d slot(s) remaining.\n",
r.Platform, r.Scope, r.Installed, r.Threshold, r.Headroom)
}
return ""
}
35 changes: 20 additions & 15 deletions internal/cli/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
)

// TestEndToEnd_FullLifecycle exercises the complete skern workflow:
// create -> validate -> install (all 3 platforms) -> platform status -> uninstall one ->
// verify remaining -> uninstall all -> remove from registry.
// create -> validate -> install (per platform, looped) -> platform status ->
// uninstall one -> verify remaining -> uninstall all -> remove from registry.
func TestEndToEnd_FullLifecycle(t *testing.T) {
cc, userDir, _ := testRegistryWithDirs(t)
home := t.TempDir()
Expand Down Expand Up @@ -63,17 +63,21 @@ func TestEndToEnd_FullLifecycle(t *testing.T) {
assert.Equal(t, "e2e-tester", showResult.Author.Name)
assert.Equal(t, "human", showResult.Author.Type)

// Step 4: Install to all 3 platforms
out, err = runCmd(t, cc, "skill", "install", skillName, "--platform", "all", "--json")
require.NoError(t, err)
// Step 4: Install to each platform individually. Per #52 D6, --platform all
// is no longer supported; agents loop per-platform when they need to.
for _, plat := range []string{"claude-code", "codex-cli", "opencode"} {
out, err = runCmd(t, cc, "skill", "install", skillName, "--platform", plat, "--json")
require.NoError(t, err)

var installResult output.SkillInstallResult
require.NoError(t, json.Unmarshal([]byte(out), &installResult))
assert.Equal(t, skillName, installResult.Skill)
assert.Len(t, installResult.Platforms, 3, "should install to all 3 platforms")
for _, p := range installResult.Platforms {
assert.True(t, p.Success, "install should succeed for %s", p.Platform)
assert.Empty(t, p.Error, "no error expected for %s", p.Platform)
var installResult output.SkillInstallResult
require.NoError(t, json.Unmarshal([]byte(out), &installResult))
assert.Equal(t, plat, installResult.Platform)
require.Len(t, installResult.Skills, 1)
assert.Equal(t, skillName, installResult.Skills[0].Skill)
assert.True(t, installResult.Skills[0].Success, "install should succeed on %s", plat)
assert.Empty(t, installResult.Skills[0].Error, "no error expected on %s", plat)
assert.NotNil(t, installResult.Capacity, "capacity report should be present")
assert.Equal(t, plat, installResult.Capacity.Platform)
}

// Step 5: Verify files exist on all 3 platforms
Expand Down Expand Up @@ -110,9 +114,10 @@ func TestEndToEnd_FullLifecycle(t *testing.T) {

var uninstallResult output.SkillUninstallResult
require.NoError(t, json.Unmarshal([]byte(out), &uninstallResult))
assert.Equal(t, skillName, uninstallResult.Skill)
assert.Len(t, uninstallResult.Platforms, 1)
assert.True(t, uninstallResult.Platforms[0].Success)
assert.Equal(t, "claude-code", uninstallResult.Platform)
require.Len(t, uninstallResult.Skills, 1)
assert.Equal(t, skillName, uninstallResult.Skills[0].Skill)
assert.True(t, uninstallResult.Skills[0].Success)

// Verify claude-code no longer has it
_, err = os.Stat(platformPaths["claude-code"])
Expand Down
Loading
Loading