mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
Install shared skills for 11 more agents and stop Qoder listing skills twice (#31657)
* Install shared skills for 11 more agents Add skill install provider entries for OpenClaude (own .openclaude folders, OPENCLAUDE_CONFIG_DIR), and OpenCode 2, MiMo Code, OMP, Prime Agent, Qoder CN, Codebuff, Freebuff, Ante, Jcode and DeepSeek Harness, which read the shared .agents/skills folder at both scopes. A coverage test now requires every Orca agent to have an entry or a recorded reason it has none. * Use the shared skills folder for Qoder Qoder's CLI loads .agents/skills at both scopes by default, so its own .qoder/skills placement made every skill appear twice. * Keep scanning Qoder's own skill folders Qoder still reads .qoder/skills, so discovery keeps both folders as discovery-only roots now that installs go to the canonical root.
This commit is contained in:
@@ -185,7 +185,7 @@ describe('bounded concurrent skill discovery', () => {
|
||||
const line = String(info.mock.calls.at(0)?.at(0))
|
||||
// `present` is the signal that separates "big tree" from "big root set", and
|
||||
// is not derivable from the other counts.
|
||||
expect(line).toContain('[skills] scan roots=33 present=3 walked=33 skills=3')
|
||||
expect(line).toContain('[skills] scan roots=35 present=3 walked=35 skills=3')
|
||||
expect(line).toContain('home-claude')
|
||||
expect(line).not.toContain(home)
|
||||
expect(line).not.toContain(tmpdir())
|
||||
|
||||
@@ -165,6 +165,14 @@ export function buildSkillDiscoverySources(
|
||||
['agent-skills'],
|
||||
'omp'
|
||||
),
|
||||
source(
|
||||
'home-qoder',
|
||||
'Qoder home',
|
||||
pathApi.join(home, '.qoder', 'skills'),
|
||||
'home',
|
||||
['agent-skills'],
|
||||
'qoder'
|
||||
),
|
||||
source('home-hermes', 'Hermes home', hermesSkillsRoot, 'home', ['agent-skills'], 'hermes'),
|
||||
source(
|
||||
'home-prime-agent',
|
||||
@@ -210,7 +218,15 @@ export function buildSkillDiscoverySources(
|
||||
null
|
||||
),
|
||||
...providerRepoSources(repoPath, label, pathApi),
|
||||
// OpenCode also reads its own project folder; it installs into the canonical root above.
|
||||
// OpenCode and Qoder also read their own project folders; they install into the canonical root above.
|
||||
source(
|
||||
`repo-qoder-${stablePathId(repoPath)}`,
|
||||
`${label} .qoder`,
|
||||
pathApi.join(repoPath, '.qoder', 'skills'),
|
||||
'repo',
|
||||
['agent-skills'],
|
||||
'qoder'
|
||||
),
|
||||
source(
|
||||
`repo-opencode-${stablePathId(repoPath)}`,
|
||||
`${label} .opencode`,
|
||||
|
||||
@@ -128,7 +128,7 @@ describe('resolveSkillProviderDestinations', () => {
|
||||
resolveSkillProviderDestinations({
|
||||
scope: 'global',
|
||||
homeDirectory: HOME,
|
||||
detectedProviders: ['aider', 'ante', 'not-an-agent']
|
||||
detectedProviders: ['aider', 'hermes', 'not-an-agent']
|
||||
})
|
||||
).toEqual([])
|
||||
})
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { join, posix, resolve } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { SKILL_INSTALL_PROVIDERS } from '../../shared/skill-install-providers'
|
||||
import type { TuiAgent } from '../../shared/tui-agent'
|
||||
import { TUI_AGENT_CONFIG } from '../../shared/tui-agent-config'
|
||||
import { buildSkillDiscoverySources } from './skill-discovery-sources'
|
||||
import { resolveEnvironmentSkillProviderRoots } from './skill-provider-runtime-roots'
|
||||
|
||||
const HOME = '/home/test'
|
||||
|
||||
// Why: a new agent must get an entry or a recorded reason, never a silent gap.
|
||||
const AGENTS_WITHOUT_INSTALL_PROVIDER: Partial<Record<TuiAgent, string>> = {
|
||||
'claude-agent-teams': 'detected only alongside claude, whose entry covers .claude/skills',
|
||||
aider: 'no skills support',
|
||||
hermes: 'Windows home under %LOCALAPPDATA% and opt-in project skills fit no table shape'
|
||||
}
|
||||
const WORKSPACE = '/workspace/current'
|
||||
|
||||
function discover(providerRootOverrides = {}) {
|
||||
@@ -75,4 +84,26 @@ describe('skill install provider table drives discovery', () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('gives every Orca agent an entry or a recorded reason it has none', () => {
|
||||
const covered = new Set<string>(SKILL_INSTALL_PROVIDERS.map((provider) => provider.id))
|
||||
for (const agent of Object.keys(TUI_AGENT_CONFIG)) {
|
||||
expect(
|
||||
covered.has(agent) !== Object.hasOwn(AGENTS_WITHOUT_INSTALL_PROVIDER, agent),
|
||||
agent
|
||||
).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps scanning Qoder folders it still reads after installs moved to the canonical root', () => {
|
||||
const qoderPaths = discover()
|
||||
.filter((source) => source.owner === 'qoder')
|
||||
.map((source) => source.path)
|
||||
expect(qoderPaths).toEqual(
|
||||
expect.arrayContaining([
|
||||
posix.join(HOME, '.qoder', 'skills'),
|
||||
posix.join(WORKSPACE, '.qoder', 'skills')
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -97,12 +97,6 @@ export const SKILL_INSTALL_PROVIDERS = Object.freeze([
|
||||
globalSegments: ['.codebuddy', 'skills'],
|
||||
workspaceSegments: ['.codebuddy', 'skills']
|
||||
},
|
||||
{
|
||||
id: 'qoder',
|
||||
displayName: 'Qoder',
|
||||
globalSegments: ['.qoder', 'skills'],
|
||||
workspaceSegments: ['.qoder', 'skills']
|
||||
},
|
||||
{
|
||||
id: 'kiro',
|
||||
displayName: 'Kiro',
|
||||
@@ -118,6 +112,14 @@ export const SKILL_INSTALL_PROVIDERS = Object.freeze([
|
||||
globalSegments: ['.gemini', 'config', 'skills'],
|
||||
workspaceSegments: null
|
||||
},
|
||||
// Why: OpenClaude keeps its own `.openclaude` home; it does not read Claude's folders by default.
|
||||
{
|
||||
id: 'openclaude',
|
||||
displayName: 'OpenClaude',
|
||||
globalSegments: ['.openclaude', 'skills'],
|
||||
workspaceSegments: ['.openclaude', 'skills'],
|
||||
homeEnv: 'OPENCLAUDE_CONFIG_DIR'
|
||||
},
|
||||
// Why: every agent below reads the canonical .agents/skills root at both scopes.
|
||||
{
|
||||
id: 'autohand',
|
||||
@@ -214,6 +216,72 @@ export const SKILL_INSTALL_PROVIDERS = Object.freeze([
|
||||
displayName: 'GitHub Copilot',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'opencode2',
|
||||
displayName: 'OpenCode 2',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'mimo-code',
|
||||
displayName: 'MiMo Code',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'omp',
|
||||
displayName: 'OMP',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'prime-agent',
|
||||
displayName: 'Prime Agent',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'qoder',
|
||||
displayName: 'Qoder',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'qoder-cn',
|
||||
displayName: 'Qoder CN',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'codebuff',
|
||||
displayName: 'Codebuff',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'freebuff',
|
||||
displayName: 'Freebuff',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'ante',
|
||||
displayName: 'Ante',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'jcode',
|
||||
displayName: 'Jcode',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
},
|
||||
{
|
||||
id: 'dsh',
|
||||
displayName: 'DeepSeek Harness',
|
||||
globalSegments: null,
|
||||
workspaceSegments: null
|
||||
}
|
||||
] satisfies SkillInstallProviderEntry[])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user