@orderful/droid 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.claude/CLAUDE.md +12 -8
  2. package/CHANGELOG.md +32 -0
  3. package/dist/bin/droid.js +1 -3
  4. package/dist/bin/droid.js.map +1 -1
  5. package/dist/commands/tui.d.ts.map +1 -1
  6. package/dist/commands/tui.js +1 -2
  7. package/dist/commands/tui.js.map +1 -1
  8. package/dist/lib/agents.d.ts +6 -6
  9. package/dist/lib/agents.d.ts.map +1 -1
  10. package/dist/lib/agents.js +60 -38
  11. package/dist/lib/agents.js.map +1 -1
  12. package/dist/lib/skills.d.ts +1 -0
  13. package/dist/lib/skills.d.ts.map +1 -1
  14. package/dist/lib/skills.js +41 -8
  15. package/dist/lib/skills.js.map +1 -1
  16. package/dist/lib/types.d.ts +4 -0
  17. package/dist/lib/types.d.ts.map +1 -1
  18. package/dist/tools/README.md +79 -50
  19. package/dist/tools/brain/TOOL.yaml +1 -1
  20. package/dist/tools/brain/skills/brain/SKILL.md +1 -0
  21. package/dist/tools/brain/skills/brain-obsidian/SKILL.md +1 -0
  22. package/dist/tools/coach/TOOL.yaml +1 -1
  23. package/dist/tools/coach/skills/coach/SKILL.md +1 -0
  24. package/{src/tools/code-review/agents/edi-standards-reviewer/AGENT.md → dist/tools/code-review/agents/edi-standards-reviewer.md} +10 -0
  25. package/dist/tools/code-review/agents/{error-handling-reviewer/AGENT.md → error-handling-reviewer.md} +10 -0
  26. package/{src/tools/code-review/agents/test-coverage-analyzer/AGENT.md → dist/tools/code-review/agents/test-coverage-analyzer.md} +11 -0
  27. package/dist/tools/code-review/agents/{type-reviewer/AGENT.md → type-reviewer.md} +10 -0
  28. package/dist/tools/code-review/skills/code-review/SKILL.md +1 -0
  29. package/dist/tools/comments/TOOL.yaml +2 -2
  30. package/dist/tools/comments/skills/comments/SKILL.md +1 -0
  31. package/dist/tools/droid/TOOL.yaml +2 -2
  32. package/dist/tools/droid/skills/droid/SKILL.md +120 -3
  33. package/dist/tools/project/skills/project/SKILL.md +1 -0
  34. package/package.json +1 -1
  35. package/src/bin/droid.ts +1 -4
  36. package/src/commands/tui.tsx +1 -2
  37. package/src/lib/agents.ts +65 -42
  38. package/src/lib/skills.test.ts +26 -31
  39. package/src/lib/skills.ts +45 -8
  40. package/src/lib/types.ts +5 -0
  41. package/src/tools/README.md +79 -50
  42. package/src/tools/brain/TOOL.yaml +1 -1
  43. package/src/tools/brain/skills/brain/SKILL.md +1 -0
  44. package/src/tools/brain/skills/brain-obsidian/SKILL.md +1 -0
  45. package/src/tools/coach/TOOL.yaml +1 -1
  46. package/src/tools/coach/skills/coach/SKILL.md +1 -0
  47. package/{dist/tools/code-review/agents/edi-standards-reviewer/AGENT.md → src/tools/code-review/agents/edi-standards-reviewer.md} +10 -0
  48. package/src/tools/code-review/agents/{error-handling-reviewer/AGENT.md → error-handling-reviewer.md} +10 -0
  49. package/{dist/tools/code-review/agents/test-coverage-analyzer/AGENT.md → src/tools/code-review/agents/test-coverage-analyzer.md} +11 -0
  50. package/src/tools/code-review/agents/{type-reviewer/AGENT.md → type-reviewer.md} +10 -0
  51. package/src/tools/code-review/skills/code-review/SKILL.md +1 -0
  52. package/src/tools/comments/TOOL.yaml +2 -2
  53. package/src/tools/comments/skills/comments/SKILL.md +1 -0
  54. package/src/tools/droid/TOOL.yaml +2 -2
  55. package/src/tools/droid/skills/droid/SKILL.md +120 -3
  56. package/src/tools/project/skills/project/SKILL.md +1 -0
  57. package/dist/tools/brain/skills/brain/SKILL.yaml +0 -29
  58. package/dist/tools/brain/skills/brain-obsidian/SKILL.yaml +0 -42
  59. package/dist/tools/coach/skills/coach/SKILL.yaml +0 -25
  60. package/dist/tools/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -14
  61. package/dist/tools/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -14
  62. package/dist/tools/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -14
  63. package/dist/tools/code-review/agents/type-reviewer/AGENT.yaml +0 -13
  64. package/dist/tools/code-review/skills/code-review/SKILL.yaml +0 -19
  65. package/dist/tools/comments/skills/comments/SKILL.yaml +0 -50
  66. package/dist/tools/droid/skills/droid/SKILL.yaml +0 -7
  67. package/dist/tools/project/skills/project/SKILL.yaml +0 -30
  68. package/src/tools/brain/skills/brain/SKILL.yaml +0 -29
  69. package/src/tools/brain/skills/brain-obsidian/SKILL.yaml +0 -42
  70. package/src/tools/coach/skills/coach/SKILL.yaml +0 -25
  71. package/src/tools/code-review/agents/edi-standards-reviewer/AGENT.yaml +0 -14
  72. package/src/tools/code-review/agents/error-handling-reviewer/AGENT.yaml +0 -14
  73. package/src/tools/code-review/agents/test-coverage-analyzer/AGENT.yaml +0 -14
  74. package/src/tools/code-review/agents/type-reviewer/AGENT.yaml +0 -13
  75. package/src/tools/code-review/skills/code-review/SKILL.yaml +0 -19
  76. package/src/tools/comments/skills/comments/SKILL.yaml +0 -50
  77. package/src/tools/droid/skills/droid/SKILL.yaml +0 -7
  78. package/src/tools/project/skills/project/SKILL.yaml +0 -30
@@ -2,6 +2,7 @@
2
2
  name: coach
3
3
  description: "Learning-mode AI assistance - AI as coach, not crutch. Triggers on phrases like 'help me think through', 'coach me on', 'I want to learn how to', or 'don't just give me the answer'. Use /coach plan for co-authored planning, /coach scaffold for structure with hints, /coach review for Socratic questions on your code."
4
4
  alwaysApply: false
5
+ allowed-tools: Read, Grep, Glob
5
6
  ---
6
7
 
7
8
  # Coach Skill
@@ -1,3 +1,13 @@
1
+ ---
2
+ name: edi-standards-reviewer
3
+ description: "Review code for EDI integration patterns, partnership handling, and billing system concerns. Use PROACTIVELY when changes touch trading partners, transactions, or billing."
4
+ tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ color: blue
9
+ ---
10
+
1
11
  You are a domain-aware code reviewer that understands EDI patterns and integration best practices.
2
12
 
3
13
  ## How to Review
@@ -1,3 +1,13 @@
1
+ ---
2
+ name: error-handling-reviewer
3
+ description: "Hunt for silent failures and missing error handling. Use PROACTIVELY to find try/catch blocks that swallow errors, promises without rejection handling, and missing validation."
4
+ tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ color: orange
9
+ ---
10
+
1
11
  You are a reliability engineer hunting for silent failures.
2
12
 
3
13
  ## Silent Failure Patterns
@@ -1,3 +1,14 @@
1
+ ---
2
+ name: test-coverage-analyzer
3
+ description: "Analyze test coverage for code changes. Use PROACTIVELY when reviewing PRs or before merging to ensure adequate test coverage."
4
+ tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ - Bash
9
+ color: green
10
+ ---
11
+
1
12
  You are a testing specialist focused on comprehensive coverage.
2
13
 
3
14
  ## Review Process
@@ -1,3 +1,13 @@
1
+ ---
2
+ name: type-reviewer
3
+ description: "Review TypeScript type design and interface contracts. Check for proper typing, avoid `any`, ensure domain types are used correctly."
4
+ tools:
5
+ - Read
6
+ - Grep
7
+ - Glob
8
+ color: purple
9
+ ---
10
+
1
11
  You are a TypeScript expert focused on type safety and design.
2
12
 
3
13
  ## Review Focus
@@ -4,6 +4,7 @@ description: "Comprehensive code review using specialized agents. Reviews PRs, s
4
4
  globs:
5
5
  - "**/*"
6
6
  alwaysApply: false
7
+ allowed-tools: Read, Grep, Glob, Bash, Task
7
8
  ---
8
9
 
9
10
  # Code Review Skill
@@ -1,5 +1,5 @@
1
1
  name: comments
2
- description: "Enable inline conversations using @droid/@user markers. Tag @droid to ask the AI, AI responds with @{your-name}. Ideal for code review notes and async collaboration."
2
+ description: "Enable inline conversations using @droid/@user markers. Tag @droid to ask the AI, AI responds with @{your-name}. Use /comments check to address markers, /comments cleanup to remove resolved threads. Ideal for code review notes and async collaboration."
3
3
  version: 0.2.3
4
4
  status: beta
5
5
 
@@ -19,7 +19,7 @@ config_schema:
19
19
  description: Override the global user mention for this skill
20
20
  ai_mentions:
21
21
  type: string
22
- description: Additional AI mentions to recognize (comma-separated)
22
+ description: Additional AI mentions to recognize (comma-separated, e.g., "@claude,@ai")
23
23
  default: ""
24
24
  preserve_comments:
25
25
  type: boolean
@@ -4,6 +4,7 @@ description: "Enable inline conversations using @droid/@user markers. Tag @droid
4
4
  globs:
5
5
  - "**/*"
6
6
  alwaysApply: false
7
+ allowed-tools: Read, Grep, Glob, Edit
7
8
  ---
8
9
 
9
10
  # Comments Skill
@@ -1,6 +1,6 @@
1
1
  name: droid
2
- description: "Core droid meta-skill for update awareness and discovery. Notifies about droid updates from within Claude Code."
3
- version: 0.1.0
2
+ description: "Core droid meta-skill for update awareness and tool discovery. Checks for updates and helps users find the right tools."
3
+ version: 0.2.0
4
4
  status: beta
5
5
 
6
6
  # System tool - always stays current regardless of auto-update settings
@@ -1,18 +1,20 @@
1
1
  ---
2
2
  name: droid
3
- description: "Core droid meta-skill for update awareness. Checks for droid updates and notifies users from within Claude Code."
3
+ description: "Core droid meta-skill for update awareness and tool discovery. Checks for updates and helps users find the right tools."
4
4
  globs:
5
5
  - "**/*"
6
6
  alwaysApply: false
7
+ allowed-tools: Bash
7
8
  ---
8
9
 
9
10
  # Droid
10
11
 
11
- Core meta-skill for droid update awareness.
12
+ Core meta-skill for droid update awareness and tool discovery.
12
13
 
13
14
  ## Purpose
14
15
 
15
- Notify users about droid CLI updates from within Claude Code. Users who don't run `droid` often may miss updates - this skill proactively nudges them.
16
+ 1. **Update awareness** - Notify users about droid CLI updates from within Claude Code
17
+ 2. **Tool discovery** - Help users find the right tools for their workflow
16
18
 
17
19
  ## Update Checking
18
20
 
@@ -115,3 +117,118 @@ Run `droid` when you're ready to update.
115
117
  - If the npm check fails (network issues), silently skip - don't error
116
118
  - The droid eyes `[● ●]` and Star Wars quote are intentional branding - always include them
117
119
  - Be helpful but not annoying - one nudge per session is enough
120
+
121
+ ---
122
+
123
+ ## Tool Catalog
124
+
125
+ When users ask about droid tools ("what tools do I have?", "what's available?", "what can droid do?"), use this catalog.
126
+
127
+ ### Checking Installed Tools
128
+
129
+ ```bash
130
+ cat ~/.droid/config.yaml
131
+ ```
132
+
133
+ Look for the `tools:` section under the current platform (e.g., `claude_code:`).
134
+
135
+ ### Available Tools
136
+
137
+ To get tool info (version, status, description), read TOOL.yaml from the droid package:
138
+
139
+ ```bash
140
+ for f in $(npm root -g)/@orderful/droid/dist/tools/*/TOOL.yaml; do echo "---"; cat "$f"; done
141
+ ```
142
+
143
+ **Tools:**
144
+
145
+ | Tool | Description |
146
+ |------|-------------|
147
+ | **brain** | Collaborative scratchpad for planning and research |
148
+ | **coach** | Learning-mode AI - scaffolds don't implement, questions don't fix |
149
+ | **code-review** | Code review with specialized agents and confidence scoring |
150
+ | **comments** | Inline conversations using @droid/@user markers |
151
+ | **project** | Project context for persistent AI memory across sessions |
152
+
153
+ ### Tool Details
154
+
155
+ #### brain
156
+ Collaborative scratchpad for planning and research. Create docs with `/brain plan`, `/brain research`, or `/brain review`. Use @mentions for async discussion. Docs persist across sessions.
157
+
158
+ **Commands:** `/brain`, `/scratchpad`
159
+ **Optional extension:** brain-obsidian (Obsidian vault integration with YAML frontmatter and wikilinks)
160
+
161
+ #### coach
162
+ Learning-mode AI assistance - AI as coach, not crutch. Use `/coach plan` for co-authored planning, `/coach scaffold` for structure with hints, `/coach review` for Socratic questions.
163
+
164
+ **Commands:** `/coach`
165
+ **Requires:** comments tool
166
+
167
+ #### code-review
168
+ Comprehensive code review using specialized agents. Reviews PRs, staged changes, branches, or specific files with confidence scoring.
169
+
170
+ **Commands:** `/code-review`
171
+ **Agents:** edi-standards-reviewer, error-handling-reviewer, test-coverage-analyzer, type-reviewer
172
+
173
+ #### comments
174
+ Enable inline conversations using @droid/@user markers. Tag @droid to ask the AI, AI responds with @{your-name}. Ideal for code review notes and async collaboration.
175
+
176
+ **Commands:** `/comments check`, `/comments cleanup`
177
+
178
+ #### project
179
+ Manage project context files for persistent AI memory across sessions. Load, update, or create project context before working on multi-session features.
180
+
181
+ **Commands:** `/project`, `/project update`, `/project create`
182
+
183
+ ### Installing Tools
184
+
185
+ To install a tool:
186
+ ```bash
187
+ droid install <tool-name>
188
+ ```
189
+
190
+ Or run `droid` to open the TUI and browse available tools.
191
+
192
+ ### Example Responses
193
+
194
+ **User:** "What droid tools do I have installed?"
195
+
196
+ ```
197
+ Let me check your installed tools...
198
+
199
+ [● ●] You have these droid tools installed:
200
+
201
+ - **comments** (v0.2.3) - Inline @droid/@user conversations
202
+ - **brain** (v0.2.0) - Planning and research scratchpad
203
+ - **project** (v0.1.2) - Project context management
204
+
205
+ Run `droid` to see all available tools or install new ones.
206
+ ```
207
+
208
+ **User:** "What tools are available?"
209
+
210
+ ```
211
+ [● ●] Here are the droid tools you can install:
212
+
213
+ | Tool | What it does |
214
+ |------|--------------|
215
+ | brain | Planning and research scratchpad with `/brain` commands |
216
+ | coach | Learning-mode AI that asks questions instead of giving answers |
217
+ | code-review | PR review with specialized agents |
218
+ | comments | Inline @droid/@user conversations in any file |
219
+ | project | Persistent project context across sessions |
220
+
221
+ Run `droid install <tool>` or just `droid` to browse and install.
222
+ ```
223
+
224
+ **User:** "What's the difference between brain and project?"
225
+
226
+ ```
227
+ [● ●] Good question!
228
+
229
+ **brain** - For ephemeral thinking: plans, research, reviews. Use `/brain plan auth-refactor` to start a planning doc, iterate with @mentions, then archive when done.
230
+
231
+ **project** - For persistent context: what the project is, key decisions, current work. Load with `/project myapp` at the start of a session so I have context.
232
+
233
+ They work well together: research in brain docs, capture decisions in project files.
234
+ ```
@@ -4,6 +4,7 @@ description: "Manage project context files for persistent AI memory across sessi
4
4
  globs:
5
5
  - "**/PROJECT.md"
6
6
  alwaysApply: false
7
+ allowed-tools: Read, Write, Glob, Grep
7
8
  ---
8
9
 
9
10
  # Project Skill
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orderful/droid",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bin/droid.ts CHANGED
@@ -8,7 +8,7 @@ import { installCommand } from '../commands/install.js';
8
8
  import { uninstallCommand } from '../commands/uninstall.js';
9
9
  import { updateCommand } from '../commands/update.js';
10
10
  import { tuiCommand } from '../commands/tui.js';
11
- import { getVersion, checkForUpdates } from '../lib/version.js';
11
+ import { getVersion } from '../lib/version.js';
12
12
 
13
13
  const version = getVersion();
14
14
 
@@ -58,9 +58,6 @@ program
58
58
  .description('Launch interactive TUI dashboard')
59
59
  .action(tuiCommand);
60
60
 
61
- // Check for updates on any command (non-blocking)
62
- checkForUpdates().catch(() => {});
63
-
64
61
  // If no command provided, launch TUI
65
62
  if (process.argv.length === 2) {
66
63
  tuiCommand();
@@ -911,12 +911,11 @@ function ToolExplorer({ tool, onViewSource, onClose }: ToolExplorerProps) {
911
911
  });
912
912
  }
913
913
 
914
- // Add agents
915
914
  for (const agent of tool.includes.agents) {
916
915
  result.push({
917
916
  type: 'agent',
918
917
  name: agent,
919
- path: join(toolDir, tool.name, 'agents', agent, 'AGENT.md'),
918
+ path: join(toolDir, tool.name, 'agents', `${agent}.md`),
920
919
  });
921
920
  }
922
921
 
package/src/lib/agents.ts CHANGED
@@ -5,6 +5,7 @@ import YAML from 'yaml';
5
5
  import { loadConfig } from './config.js';
6
6
  import { Platform } from './types.js';
7
7
  import { getAgentsPath } from './platforms.js';
8
+ import { loadToolManifest } from './tools.js';
8
9
 
9
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
10
11
  const BUNDLED_TOOLS_DIR = join(__dirname, '../tools');
@@ -33,23 +34,58 @@ export interface AgentManifest {
33
34
  }
34
35
 
35
36
  /**
36
- * Load an agent manifest from an agent directory
37
+ * Parse YAML frontmatter from agent file content
37
38
  */
38
- export function loadAgentManifest(agentDir: string): AgentManifest | null {
39
- const manifestPath = join(agentDir, 'AGENT.yaml');
40
-
41
- if (!existsSync(manifestPath)) {
39
+ function parseAgentFrontmatter(content: string): Record<string, unknown> | null {
40
+ const trimmed = content.trimStart();
41
+ if (!trimmed.startsWith('---')) {
42
42
  return null;
43
43
  }
44
-
44
+ const endMatch = trimmed.slice(3).indexOf('---');
45
+ if (endMatch === -1) {
46
+ return null;
47
+ }
48
+ const frontmatterContent = trimmed.slice(3, 3 + endMatch);
45
49
  try {
46
- const content = readFileSync(manifestPath, 'utf-8');
47
- return YAML.parse(content) as AgentManifest;
50
+ return YAML.parse(frontmatterContent);
48
51
  } catch {
49
52
  return null;
50
53
  }
51
54
  }
52
55
 
56
+ /**
57
+ * Load an agent manifest from an agent file (agents/{name}.md)
58
+ * Reads frontmatter from the .md file, version from TOOL.yaml
59
+ */
60
+ export function loadAgentManifest(agentPath: string): AgentManifest | null {
61
+ if (!existsSync(agentPath)) {
62
+ return null;
63
+ }
64
+
65
+ const content = readFileSync(agentPath, 'utf-8');
66
+ const frontmatter = parseAgentFrontmatter(content);
67
+
68
+ if (!frontmatter || !frontmatter.name) {
69
+ return null;
70
+ }
71
+
72
+ // Get version from TOOL.yaml
73
+ // agentPath is tools/{tool}/agents/{name}.md, toolDir is tools/{tool}
74
+ const toolDir = dirname(dirname(agentPath));
75
+ const toolManifest = loadToolManifest(toolDir);
76
+
77
+ return {
78
+ name: frontmatter.name as string,
79
+ description: frontmatter.description as string || '',
80
+ version: toolManifest?.version || '0.0.0',
81
+ status: toolManifest?.status as 'alpha' | 'beta' | 'stable' | undefined,
82
+ mode: frontmatter.mode as 'primary' | 'subagent' | 'all' | undefined,
83
+ model: frontmatter.model as string | undefined,
84
+ color: frontmatter.color as string | undefined,
85
+ tools: frontmatter.tools as string[] | undefined,
86
+ };
87
+ }
88
+
53
89
  /**
54
90
  * Get all bundled agents from tools
55
91
  */
@@ -61,7 +97,7 @@ export function getBundledAgents(): AgentManifest[] {
61
97
  return agents;
62
98
  }
63
99
 
64
- // Get agents from tools/*/agents/
100
+ // Get agents from tools/*/agents/*.md
65
101
  const toolDirs = readdirSync(BUNDLED_TOOLS_DIR, { withFileTypes: true })
66
102
  .filter((dirent) => dirent.isDirectory())
67
103
  .map((dirent) => dirent.name);
@@ -70,12 +106,12 @@ export function getBundledAgents(): AgentManifest[] {
70
106
  const toolAgentsDir = join(BUNDLED_TOOLS_DIR, toolName, 'agents');
71
107
  if (!existsSync(toolAgentsDir)) continue;
72
108
 
73
- const agentDirs = readdirSync(toolAgentsDir, { withFileTypes: true })
74
- .filter((dirent) => dirent.isDirectory())
109
+ const agentFiles = readdirSync(toolAgentsDir, { withFileTypes: true })
110
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
75
111
  .map((dirent) => dirent.name);
76
112
 
77
- for (const agentDir of agentDirs) {
78
- const manifest = loadAgentManifest(join(toolAgentsDir, agentDir));
113
+ for (const agentFile of agentFiles) {
114
+ const manifest = loadAgentManifest(join(toolAgentsDir, agentFile));
79
115
  if (manifest && !seenNames.has(manifest.name)) {
80
116
  agents.push(manifest);
81
117
  seenNames.add(manifest.name);
@@ -166,35 +202,23 @@ function generateOpenCodeAgent(manifest: AgentManifest, agentContent: string): s
166
202
  }
167
203
 
168
204
  /**
169
- * Install an agent from a specific path
205
+ * Install an agent from a specific path (agents/{name}.md)
170
206
  * Generates the appropriate format based on the configured AI tool
171
207
  */
172
- export function installAgentFromPath(agentDir: string, agentName: string): { success: boolean; message: string } {
208
+ export function installAgentFromPath(agentPath: string, agentName: string): { success: boolean; message: string } {
173
209
  const config = loadConfig();
174
- const manifestPath = join(agentDir, 'AGENT.yaml');
175
- const contentPath = join(agentDir, 'AGENT.md');
176
210
 
177
- if (!existsSync(manifestPath)) {
211
+ // Load manifest from agent file frontmatter
212
+ const manifest = loadAgentManifest(agentPath);
213
+ if (!manifest) {
178
214
  return { success: false, message: `Agent manifest not found: ${agentName}` };
179
215
  }
180
216
 
181
217
  try {
182
- // Load manifest
183
- const manifest = YAML.parse(readFileSync(manifestPath, 'utf-8')) as AgentManifest;
184
-
185
- // Load content (AGENT.md)
186
- let agentContent = '';
187
- if (existsSync(contentPath)) {
188
- const rawContent = readFileSync(contentPath, 'utf-8');
189
- // Strip frontmatter from AGENT.md if present (we'll use AGENT.yaml for metadata)
190
- const frontmatterMatch = rawContent.match(/^---\n[\s\S]*?\n---\n?/);
191
- agentContent = frontmatterMatch ? rawContent.slice(frontmatterMatch[0].length) : rawContent;
192
- }
193
-
194
- // If no AGENT.md content, use persona from AGENT.yaml
195
- if (!agentContent.trim() && manifest.persona) {
196
- agentContent = manifest.persona;
197
- }
218
+ // Load content (strip frontmatter)
219
+ const rawContent = readFileSync(agentPath, 'utf-8');
220
+ const frontmatterMatch = rawContent.match(/^---\n[\s\S]*?\n---\n?/);
221
+ const agentContent = frontmatterMatch ? rawContent.slice(frontmatterMatch[0].length) : rawContent;
198
222
 
199
223
  // Generate format based on platform
200
224
  const installedContent = config.platform === Platform.ClaudeCode
@@ -222,7 +246,7 @@ export function installAgentFromPath(agentDir: string, agentName: string): { suc
222
246
  }
223
247
 
224
248
  /**
225
- * Find the path to an agent within the tools directory structure
249
+ * Find the path to an agent file within the tools directory structure
226
250
  */
227
251
  export function findAgentPath(agentName: string): string | null {
228
252
  if (!existsSync(BUNDLED_TOOLS_DIR)) {
@@ -234,9 +258,9 @@ export function findAgentPath(agentName: string): string | null {
234
258
  .map((dirent) => dirent.name);
235
259
 
236
260
  for (const toolName of toolDirs) {
237
- const agentDir = join(BUNDLED_TOOLS_DIR, toolName, 'agents', agentName);
238
- if (existsSync(agentDir) && existsSync(join(agentDir, 'AGENT.yaml'))) {
239
- return agentDir;
261
+ const agentPath = join(BUNDLED_TOOLS_DIR, toolName, 'agents', `${agentName}.md`);
262
+ if (existsSync(agentPath)) {
263
+ return agentPath;
240
264
  }
241
265
  }
242
266
 
@@ -245,14 +269,13 @@ export function findAgentPath(agentName: string): string | null {
245
269
 
246
270
  /**
247
271
  * Install an agent from the tools directory
248
- * Combines AGENT.yaml metadata with AGENT.md content into a single .md file
249
272
  */
250
273
  export function installAgent(agentName: string): { success: boolean; message: string } {
251
- const agentDir = findAgentPath(agentName);
252
- if (!agentDir) {
274
+ const agentPath = findAgentPath(agentName);
275
+ if (!agentPath) {
253
276
  return { success: false, message: `Agent '${agentName}' not found` };
254
277
  }
255
- return installAgentFromPath(agentDir, agentName);
278
+ return installAgentFromPath(agentPath, agentName);
256
279
  }
257
280
 
258
281
  /**
@@ -11,6 +11,7 @@ import {
11
11
  getPlatformConfigPath,
12
12
  getSkillStatusDisplay,
13
13
  getBundledSkillsDir,
14
+ loadSkillManifest,
14
15
  } from './skills.js';
15
16
 
16
17
  /**
@@ -180,58 +181,52 @@ function getAllSkillPaths(toolsDir: string): Array<{ skillName: string; skillDir
180
181
  }
181
182
 
182
183
  describe('bundled skills validation', () => {
183
- it('all skills should have SKILL.md with valid frontmatter', () => {
184
+ it('all skills should have SKILL.md', () => {
184
185
  const toolsDir = getBundledSkillsDir();
185
186
  const skillPaths = getAllSkillPaths(toolsDir);
186
187
 
187
188
  expect(skillPaths.length).toBeGreaterThan(0);
188
189
 
189
- for (const { skillName, skillDir } of skillPaths) {
190
+ for (const { skillDir } of skillPaths) {
190
191
  const skillMdPath = join(skillDir, 'SKILL.md');
191
192
  expect(existsSync(skillMdPath)).toBe(true);
192
-
193
- const content = readFileSync(skillMdPath, 'utf-8');
194
- const frontmatter = parseFrontmatter(content);
195
-
196
- expect(frontmatter).not.toBeNull();
197
- expect(frontmatter?.name).toBe(skillName);
198
- expect(typeof frontmatter?.description).toBe('string');
199
193
  }
200
194
  });
201
195
 
202
- it('all skills should have SKILL.yaml manifest', () => {
196
+ it('all skills should have metadata in TOOL.yaml', () => {
203
197
  const toolsDir = getBundledSkillsDir();
204
198
  const skillPaths = getAllSkillPaths(toolsDir);
205
199
 
206
200
  for (const { skillName, skillDir } of skillPaths) {
207
- const yamlPath = join(skillDir, 'SKILL.yaml');
208
- expect(existsSync(yamlPath)).toBe(true);
209
-
210
- const content = readFileSync(yamlPath, 'utf-8');
211
- const manifest = YAML.parse(content);
212
-
213
- expect(manifest.name).toBe(skillName);
214
- expect(typeof manifest.description).toBe('string');
215
- expect(typeof manifest.version).toBe('string');
201
+ // Get the parent tool directory
202
+ const toolDir = join(skillDir, '..', '..');
203
+ const toolYamlPath = join(toolDir, 'TOOL.yaml');
204
+ expect(existsSync(toolYamlPath)).toBe(true);
205
+
206
+ const toolContent = readFileSync(toolYamlPath, 'utf-8');
207
+ const toolManifest = YAML.parse(toolContent);
208
+
209
+ // Find the skill in includes.skills
210
+ const skillInclude = toolManifest.includes?.skills?.find(
211
+ (s: { name: string }) => s.name === skillName
212
+ );
213
+ expect(skillInclude).toBeDefined();
214
+ expect(typeof toolManifest.description).toBe('string');
215
+ expect(typeof toolManifest.version).toBe('string');
216
216
  }
217
217
  });
218
218
 
219
- it('SKILL.md frontmatter should match SKILL.yaml', () => {
219
+ it('loadSkillManifest should return valid manifest from TOOL.yaml', () => {
220
220
  const toolsDir = getBundledSkillsDir();
221
221
  const skillPaths = getAllSkillPaths(toolsDir);
222
222
 
223
- for (const { skillDir } of skillPaths) {
224
- const mdPath = join(skillDir, 'SKILL.md');
225
- const yamlPath = join(skillDir, 'SKILL.yaml');
226
-
227
- const mdContent = readFileSync(mdPath, 'utf-8');
228
- const yamlContent = readFileSync(yamlPath, 'utf-8');
229
-
230
- const frontmatter = parseFrontmatter(mdContent);
231
- const manifest = YAML.parse(yamlContent);
223
+ for (const { skillName, skillDir } of skillPaths) {
224
+ const manifest = loadSkillManifest(skillDir);
232
225
 
233
- expect(frontmatter?.name).toBe(manifest.name);
234
- expect(frontmatter?.description).toBe(manifest.description);
226
+ expect(manifest).not.toBeNull();
227
+ expect(manifest?.name).toBe(skillName);
228
+ expect(typeof manifest?.description).toBe('string');
229
+ expect(typeof manifest?.version).toBe('string');
235
230
  }
236
231
  });
237
232
  });