@orderful/droid 0.26.0 → 0.27.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.
- package/.claude-plugin/marketplace.json +7 -7
- package/AGENTS.md +36 -32
- package/CHANGELOG.md +29 -0
- package/dist/bin/droid.js +102 -24
- package/dist/index.js +59 -25
- package/dist/lib/migrations.d.ts +8 -0
- package/dist/lib/migrations.d.ts.map +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/tools.d.ts.map +1 -1
- package/dist/lib/types.d.ts +10 -2
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/brain/.claude-plugin/plugin.json +1 -1
- package/dist/tools/brain/TOOL.yaml +7 -5
- package/dist/tools/brain/commands/brain.md +17 -49
- package/dist/tools/brain/commands/scratchpad.md +13 -50
- package/{src/tools/brain/skills/droid-brain → dist/tools/brain/skills/brain}/SKILL.md +5 -5
- package/dist/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/SKILL.md +8 -8
- package/dist/tools/coach/.claude-plugin/plugin.json +1 -1
- package/dist/tools/coach/TOOL.yaml +4 -3
- package/dist/tools/coach/commands/coach.md +14 -54
- package/{src/tools/coach/skills/droid-coach → dist/tools/coach/skills/coach}/SKILL.md +4 -3
- package/dist/tools/code-review/.claude-plugin/plugin.json +1 -1
- package/dist/tools/code-review/TOOL.yaml +4 -3
- package/dist/tools/code-review/commands/code-review.md +18 -102
- package/dist/tools/code-review/skills/code-review/SKILL.md +154 -0
- package/dist/tools/codex/.claude-plugin/plugin.json +1 -1
- package/dist/tools/codex/TOOL.yaml +4 -3
- package/dist/tools/codex/commands/codex.md +18 -65
- package/dist/tools/codex/skills/{droid-codex → codex}/SKILL.md +64 -45
- package/{src/tools/codex/skills/droid-codex → dist/tools/codex/skills/codex}/references/loading.md +94 -55
- package/dist/tools/codex/skills/codex/scripts/git-finish-write.d.ts.map +1 -0
- package/dist/tools/codex/skills/codex/scripts/git-preamble.d.ts.map +1 -0
- package/dist/tools/codex/skills/codex/scripts/git-start-write.d.ts.map +1 -0
- package/dist/tools/comments/.claude-plugin/plugin.json +1 -1
- package/dist/tools/comments/TOOL.yaml +4 -3
- package/dist/tools/comments/commands/comments.md +12 -14
- package/{src/tools/comments/skills/droid-comments → dist/tools/comments/skills/comments}/SKILL.md +3 -1
- package/dist/tools/project/.claude-plugin/plugin.json +1 -1
- package/dist/tools/project/TOOL.yaml +4 -3
- package/dist/tools/project/commands/project.md +12 -27
- package/dist/tools/project/skills/{droid-project → project}/SKILL.md +12 -11
- package/dist/tools/tech-design/.claude-plugin/plugin.json +1 -1
- package/dist/tools/tech-design/TOOL.yaml +4 -3
- package/dist/tools/tech-design/commands/tech-design.md +18 -80
- package/{src/tools/tech-design/skills/droid-tech-design → dist/tools/tech-design/skills/tech-design}/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/commands/tui/components/Badge.test.tsx +10 -4
- package/src/commands/tui.tsx +4 -4
- package/src/lib/migrations.ts +95 -4
- package/src/lib/skills.test.ts +199 -74
- package/src/lib/skills.ts +55 -54
- package/src/lib/tools.ts +19 -12
- package/src/lib/types.ts +20 -5
- package/src/tools/brain/.claude-plugin/plugin.json +1 -1
- package/src/tools/brain/TOOL.yaml +7 -5
- package/src/tools/brain/commands/brain.md +17 -49
- package/src/tools/brain/commands/scratchpad.md +13 -50
- package/{dist/tools/brain/skills/droid-brain → src/tools/brain/skills/brain}/SKILL.md +5 -5
- package/src/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/SKILL.md +8 -8
- package/src/tools/coach/.claude-plugin/plugin.json +1 -1
- package/src/tools/coach/TOOL.yaml +4 -3
- package/src/tools/coach/commands/coach.md +14 -54
- package/{dist/tools/coach/skills/droid-coach → src/tools/coach/skills/coach}/SKILL.md +4 -3
- package/src/tools/code-review/.claude-plugin/plugin.json +1 -1
- package/src/tools/code-review/TOOL.yaml +4 -3
- package/src/tools/code-review/commands/code-review.md +18 -102
- package/src/tools/code-review/skills/code-review/SKILL.md +154 -0
- package/src/tools/codex/.claude-plugin/plugin.json +1 -1
- package/src/tools/codex/TOOL.yaml +4 -3
- package/src/tools/codex/commands/codex.md +18 -65
- package/src/tools/codex/skills/{droid-codex → codex}/SKILL.md +64 -45
- package/{dist/tools/codex/skills/droid-codex → src/tools/codex/skills/codex}/references/loading.md +94 -55
- package/src/tools/comments/.claude-plugin/plugin.json +1 -1
- package/src/tools/comments/TOOL.yaml +4 -3
- package/src/tools/comments/commands/comments.md +12 -14
- package/{dist/tools/comments/skills/droid-comments → src/tools/comments/skills/comments}/SKILL.md +3 -1
- package/src/tools/project/.claude-plugin/plugin.json +1 -1
- package/src/tools/project/TOOL.yaml +4 -3
- package/src/tools/project/commands/project.md +12 -27
- package/src/tools/project/skills/{droid-project → project}/SKILL.md +12 -11
- package/src/tools/tech-design/.claude-plugin/plugin.json +1 -1
- package/src/tools/tech-design/TOOL.yaml +4 -3
- package/src/tools/tech-design/commands/tech-design.md +18 -80
- package/{dist/tools/tech-design/skills/droid-tech-design → src/tools/tech-design/skills/tech-design}/SKILL.md +1 -1
- package/dist/tools/code-review/skills/droid-code-review/SKILL.md +0 -55
- package/dist/tools/codex/skills/droid-codex/scripts/git-finish-write.d.ts.map +0 -1
- package/dist/tools/codex/skills/droid-codex/scripts/git-preamble.d.ts.map +0 -1
- package/dist/tools/codex/skills/droid-codex/scripts/git-start-write.d.ts.map +0 -1
- package/src/tools/code-review/skills/droid-code-review/SKILL.md +0 -55
- /package/dist/tools/brain/skills/{droid-brain → brain}/references/metadata.md +0 -0
- /package/dist/tools/brain/skills/{droid-brain → brain}/references/naming.md +0 -0
- /package/dist/tools/brain/skills/{droid-brain → brain}/references/templates.md +0 -0
- /package/dist/tools/brain/skills/{droid-brain → brain}/references/workflows.md +0 -0
- /package/dist/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/templates.md +0 -0
- /package/dist/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/workflows.md +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/references/creating.md +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/references/decisions.md +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/references/topics.md +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-finish-write.d.ts +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-finish-write.ts +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-preamble.d.ts +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-preamble.ts +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-scripts.test.ts +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-start-write.d.ts +0 -0
- /package/dist/tools/codex/skills/{droid-codex → codex}/scripts/git-start-write.ts +0 -0
- /package/dist/tools/project/skills/{droid-project → project}/references/changelog.md +0 -0
- /package/dist/tools/project/skills/{droid-project → project}/references/creating.md +0 -0
- /package/dist/tools/project/skills/{droid-project → project}/references/loading.md +0 -0
- /package/dist/tools/project/skills/{droid-project → project}/references/templates.md +0 -0
- /package/dist/tools/project/skills/{droid-project → project}/references/updating.md +0 -0
- /package/dist/tools/project/skills/{droid-project → project}/references/versioning.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/draft.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/gaps.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/publish.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/research-doc-template.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/rollup-template.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/start.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/think.md +0 -0
- /package/dist/tools/tech-design/skills/{droid-tech-design → tech-design}/references/thought-doc-template.md +0 -0
- /package/src/tools/brain/skills/{droid-brain → brain}/references/metadata.md +0 -0
- /package/src/tools/brain/skills/{droid-brain → brain}/references/naming.md +0 -0
- /package/src/tools/brain/skills/{droid-brain → brain}/references/templates.md +0 -0
- /package/src/tools/brain/skills/{droid-brain → brain}/references/workflows.md +0 -0
- /package/src/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/templates.md +0 -0
- /package/src/tools/brain/skills/{droid-brain-obsidian → brain-obsidian}/references/workflows.md +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/references/creating.md +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/references/decisions.md +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/references/topics.md +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-finish-write.ts +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-preamble.ts +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-scripts.test.ts +0 -0
- /package/src/tools/codex/skills/{droid-codex → codex}/scripts/git-start-write.ts +0 -0
- /package/src/tools/project/skills/{droid-project → project}/references/changelog.md +0 -0
- /package/src/tools/project/skills/{droid-project → project}/references/creating.md +0 -0
- /package/src/tools/project/skills/{droid-project → project}/references/loading.md +0 -0
- /package/src/tools/project/skills/{droid-project → project}/references/templates.md +0 -0
- /package/src/tools/project/skills/{droid-project → project}/references/updating.md +0 -0
- /package/src/tools/project/skills/{droid-project → project}/references/versioning.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/draft.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/gaps.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/publish.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/research-doc-template.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/rollup-template.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/start.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/think.md +0 -0
- /package/src/tools/tech-design/skills/{droid-tech-design → tech-design}/references/thought-doc-template.md +0 -0
|
@@ -29,7 +29,9 @@ describe('Badge', () => {
|
|
|
29
29
|
const types = ['skill', 'command', 'agent'] as const;
|
|
30
30
|
for (const type of types) {
|
|
31
31
|
const { lastFrame } = render(<Badge type={type} />);
|
|
32
|
-
expect(lastFrame()).toContain(
|
|
32
|
+
expect(lastFrame()).toContain(
|
|
33
|
+
type.charAt(0).toUpperCase() + type.slice(1),
|
|
34
|
+
);
|
|
33
35
|
}
|
|
34
36
|
});
|
|
35
37
|
});
|
|
@@ -44,7 +46,7 @@ describe('ComponentBadges', () => {
|
|
|
44
46
|
{ name: 'skill1', required: true },
|
|
45
47
|
{ name: 'skill2', required: false },
|
|
46
48
|
],
|
|
47
|
-
commands: ['cmd1'],
|
|
49
|
+
commands: [{ name: 'cmd1', is_alias: false }],
|
|
48
50
|
agents: [],
|
|
49
51
|
},
|
|
50
52
|
};
|
|
@@ -63,7 +65,11 @@ describe('ComponentBadges', () => {
|
|
|
63
65
|
it('renders singular form for single item', () => {
|
|
64
66
|
const singleSkillTool: ToolManifest = {
|
|
65
67
|
...mockTool,
|
|
66
|
-
includes: {
|
|
68
|
+
includes: {
|
|
69
|
+
skills: [{ name: 'skill1', required: true }],
|
|
70
|
+
commands: [],
|
|
71
|
+
agents: [],
|
|
72
|
+
},
|
|
67
73
|
};
|
|
68
74
|
const { lastFrame } = render(<ComponentBadges tool={singleSkillTool} />);
|
|
69
75
|
expect(lastFrame()).toContain('1 skill');
|
|
@@ -82,7 +88,7 @@ describe('ComponentBadges', () => {
|
|
|
82
88
|
...mockTool,
|
|
83
89
|
includes: {
|
|
84
90
|
skills: [{ name: 's1', required: true }],
|
|
85
|
-
commands: ['c1'],
|
|
91
|
+
commands: [{ name: 'c1', is_alias: false }],
|
|
86
92
|
agents: ['a1'],
|
|
87
93
|
},
|
|
88
94
|
};
|
package/src/commands/tui.tsx
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { configExists, loadConfig, getAutoUpdateConfig } from '../lib/config';
|
|
15
15
|
import { Platform, type ConfigOption, type ToolManifest } from '../lib/types';
|
|
16
16
|
import { getVersion } from '../lib/version';
|
|
17
|
-
import {
|
|
17
|
+
import { runPackageMigrations } from '../lib/migrations';
|
|
18
18
|
import { type Tab, type View } from './tui/types';
|
|
19
19
|
import { colors, MAX_VISIBLE_ITEMS } from './tui/constants';
|
|
20
20
|
import { TabBar } from './tui/components/TabBar';
|
|
@@ -72,10 +72,10 @@ function App() {
|
|
|
72
72
|
},
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
// Run droid
|
|
75
|
+
// Run droid package migrations on startup (sync installed tools across platforms)
|
|
76
76
|
useEffect(() => {
|
|
77
|
-
const
|
|
78
|
-
|
|
77
|
+
const packageVersion = getVersion();
|
|
78
|
+
runPackageMigrations(packageVersion);
|
|
79
79
|
}, []);
|
|
80
80
|
|
|
81
81
|
// Auto-update app if enabled and update available
|
package/src/lib/migrations.ts
CHANGED
|
@@ -153,6 +153,14 @@ function createPlatformSyncMigration(version: string): Migration {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Registry of package-level migrations
|
|
158
|
+
* These run when the @orderful/droid npm package updates
|
|
159
|
+
* Triggered by package version from package.json (e.g., 0.26.0)
|
|
160
|
+
* Run at TUI startup
|
|
161
|
+
*/
|
|
162
|
+
const PACKAGE_MIGRATIONS: Migration[] = [createPlatformSyncMigration('0.25.0')];
|
|
163
|
+
|
|
156
164
|
/**
|
|
157
165
|
* Registry of all tool migrations
|
|
158
166
|
* Key: tool name (e.g., "brain", "comments")
|
|
@@ -169,8 +177,6 @@ const TOOL_MIGRATIONS: Record<string, Migration[]> = {
|
|
|
169
177
|
comments: [createConfigDirMigration('droid-comments', '0.2.6')],
|
|
170
178
|
project: [createConfigDirMigration('droid-project', '0.1.5')],
|
|
171
179
|
coach: [createConfigDirMigration('droid-coach', '0.1.3')],
|
|
172
|
-
// Global migration for the droid meta-tool
|
|
173
|
-
droid: [createPlatformSyncMigration('0.25.0')],
|
|
174
180
|
};
|
|
175
181
|
|
|
176
182
|
/**
|
|
@@ -185,7 +191,18 @@ export function getToolMigrations(toolName: string): Migration[] {
|
|
|
185
191
|
*/
|
|
186
192
|
export function getLastMigratedVersion(toolName: string): string {
|
|
187
193
|
const config = loadConfig();
|
|
188
|
-
|
|
194
|
+
|
|
195
|
+
// Check new location first
|
|
196
|
+
if (config.migrations?.tools?.[toolName]) {
|
|
197
|
+
return config.migrations.tools[toolName];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Fall back to old location (backward compatibility)
|
|
201
|
+
if (config.migrations && typeof config.migrations[toolName] === 'string') {
|
|
202
|
+
return config.migrations[toolName] as string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return '0.0.0';
|
|
189
206
|
}
|
|
190
207
|
|
|
191
208
|
/**
|
|
@@ -196,10 +213,22 @@ export function setLastMigratedVersion(
|
|
|
196
213
|
version: string,
|
|
197
214
|
): void {
|
|
198
215
|
const config = loadConfig();
|
|
216
|
+
|
|
217
|
+
// Ensure new structure exists
|
|
199
218
|
if (!config.migrations) {
|
|
200
219
|
config.migrations = {};
|
|
201
220
|
}
|
|
202
|
-
config.migrations
|
|
221
|
+
if (!config.migrations.tools) {
|
|
222
|
+
config.migrations.tools = {};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
config.migrations.tools[toolName] = version;
|
|
226
|
+
|
|
227
|
+
// Clean up old location if it exists
|
|
228
|
+
if (typeof config.migrations[toolName] === 'string') {
|
|
229
|
+
delete config.migrations[toolName];
|
|
230
|
+
}
|
|
231
|
+
|
|
203
232
|
saveConfig(config);
|
|
204
233
|
}
|
|
205
234
|
|
|
@@ -273,3 +302,65 @@ export function runToolMigrations(
|
|
|
273
302
|
|
|
274
303
|
return runMigrations(toolName, lastMigrated, installedVersion);
|
|
275
304
|
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Run package-level migrations
|
|
308
|
+
* Call this on TUI startup with package version from package.json
|
|
309
|
+
*/
|
|
310
|
+
export function runPackageMigrations(packageVersion: string): {
|
|
311
|
+
success: boolean;
|
|
312
|
+
error?: string;
|
|
313
|
+
} {
|
|
314
|
+
const config = loadConfig();
|
|
315
|
+
const lastMigrated = config.migrations?.package ?? '0.0.0';
|
|
316
|
+
|
|
317
|
+
// Only run if the package version is newer than last migrated
|
|
318
|
+
if (compareSemver(packageVersion, lastMigrated) <= 0) {
|
|
319
|
+
return { success: true };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Filter migrations that need to run
|
|
323
|
+
const pendingMigrations = PACKAGE_MIGRATIONS.filter((m) => {
|
|
324
|
+
const afterFrom = compareSemver(m.version, lastMigrated) > 0;
|
|
325
|
+
const beforeOrAtTo = compareSemver(m.version, packageVersion) <= 0;
|
|
326
|
+
return afterFrom && beforeOrAtTo;
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (pendingMigrations.length === 0) {
|
|
330
|
+
// No migrations to run, but still update the version marker
|
|
331
|
+
config.migrations = config.migrations || {};
|
|
332
|
+
config.migrations.package = packageVersion;
|
|
333
|
+
saveConfig(config);
|
|
334
|
+
return { success: true };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const configDir = getConfigDir();
|
|
338
|
+
|
|
339
|
+
for (const migration of pendingMigrations) {
|
|
340
|
+
try {
|
|
341
|
+
migration.up(configDir);
|
|
342
|
+
logMigration('package', lastMigrated, migration.version, 'OK');
|
|
343
|
+
} catch (error) {
|
|
344
|
+
const errorMessage =
|
|
345
|
+
error instanceof Error ? error.message : String(error);
|
|
346
|
+
logMigration(
|
|
347
|
+
'package',
|
|
348
|
+
lastMigrated,
|
|
349
|
+
migration.version,
|
|
350
|
+
'FAILED',
|
|
351
|
+
errorMessage,
|
|
352
|
+
);
|
|
353
|
+
// Don't update version marker on failure - will retry next time
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
error: `Package migration ${migration.version} failed: ${errorMessage}`,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// All migrations succeeded, update version marker
|
|
362
|
+
config.migrations = config.migrations || {};
|
|
363
|
+
config.migrations.package = packageVersion;
|
|
364
|
+
saveConfig(config);
|
|
365
|
+
return { success: true };
|
|
366
|
+
}
|
package/src/lib/skills.test.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
} from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { tmpdir } from "os";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import YAML from "yaml";
|
|
14
|
+
import { Platform, SkillStatus } from "./types";
|
|
8
15
|
import {
|
|
9
16
|
getSkillsInstallPath,
|
|
10
17
|
getCommandsInstallPath,
|
|
@@ -12,7 +19,9 @@ import {
|
|
|
12
19
|
getSkillStatusDisplay,
|
|
13
20
|
getBundledSkillsDir,
|
|
14
21
|
loadSkillManifest,
|
|
15
|
-
|
|
22
|
+
installSkill,
|
|
23
|
+
} from "./skills";
|
|
24
|
+
import { loadConfig, saveConfig } from "./config";
|
|
16
25
|
|
|
17
26
|
/**
|
|
18
27
|
* Parse YAML frontmatter from markdown content
|
|
@@ -27,73 +36,73 @@ function parseFrontmatter(content: string): Record<string, unknown> | null {
|
|
|
27
36
|
}
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
describe(
|
|
31
|
-
it(
|
|
39
|
+
describe("getSkillsInstallPath", () => {
|
|
40
|
+
it("should return Claude Code path", () => {
|
|
32
41
|
const path = getSkillsInstallPath(Platform.ClaudeCode);
|
|
33
|
-
expect(path).toBe(join(homedir(),
|
|
42
|
+
expect(path).toBe(join(homedir(), ".claude", "skills"));
|
|
34
43
|
});
|
|
35
44
|
|
|
36
|
-
it(
|
|
45
|
+
it("should return OpenCode path", () => {
|
|
37
46
|
const path = getSkillsInstallPath(Platform.OpenCode);
|
|
38
|
-
expect(path).toBe(join(homedir(),
|
|
47
|
+
expect(path).toBe(join(homedir(), ".config", "opencode", "skills"));
|
|
39
48
|
});
|
|
40
49
|
});
|
|
41
50
|
|
|
42
|
-
describe(
|
|
43
|
-
it(
|
|
51
|
+
describe("getCommandsInstallPath", () => {
|
|
52
|
+
it("should return Claude Code commands path", () => {
|
|
44
53
|
const path = getCommandsInstallPath(Platform.ClaudeCode);
|
|
45
|
-
expect(path).toBe(join(homedir(),
|
|
54
|
+
expect(path).toBe(join(homedir(), ".claude", "commands"));
|
|
46
55
|
});
|
|
47
56
|
|
|
48
|
-
it(
|
|
57
|
+
it("should return OpenCode commands path", () => {
|
|
49
58
|
const path = getCommandsInstallPath(Platform.OpenCode);
|
|
50
|
-
expect(path).toBe(join(homedir(),
|
|
59
|
+
expect(path).toBe(join(homedir(), ".config", "opencode", "command"));
|
|
51
60
|
});
|
|
52
61
|
});
|
|
53
62
|
|
|
54
|
-
describe(
|
|
55
|
-
it(
|
|
63
|
+
describe("getPlatformConfigPath", () => {
|
|
64
|
+
it("should return Claude Code CLAUDE.md path", () => {
|
|
56
65
|
const path = getPlatformConfigPath(Platform.ClaudeCode);
|
|
57
|
-
expect(path).toBe(join(homedir(),
|
|
66
|
+
expect(path).toBe(join(homedir(), ".claude", "CLAUDE.md"));
|
|
58
67
|
});
|
|
59
68
|
|
|
60
|
-
it(
|
|
69
|
+
it("should return OpenCode AGENTS.md path", () => {
|
|
61
70
|
const path = getPlatformConfigPath(Platform.OpenCode);
|
|
62
|
-
expect(path).toBe(join(homedir(),
|
|
71
|
+
expect(path).toBe(join(homedir(), ".config", "opencode", "AGENTS.md"));
|
|
63
72
|
});
|
|
64
73
|
});
|
|
65
74
|
|
|
66
|
-
describe(
|
|
67
|
-
it(
|
|
75
|
+
describe("getBundledSkillsDir", () => {
|
|
76
|
+
it("should return a path ending in tools", () => {
|
|
68
77
|
const dir = getBundledSkillsDir();
|
|
69
|
-
expect(dir.endsWith(
|
|
78
|
+
expect(dir.endsWith("tools")).toBe(true);
|
|
70
79
|
});
|
|
71
80
|
|
|
72
|
-
it(
|
|
81
|
+
it("should return an existing directory", () => {
|
|
73
82
|
const dir = getBundledSkillsDir();
|
|
74
83
|
expect(existsSync(dir)).toBe(true);
|
|
75
84
|
});
|
|
76
85
|
});
|
|
77
86
|
|
|
78
|
-
describe(
|
|
79
|
-
it(
|
|
80
|
-
expect(getSkillStatusDisplay(SkillStatus.Alpha)).toBe(
|
|
87
|
+
describe("getSkillStatusDisplay", () => {
|
|
88
|
+
it("should return [alpha] for alpha status", () => {
|
|
89
|
+
expect(getSkillStatusDisplay(SkillStatus.Alpha)).toBe("[alpha]");
|
|
81
90
|
});
|
|
82
91
|
|
|
83
|
-
it(
|
|
84
|
-
expect(getSkillStatusDisplay(SkillStatus.Beta)).toBe(
|
|
92
|
+
it("should return [beta] for beta status", () => {
|
|
93
|
+
expect(getSkillStatusDisplay(SkillStatus.Beta)).toBe("[beta]");
|
|
85
94
|
});
|
|
86
95
|
|
|
87
|
-
it(
|
|
88
|
-
expect(getSkillStatusDisplay(SkillStatus.Stable)).toBe(
|
|
96
|
+
it("should return empty string for stable status", () => {
|
|
97
|
+
expect(getSkillStatusDisplay(SkillStatus.Stable)).toBe("");
|
|
89
98
|
});
|
|
90
99
|
|
|
91
|
-
it(
|
|
92
|
-
expect(getSkillStatusDisplay(undefined)).toBe(
|
|
100
|
+
it("should return empty string for undefined status", () => {
|
|
101
|
+
expect(getSkillStatusDisplay(undefined)).toBe("");
|
|
93
102
|
});
|
|
94
103
|
});
|
|
95
104
|
|
|
96
|
-
describe(
|
|
105
|
+
describe("skill manifest parsing", () => {
|
|
97
106
|
let testDir: string;
|
|
98
107
|
|
|
99
108
|
beforeEach(() => {
|
|
@@ -107,44 +116,44 @@ describe('skill manifest parsing', () => {
|
|
|
107
116
|
}
|
|
108
117
|
});
|
|
109
118
|
|
|
110
|
-
it(
|
|
119
|
+
it("should parse valid skill manifest", () => {
|
|
111
120
|
const manifest = {
|
|
112
|
-
name:
|
|
113
|
-
description:
|
|
114
|
-
version:
|
|
115
|
-
status:
|
|
116
|
-
dependencies: [
|
|
121
|
+
name: "test-skill",
|
|
122
|
+
description: "A test skill",
|
|
123
|
+
version: "1.0.0",
|
|
124
|
+
status: "beta",
|
|
125
|
+
dependencies: ["comments"],
|
|
117
126
|
provides_output: true,
|
|
118
127
|
};
|
|
119
128
|
|
|
120
|
-
const manifestPath = join(testDir,
|
|
121
|
-
writeFileSync(manifestPath, YAML.stringify(manifest),
|
|
129
|
+
const manifestPath = join(testDir, "SKILL.yaml");
|
|
130
|
+
writeFileSync(manifestPath, YAML.stringify(manifest), "utf-8");
|
|
122
131
|
|
|
123
|
-
const content = require(
|
|
132
|
+
const content = require("fs").readFileSync(manifestPath, "utf-8");
|
|
124
133
|
const parsed = YAML.parse(content);
|
|
125
134
|
|
|
126
|
-
expect(parsed.name).toBe(
|
|
127
|
-
expect(parsed.description).toBe(
|
|
128
|
-
expect(parsed.version).toBe(
|
|
129
|
-
expect(parsed.status).toBe(
|
|
130
|
-
expect(parsed.dependencies).toEqual([
|
|
135
|
+
expect(parsed.name).toBe("test-skill");
|
|
136
|
+
expect(parsed.description).toBe("A test skill");
|
|
137
|
+
expect(parsed.version).toBe("1.0.0");
|
|
138
|
+
expect(parsed.status).toBe("beta");
|
|
139
|
+
expect(parsed.dependencies).toEqual(["comments"]);
|
|
131
140
|
expect(parsed.provides_output).toBe(true);
|
|
132
141
|
});
|
|
133
142
|
|
|
134
|
-
it(
|
|
143
|
+
it("should handle manifest without optional fields", () => {
|
|
135
144
|
const manifest = {
|
|
136
|
-
name:
|
|
137
|
-
description:
|
|
138
|
-
version:
|
|
145
|
+
name: "minimal-skill",
|
|
146
|
+
description: "Minimal",
|
|
147
|
+
version: "0.1.0",
|
|
139
148
|
};
|
|
140
149
|
|
|
141
|
-
const manifestPath = join(testDir,
|
|
142
|
-
writeFileSync(manifestPath, YAML.stringify(manifest),
|
|
150
|
+
const manifestPath = join(testDir, "SKILL.yaml");
|
|
151
|
+
writeFileSync(manifestPath, YAML.stringify(manifest), "utf-8");
|
|
143
152
|
|
|
144
|
-
const content = require(
|
|
153
|
+
const content = require("fs").readFileSync(manifestPath, "utf-8");
|
|
145
154
|
const parsed = YAML.parse(content);
|
|
146
155
|
|
|
147
|
-
expect(parsed.name).toBe(
|
|
156
|
+
expect(parsed.name).toBe("minimal-skill");
|
|
148
157
|
expect(parsed.status).toBeUndefined();
|
|
149
158
|
expect(parsed.dependencies).toBeUndefined();
|
|
150
159
|
expect(parsed.provides_output).toBeUndefined();
|
|
@@ -154,7 +163,9 @@ describe('skill manifest parsing', () => {
|
|
|
154
163
|
/**
|
|
155
164
|
* Helper to get all skill directories in the new tools/{tool}/skills/{skill} structure
|
|
156
165
|
*/
|
|
157
|
-
function getAllSkillPaths(
|
|
166
|
+
function getAllSkillPaths(
|
|
167
|
+
toolsDir: string,
|
|
168
|
+
): Array<{ skillName: string; skillDir: string }> {
|
|
158
169
|
const skillPaths: Array<{ skillName: string; skillDir: string }> = [];
|
|
159
170
|
|
|
160
171
|
const toolDirs = readdirSync(toolsDir, { withFileTypes: true })
|
|
@@ -162,7 +173,7 @@ function getAllSkillPaths(toolsDir: string): Array<{ skillName: string; skillDir
|
|
|
162
173
|
.map((d) => d.name);
|
|
163
174
|
|
|
164
175
|
for (const toolName of toolDirs) {
|
|
165
|
-
const skillsDir = join(toolsDir, toolName,
|
|
176
|
+
const skillsDir = join(toolsDir, toolName, "skills");
|
|
166
177
|
if (!existsSync(skillsDir)) continue;
|
|
167
178
|
|
|
168
179
|
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
@@ -180,43 +191,43 @@ function getAllSkillPaths(toolsDir: string): Array<{ skillName: string; skillDir
|
|
|
180
191
|
return skillPaths;
|
|
181
192
|
}
|
|
182
193
|
|
|
183
|
-
describe(
|
|
184
|
-
it(
|
|
194
|
+
describe("bundled skills validation", () => {
|
|
195
|
+
it("all skills should have SKILL.md", () => {
|
|
185
196
|
const toolsDir = getBundledSkillsDir();
|
|
186
197
|
const skillPaths = getAllSkillPaths(toolsDir);
|
|
187
198
|
|
|
188
199
|
expect(skillPaths.length).toBeGreaterThan(0);
|
|
189
200
|
|
|
190
201
|
for (const { skillDir } of skillPaths) {
|
|
191
|
-
const skillMdPath = join(skillDir,
|
|
202
|
+
const skillMdPath = join(skillDir, "SKILL.md");
|
|
192
203
|
expect(existsSync(skillMdPath)).toBe(true);
|
|
193
204
|
}
|
|
194
205
|
});
|
|
195
206
|
|
|
196
|
-
it(
|
|
207
|
+
it("all skills should have metadata in TOOL.yaml", () => {
|
|
197
208
|
const toolsDir = getBundledSkillsDir();
|
|
198
209
|
const skillPaths = getAllSkillPaths(toolsDir);
|
|
199
210
|
|
|
200
211
|
for (const { skillName, skillDir } of skillPaths) {
|
|
201
212
|
// Get the parent tool directory
|
|
202
|
-
const toolDir = join(skillDir,
|
|
203
|
-
const toolYamlPath = join(toolDir,
|
|
213
|
+
const toolDir = join(skillDir, "..", "..");
|
|
214
|
+
const toolYamlPath = join(toolDir, "TOOL.yaml");
|
|
204
215
|
expect(existsSync(toolYamlPath)).toBe(true);
|
|
205
216
|
|
|
206
|
-
const toolContent = readFileSync(toolYamlPath,
|
|
217
|
+
const toolContent = readFileSync(toolYamlPath, "utf-8");
|
|
207
218
|
const toolManifest = YAML.parse(toolContent);
|
|
208
219
|
|
|
209
220
|
// Find the skill in includes.skills
|
|
210
221
|
const skillInclude = toolManifest.includes?.skills?.find(
|
|
211
|
-
(s: { name: string }) => s.name === skillName
|
|
222
|
+
(s: { name: string }) => s.name === skillName,
|
|
212
223
|
);
|
|
213
224
|
expect(skillInclude).toBeDefined();
|
|
214
|
-
expect(typeof toolManifest.description).toBe(
|
|
215
|
-
expect(typeof toolManifest.version).toBe(
|
|
225
|
+
expect(typeof toolManifest.description).toBe("string");
|
|
226
|
+
expect(typeof toolManifest.version).toBe("string");
|
|
216
227
|
}
|
|
217
228
|
});
|
|
218
229
|
|
|
219
|
-
it(
|
|
230
|
+
it("loadSkillManifest should return valid manifest from TOOL.yaml", () => {
|
|
220
231
|
const toolsDir = getBundledSkillsDir();
|
|
221
232
|
const skillPaths = getAllSkillPaths(toolsDir);
|
|
222
233
|
|
|
@@ -225,8 +236,122 @@ describe('bundled skills validation', () => {
|
|
|
225
236
|
|
|
226
237
|
expect(manifest).not.toBeNull();
|
|
227
238
|
expect(manifest?.name).toBe(skillName);
|
|
228
|
-
expect(typeof manifest?.description).toBe(
|
|
229
|
-
expect(typeof manifest?.version).toBe(
|
|
239
|
+
expect(typeof manifest?.description).toBe("string");
|
|
240
|
+
expect(typeof manifest?.version).toBe("string");
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe('platform-specific command installation', () => {
|
|
245
|
+
let testConfigDir: string;
|
|
246
|
+
let testSkillsDir: string;
|
|
247
|
+
let testCommandsDir: string;
|
|
248
|
+
let originalConfig: any;
|
|
249
|
+
|
|
250
|
+
beforeEach(() => {
|
|
251
|
+
// Save original config
|
|
252
|
+
originalConfig = loadConfig();
|
|
253
|
+
|
|
254
|
+
// Create temporary directories for testing
|
|
255
|
+
testConfigDir = join(tmpdir(), `droid-test-config-${Date.now()}`);
|
|
256
|
+
testSkillsDir = join(tmpdir(), `droid-test-skills-${Date.now()}`);
|
|
257
|
+
testCommandsDir = join(tmpdir(), `droid-test-commands-${Date.now()}`);
|
|
258
|
+
|
|
259
|
+
mkdirSync(testConfigDir, { recursive: true });
|
|
260
|
+
mkdirSync(testSkillsDir, { recursive: true });
|
|
261
|
+
mkdirSync(testCommandsDir, { recursive: true });
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
afterEach(() => {
|
|
265
|
+
// Restore original config
|
|
266
|
+
saveConfig(originalConfig);
|
|
267
|
+
|
|
268
|
+
// Cleanup test directories
|
|
269
|
+
if (existsSync(testConfigDir)) {
|
|
270
|
+
rmSync(testConfigDir, { recursive: true });
|
|
271
|
+
}
|
|
272
|
+
if (existsSync(testSkillsDir)) {
|
|
273
|
+
rmSync(testSkillsDir, { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
if (existsSync(testCommandsDir)) {
|
|
276
|
+
rmSync(testCommandsDir, { recursive: true });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should install all commands on OpenCode platform', () => {
|
|
281
|
+
// This is an integration test that would require mocking the install paths
|
|
282
|
+
// For now, we verify the logic exists by checking tool manifests
|
|
283
|
+
const toolsDir = getBundledSkillsDir();
|
|
284
|
+
|
|
285
|
+
// Find brain tool which has both primary and alias commands
|
|
286
|
+
const brainToolDir = join(toolsDir, 'brain');
|
|
287
|
+
const toolYamlPath = join(brainToolDir, 'TOOL.yaml');
|
|
288
|
+
|
|
289
|
+
if (!existsSync(toolYamlPath)) {
|
|
290
|
+
// Skip test if brain tool doesn't exist
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const toolContent = readFileSync(toolYamlPath, 'utf-8');
|
|
295
|
+
const toolManifest = YAML.parse(toolContent);
|
|
296
|
+
|
|
297
|
+
// Verify brain has both primary and alias commands
|
|
298
|
+
const commands = toolManifest.includes?.commands || [];
|
|
299
|
+
const brainCommand = commands.find((c: any) => c.name === 'brain');
|
|
300
|
+
const scratchpadCommand = commands.find((c: any) => c.name === 'scratchpad');
|
|
301
|
+
|
|
302
|
+
expect(brainCommand).toBeDefined();
|
|
303
|
+
expect(brainCommand.is_alias).toBe(false);
|
|
304
|
+
|
|
305
|
+
expect(scratchpadCommand).toBeDefined();
|
|
306
|
+
expect(scratchpadCommand.is_alias).toBe(true);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should identify alias commands correctly', () => {
|
|
310
|
+
const toolsDir = getBundledSkillsDir();
|
|
311
|
+
const brainToolDir = join(toolsDir, 'brain');
|
|
312
|
+
const toolYamlPath = join(brainToolDir, 'TOOL.yaml');
|
|
313
|
+
|
|
314
|
+
if (!existsSync(toolYamlPath)) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const toolContent = readFileSync(toolYamlPath, 'utf-8');
|
|
319
|
+
const toolManifest = YAML.parse(toolContent);
|
|
320
|
+
const commands = toolManifest.includes?.commands || [];
|
|
321
|
+
|
|
322
|
+
// All alias commands should have is_alias: true
|
|
323
|
+
const aliasCommands = commands.filter((c: any) => c.is_alias === true);
|
|
324
|
+
const primaryCommands = commands.filter((c: any) => c.is_alias === false);
|
|
325
|
+
|
|
326
|
+
// Should have at least one of each type in brain tool
|
|
327
|
+
expect(aliasCommands.length).toBeGreaterThan(0);
|
|
328
|
+
expect(primaryCommands.length).toBeGreaterThan(0);
|
|
329
|
+
|
|
330
|
+
// Verify scratchpad is marked as alias
|
|
331
|
+
const scratchpad = commands.find((c: any) => c.name === 'scratchpad');
|
|
332
|
+
if (scratchpad) {
|
|
333
|
+
expect(scratchpad.is_alias).toBe(true);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should have is_alias property on all command objects', () => {
|
|
338
|
+
const toolsDir = getBundledSkillsDir();
|
|
339
|
+
const toolDirs = readdirSync(toolsDir, { withFileTypes: true })
|
|
340
|
+
.filter(d => d.isDirectory())
|
|
341
|
+
.map(d => join(toolsDir, d.name));
|
|
342
|
+
|
|
343
|
+
for (const toolDir of toolDirs) {
|
|
344
|
+
const toolYamlPath = join(toolDir, 'TOOL.yaml');
|
|
345
|
+
if (!existsSync(toolYamlPath)) continue;
|
|
346
|
+
|
|
347
|
+
const toolContent = readFileSync(toolYamlPath, 'utf-8');
|
|
348
|
+
const toolManifest = YAML.parse(toolContent);
|
|
349
|
+
const commands = toolManifest.includes?.commands || [];
|
|
350
|
+
|
|
351
|
+
// All commands should have is_alias property (boolean)
|
|
352
|
+
for (const cmd of commands) {
|
|
353
|
+
expect(typeof cmd.is_alias).toBe('boolean');
|
|
354
|
+
}
|
|
230
355
|
}
|
|
231
356
|
});
|
|
232
357
|
});
|