[nexus] test(18-01): add failing tests for adapter skill config resolver
- Add AdapterSkillFormat type and AdapterSkillConfig interface to types.ts - Create stub adapter-skill-config.ts (throws not implemented) - Re-export new types and functions from index.ts - Add comprehensive test file covering all 10 adapter types and fallback
This commit is contained in:
parent
2f5e0ea189
commit
2d546bedaf
4 changed files with 206 additions and 0 deletions
17
packages/adapter-utils/src/adapter-skill-config.ts
Normal file
17
packages/adapter-utils/src/adapter-skill-config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import type { AdapterSkillConfig } from "./types.js";
|
||||
|
||||
/**
|
||||
* Returns the AdapterSkillConfig for the given adapter type.
|
||||
* Unknown types return a safe fallback config with supportsInstall: false.
|
||||
* Never throws.
|
||||
*/
|
||||
export function resolveAdapterSkillConfig(_adapterType: string): AdapterSkillConfig {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all registered adapter skill configs (one per known adapter type).
|
||||
*/
|
||||
export function listAdapterSkillConfigs(): readonly AdapterSkillConfig[] {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
export type { AdapterSkillFormat, AdapterSkillConfig } from "./types.js";
|
||||
export { resolveAdapterSkillConfig, listAdapterSkillConfigs } from "./adapter-skill-config.js";
|
||||
export type {
|
||||
AdapterAgent,
|
||||
AdapterRuntime,
|
||||
|
|
|
|||
|
|
@ -354,3 +354,36 @@ export interface CreateConfigValues {
|
|||
heartbeatEnabled: boolean;
|
||||
intervalSec: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Adapter skill config types — maps adapter type to skill directory and capabilities
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** Format of skill files recognized by the adapter. */
|
||||
export type AdapterSkillFormat = "skill-md" | "none";
|
||||
|
||||
/**
|
||||
* Static configuration describing where an adapter stores skills and whether
|
||||
* the Nexus skill install/uninstall flow is supported for it.
|
||||
*/
|
||||
export interface AdapterSkillConfig {
|
||||
/** The adapter type string (e.g. "claude_local", "hermes_local"). */
|
||||
adapterType: string;
|
||||
/**
|
||||
* Path to the directory where skills are installed for this adapter.
|
||||
* Uses `~` as home-directory prefix. Null when skills are not supported.
|
||||
*/
|
||||
skillDir: string | null;
|
||||
/**
|
||||
* Path to the adapter's native skill directory when it differs from skillDir,
|
||||
* e.g. when the adapter has its own concept of a skills folder.
|
||||
* Null for most adapters.
|
||||
*/
|
||||
nativeSkillDir?: string | null;
|
||||
/** Format of skill documents used by this adapter. */
|
||||
format: AdapterSkillFormat;
|
||||
/** Whether the Nexus install/uninstall flow is supported for this adapter. */
|
||||
supportsInstall: boolean;
|
||||
/** Human-readable reason why skills are not supported (when supportsInstall is false). */
|
||||
unsupportedReason: string | null;
|
||||
}
|
||||
|
|
|
|||
154
server/src/__tests__/adapter-skill-config.test.ts
Normal file
154
server/src/__tests__/adapter-skill-config.test.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
resolveAdapterSkillConfig,
|
||||
listAdapterSkillConfigs,
|
||||
} from "@paperclipai/adapter-utils";
|
||||
|
||||
// ADAPT-01: resolveAdapterSkillConfig exists and returns AdapterSkillConfig for any string input
|
||||
describe("resolveAdapterSkillConfig", () => {
|
||||
// ADAPT-02: claude_local
|
||||
it("returns correct config for claude_local", () => {
|
||||
const cfg = resolveAdapterSkillConfig("claude_local");
|
||||
expect(cfg.adapterType).toBe("claude_local");
|
||||
expect(cfg.skillDir).toBe("~/.claude/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
expect(cfg.unsupportedReason).toBeNull();
|
||||
});
|
||||
|
||||
// ADAPT-03: hermes_local
|
||||
it("returns correct config for hermes_local", () => {
|
||||
const cfg = resolveAdapterSkillConfig("hermes_local");
|
||||
expect(cfg.adapterType).toBe("hermes_local");
|
||||
expect(cfg.skillDir).toBe("~/.hermes/skills/");
|
||||
expect(cfg.nativeSkillDir).toBe("~/.hermes/skills/");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
expect(cfg.unsupportedReason).toBeNull();
|
||||
});
|
||||
|
||||
// ADAPT-04: openclaw_gateway
|
||||
it("returns correct config for openclaw_gateway", () => {
|
||||
const cfg = resolveAdapterSkillConfig("openclaw_gateway");
|
||||
expect(cfg.adapterType).toBe("openclaw_gateway");
|
||||
expect(cfg.skillDir).toBe("~/.openclaw/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
expect(cfg.unsupportedReason).toBeNull();
|
||||
});
|
||||
|
||||
// ADAPT-05: codex_local
|
||||
it("returns correct config for codex_local", () => {
|
||||
const cfg = resolveAdapterSkillConfig("codex_local");
|
||||
expect(cfg.adapterType).toBe("codex_local");
|
||||
expect(cfg.skillDir).toBe("~/.agents/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
expect(cfg.unsupportedReason).toBeNull();
|
||||
});
|
||||
|
||||
// ADAPT-06: cursor
|
||||
it("returns correct config for cursor", () => {
|
||||
const cfg = resolveAdapterSkillConfig("cursor");
|
||||
expect(cfg.adapterType).toBe("cursor");
|
||||
expect(cfg.skillDir).toBe("~/.cursor/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
expect(cfg.unsupportedReason).toBeNull();
|
||||
});
|
||||
|
||||
// ADAPT-07: opencode_local
|
||||
it("returns correct config for opencode_local", () => {
|
||||
const cfg = resolveAdapterSkillConfig("opencode_local");
|
||||
expect(cfg.adapterType).toBe("opencode_local");
|
||||
expect(cfg.skillDir).toBe("~/.config/opencode/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
expect(cfg.unsupportedReason).toBeNull();
|
||||
});
|
||||
|
||||
// ADAPT-08: pi_local and gemini_local
|
||||
it("returns correct config for pi_local", () => {
|
||||
const cfg = resolveAdapterSkillConfig("pi_local");
|
||||
expect(cfg.adapterType).toBe("pi_local");
|
||||
expect(cfg.skillDir).toBe("~/.pi/agent/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
});
|
||||
|
||||
it("returns correct config for gemini_local", () => {
|
||||
const cfg = resolveAdapterSkillConfig("gemini_local");
|
||||
expect(cfg.adapterType).toBe("gemini_local");
|
||||
expect(cfg.skillDir).toBe("~/.gemini/skills/");
|
||||
expect(cfg.format).toBe("skill-md");
|
||||
expect(cfg.supportsInstall).toBe(true);
|
||||
});
|
||||
|
||||
// ADAPT-09: process and http — no skills supported
|
||||
it("returns unsupported config for process adapter", () => {
|
||||
const cfg = resolveAdapterSkillConfig("process");
|
||||
expect(cfg.adapterType).toBe("process");
|
||||
expect(cfg.skillDir).toBeNull();
|
||||
expect(cfg.format).toBe("none");
|
||||
expect(cfg.supportsInstall).toBe(false);
|
||||
expect(cfg.unsupportedReason).toBeTruthy();
|
||||
});
|
||||
|
||||
it("returns unsupported config for http adapter", () => {
|
||||
const cfg = resolveAdapterSkillConfig("http");
|
||||
expect(cfg.adapterType).toBe("http");
|
||||
expect(cfg.skillDir).toBeNull();
|
||||
expect(cfg.format).toBe("none");
|
||||
expect(cfg.supportsInstall).toBe(false);
|
||||
expect(cfg.unsupportedReason).toBeTruthy();
|
||||
});
|
||||
|
||||
// ADAPT-10: unknown adapter type returns fallback, does not throw
|
||||
it("returns fallback config for unknown adapter type without throwing", () => {
|
||||
const cfg = resolveAdapterSkillConfig("totally_unknown_adapter");
|
||||
expect(cfg.adapterType).toBe("totally_unknown_adapter");
|
||||
expect(cfg.supportsInstall).toBe(false);
|
||||
expect(cfg.format).toBe("none");
|
||||
// should not throw — just a safe fallback
|
||||
});
|
||||
|
||||
it("populates adapterType field for unknown adapter types", () => {
|
||||
const cfg = resolveAdapterSkillConfig("some_future_adapter");
|
||||
expect(cfg.adapterType).toBe("some_future_adapter");
|
||||
});
|
||||
});
|
||||
|
||||
// listAdapterSkillConfigs returns all 10 registered configs
|
||||
describe("listAdapterSkillConfigs", () => {
|
||||
it("returns an array of all 10 registered adapter configs", () => {
|
||||
const configs = listAdapterSkillConfigs();
|
||||
expect(configs).toHaveLength(10);
|
||||
});
|
||||
|
||||
it("contains entries for all expected adapter types", () => {
|
||||
const configs = listAdapterSkillConfigs();
|
||||
const types = configs.map((c) => c.adapterType);
|
||||
expect(types).toContain("claude_local");
|
||||
expect(types).toContain("hermes_local");
|
||||
expect(types).toContain("openclaw_gateway");
|
||||
expect(types).toContain("codex_local");
|
||||
expect(types).toContain("cursor");
|
||||
expect(types).toContain("opencode_local");
|
||||
expect(types).toContain("pi_local");
|
||||
expect(types).toContain("gemini_local");
|
||||
expect(types).toContain("process");
|
||||
expect(types).toContain("http");
|
||||
});
|
||||
|
||||
it("has no TBD or empty stub entries — all configs are fully populated", () => {
|
||||
const configs = listAdapterSkillConfigs();
|
||||
for (const cfg of configs) {
|
||||
expect(cfg.adapterType).toBeTruthy();
|
||||
expect(cfg.format).toMatch(/^(skill-md|none)$/);
|
||||
if (cfg.supportsInstall) {
|
||||
expect(cfg.skillDir).toBeTruthy();
|
||||
} else {
|
||||
expect(cfg.skillDir).toBeNull();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue