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
2 changes: 1 addition & 1 deletion packages/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ npm install @skillkit/agents
| Antigravity | SKILL.md | `.antigravity/skills/` | - |
| Amp | SKILL.md | `.amp/skills/` | - |
| Clawdbot | SKILL.md | `.clawdbot/skills/` | - |
| OpenClaw | SKILL.md | `skills/` | - |
| OpenClaw | SKILL.md | `.openclaw/skills/` | `~/.openclaw/workspace/skills/` |
| Cline | SKILL.md | `.cline/skills/` | - |
| CodeBuddy | SKILL.md | `.codebuddy/skills/` | - |
| CodeGPT | SKILL.md | `.codegpt/skills/` | - |
Expand Down
5 changes: 4 additions & 1 deletion packages/agents/src/openclaw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ export class OpenClawAdapter extends ClawdbotAdapter {
override readonly configFile = config.configFile;

override async isDetected(): Promise<boolean> {
const projectOpenClaw = join(process.cwd(), '.openclaw');
const globalOpenClaw = join(homedir(), '.openclaw');
const globalWorkspace = join(homedir(), '.openclaw', 'workspace');
const openclawConfig = join(process.cwd(), 'openclaw.json');

return existsSync(globalOpenClaw) || existsSync(openclawConfig);
return existsSync(projectOpenClaw) || existsSync(globalOpenClaw) ||
existsSync(globalWorkspace) || existsSync(openclawConfig);
Comment on lines +19 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Redundant existsSync(globalWorkspace) check — always shadowed by parent directory check

globalWorkspace is join(homedir(), '.openclaw', 'workspace') (i.e. ~/.openclaw/workspace), while globalOpenClaw is join(homedir(), '.openclaw') (i.e. ~/.openclaw). Since ~/.openclaw/workspace is a subdirectory of ~/.openclaw, it's impossible for existsSync(globalWorkspace) to be true when existsSync(globalOpenClaw) is false — the parent directory must exist for the child to exist. The globalWorkspace variable and its check on line 22-23 are dead code. If the intent was to detect specifically the workspace subdirectory (not just any .openclaw presence), the globalOpenClaw check should be removed or reordered.

Suggested change
const globalWorkspace = join(homedir(), '.openclaw', 'workspace');
const openclawConfig = join(process.cwd(), 'openclaw.json');
return existsSync(globalOpenClaw) || existsSync(openclawConfig);
return existsSync(projectOpenClaw) || existsSync(globalOpenClaw) ||
existsSync(globalWorkspace) || existsSync(openclawConfig);
const globalWorkspace = join(homedir(), '.openclaw', 'workspace');
const openclawConfig = join(process.cwd(), 'openclaw.json');
return existsSync(projectOpenClaw) || existsSync(globalWorkspace) ||
existsSync(openclawConfig);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
}
11 changes: 7 additions & 4 deletions packages/core/src/agent-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface AgentDirectoryConfig {
skillsDir: string;
/** Config file that references skills */
configFile: string;
/** Alternative config files that also mark this agent as present */
altConfigFiles?: string[];
/** Alternative skills directories */
altSkillsDirs?: string[];
/** Global skills directory */
Expand Down Expand Up @@ -121,10 +123,11 @@ export const AGENT_CONFIG: Record<AgentType, AgentDirectoryConfig> = {
},

openclaw: {
skillsDir: 'skills',
configFile: 'CLAUDE.md',
altSkillsDirs: ['~/.openclaw/skills'],
globalSkillsDir: '~/.openclaw/skills',
skillsDir: '.openclaw/skills',
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
configFile: 'AGENTS.md',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep openclaw.json as a core detection marker.

ContextSync checks only config.configFile, so changing this to AGENTS.md makes projects with only upstream openclaw.json invisible to core agent detection, even though OpenClawAdapter.isDetected() still checks it.

🐛 Proposed direction
 export interface AgentDirectoryConfig {
   /** Config file that references skills */
   configFile: string;
+  /** Additional config files that identify the agent */
+  altConfigFiles?: string[];
   openclaw: {
     skillsDir: '.openclaw/skills',
     configFile: 'AGENTS.md',
+    altConfigFiles: ['openclaw.json'],
     altSkillsDirs: ['skills', '~/.openclaw/workspace/skills'],

Then update core detection to check config.configFile plus config.altConfigFiles.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/agent-config.ts` at line 125, ContextSync's detection
currently only uses config.configFile and changing it to 'AGENTS.md' hides
projects that only have 'openclaw.json' even though OpenClawAdapter.isDetected()
still checks it; update ContextSync's detection logic (where it reads
config.configFile) to also consult a new config.altConfigFiles array (or
similar) so it checks both config.configFile and each entry in
config.altConfigFiles when determining detection; ensure
OpenClawAdapter.isDetected() remains compatible and add/rename the config
property references (config.configFile and config.altConfigFiles) used by the
detection code to cover both markers.

altConfigFiles: ['openclaw.json'],
altSkillsDirs: ['skills', '~/.openclaw/workspace/skills'],
globalSkillsDir: '~/.openclaw/workspace/skills',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wire globalSkillsDir into skill search paths.

The new value is not enough by itself: getSearchDirs() currently derives the home path from adapter.skillsDir, which points OpenClaw at ~/.openclaw/skills instead of ~/.openclaw/workspace/skills.

🐛 Proposed direction
 export function getSearchDirs(adapter: AgentAdapterInfo): string[] {
   const dirs: string[] = [];

   dirs.push(join(process.cwd(), adapter.skillsDir));
   dirs.push(join(process.cwd(), '.agent', 'skills'));
-  dirs.push(join(homedir(), adapter.skillsDir));
+  if (adapter.globalSkillsDir) {
+    dirs.push(adapter.globalSkillsDir.replace(/^~(?=\/)/, homedir()));
+  } else {
+    dirs.push(join(homedir(), adapter.skillsDir));
+  }
   dirs.push(join(homedir(), '.agent', 'skills'));

   return dirs;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/agent-config.ts` at line 127, getSearchDirs() is still
deriving the home path from adapter.skillsDir so setting globalSkillsDir alone
doesn't change the actual search locations; update getSearchDirs() (and any code
that reads adapter.skillsDir) to include or prefer the new globalSkillsDir value
when constructing search paths (e.g., treat globalSkillsDir as the canonical
home/workspace path for global skills), and ensure adapter.skillsDir falls back
to or is composed from globalSkillsDir so that "~/.openclaw/workspace/skills" is
used instead of "~/.openclaw/skills".

configFormat: 'xml',
usesFrontmatter: true,
frontmatterFields: [
Expand Down
12 changes: 11 additions & 1 deletion packages/core/src/config.ts
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 getInstallDir global path inconsistent with getSearchDirs for openclaw

The PR updated getSearchDirs (line 104-112) to resolve the global skills directory from AGENT_CONFIG[adapter.type]?.globalSkillsDir, which for openclaw resolves to ~/.openclaw/workspace/skills. However, the companion function getInstallDir (packages/core/src/config.ts:118-124) was not updated and still uses join(homedir(), adapter.skillsDir), which for openclaw yields ~/.openclaw/skills. This means skills installed globally via skillkit install --global go to ~/.openclaw/skills, but getSearchDirs looks in ~/.openclaw/workspace/skills — so globally installed skills won't be found during search or sync. The same function is called by packages/cli/src/helpers.ts:36 and packages/tui/src/utils/helpers.ts:48 and used in install/quick commands.

(Refers to lines 119-120)

Prompt for agents
The getInstallDir function at packages/core/src/config.ts:118-124 uses join(homedir(), adapter.skillsDir) for the global install path. This was fine before because getSearchDirs also used the same expression. However, getSearchDirs was updated (lines 104-112) to resolve the actual globalSkillsDir from AGENT_CONFIG, which for openclaw is ~/.openclaw/workspace/skills (not ~/.openclaw/skills which adapter.skillsDir would produce).

The fix should make getInstallDir also use AGENT_CONFIG[adapter.type]?.globalSkillsDir when available, with the same tilde-resolution logic. Something like:

  if (global) {
    const globalDir = AGENT_CONFIG[adapter.type]?.globalSkillsDir;
    if (globalDir) {
      return globalDir.startsWith('~/')
        ? join(homedir(), globalDir.slice(2))
        : globalDir;
    }
    return join(homedir(), adapter.skillsDir);
  }

This ensures getInstallDir and getSearchDirs agree on the global directory location. The AGENT_CONFIG import is already present in this file (line 7).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { homedir } from 'node:os';
import { createHash } from 'node:crypto';
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
import { SkillkitConfig, LockFile, type AgentType, type SkillMetadata, type AgentAdapterInfo, type LockEntry } from './types.js';
import { AGENT_CONFIG } from './agent-config.js';

const CONFIG_FILE = 'skillkit.yaml';
const METADATA_FILE = '.skillkit.json';
Expand Down Expand Up @@ -99,7 +100,16 @@ export function getSearchDirs(adapter: AgentAdapterInfo): string[] {

dirs.push(join(process.cwd(), adapter.skillsDir));
dirs.push(join(process.cwd(), '.agent', 'skills'));
dirs.push(join(homedir(), adapter.skillsDir));

const globalDir = AGENT_CONFIG[adapter.type]?.globalSkillsDir;
if (globalDir) {
const resolved = globalDir.startsWith('~/')
? join(homedir(), globalDir.slice(2))
: globalDir;
dirs.push(resolved);
} else {
dirs.push(join(homedir(), adapter.skillsDir));
}
dirs.push(join(homedir(), '.agent', 'skills'));

return dirs;
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/context/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@ export class ContextSync {
const skillsPath = join(this.projectPath, config.skillsDir);
const configPath = join(this.projectPath, config.configFile);

// Check if either skills directory or config file exists
if (existsSync(skillsPath) || existsSync(configPath)) {
const altConfigExists = (config.altConfigFiles ?? []).some((alt) =>
existsSync(join(this.projectPath, alt))
);

if (existsSync(skillsPath) || existsSync(configPath) || altConfigExists) {
detected.push(agent as AgentType);
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const SKILL_DISCOVERY_PATHS = [
'.mux/skills',
'.neovate/skills',
'.opencode/skills',
'.openclaw/skills',
'.openhands/skills',
'.pi/skills',
'.playcode/skills',
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/src/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ const AGENT_DIR_MAP: Record<string, string[]> = {
'claude-code': ['.claude/skills'], 'cursor': ['.cursor/skills'], 'codex': ['.codex/skills'],
'gemini-cli': ['.gemini/skills'], 'opencode': ['.opencode/skills', '.config/opencode/skills'],
'antigravity': ['.antigravity/skills'], 'amp': ['.amp/skills'], 'clawdbot': ['.clawdbot/skills'],
'openclaw': ['skills'], 'github-copilot': ['.github/skills'], 'goose': ['.goose/skills'],
'openclaw': ['.openclaw/skills', '.openclaw/workspace/skills', 'skills'], 'github-copilot': ['.github/skills'], 'goose': ['.goose/skills'],
'kilo': ['.kilocode/skills'], 'kiro-cli': ['.kiro/skills'], 'roo': ['.roo/skills'],
'trae': ['.trae/skills'], 'windsurf': ['.windsurf/skills', '.codeium/windsurf/skills'],
'universal': ['skills'], 'droid': ['.factory/skills'], 'factory': ['.factory/skills'],
Expand Down
Loading