@orderful/droid 0.2.0 → 0.3.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/.github/workflows/changeset-check.yml +43 -0
- package/.github/workflows/release.yml +6 -3
- package/CHANGELOG.md +20 -0
- package/bun.lock +357 -14
- package/dist/bin/droid.js +12 -1
- package/dist/bin/droid.js.map +1 -1
- package/dist/commands/setup.d.ts +8 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +67 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/tui.d.ts +2 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +480 -0
- package/dist/commands/tui.js.map +1 -0
- package/dist/lib/types.d.ts +5 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/version.d.ts +5 -0
- package/dist/lib/version.d.ts.map +1 -1
- package/dist/lib/version.js +19 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/skills/comments/SKILL.md +8 -0
- package/dist/skills/comments/SKILL.yaml +32 -0
- package/package.json +14 -1
- package/src/bin/droid.ts +12 -1
- package/src/commands/setup.ts +77 -0
- package/src/commands/tui.tsx +1102 -0
- package/src/lib/skills.test.ts +75 -1
- package/src/lib/types.ts +7 -0
- package/src/lib/version.test.ts +20 -1
- package/src/lib/version.ts +19 -1
- package/src/skills/comments/SKILL.md +8 -0
- package/src/skills/comments/SKILL.yaml +32 -0
- package/tsconfig.json +5 -3
package/src/lib/skills.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
import { tmpdir } from 'os';
|
|
5
5
|
import { homedir } from 'os';
|
|
@@ -13,6 +13,19 @@ import {
|
|
|
13
13
|
getBundledSkillsDir,
|
|
14
14
|
} from './skills.js';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Parse YAML frontmatter from markdown content
|
|
18
|
+
*/
|
|
19
|
+
function parseFrontmatter(content: string): Record<string, unknown> | null {
|
|
20
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
21
|
+
if (!match) return null;
|
|
22
|
+
try {
|
|
23
|
+
return YAML.parse(match[1]);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
describe('getSkillsInstallPath', () => {
|
|
17
30
|
it('should return Claude Code path', () => {
|
|
18
31
|
const path = getSkillsInstallPath(AITool.ClaudeCode);
|
|
@@ -136,3 +149,64 @@ describe('skill manifest parsing', () => {
|
|
|
136
149
|
expect(parsed.provides_output).toBeUndefined();
|
|
137
150
|
});
|
|
138
151
|
});
|
|
152
|
+
|
|
153
|
+
describe('bundled skills validation', () => {
|
|
154
|
+
it('all skills should have SKILL.md with valid frontmatter', () => {
|
|
155
|
+
const skillsDir = getBundledSkillsDir();
|
|
156
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
157
|
+
.filter((d) => d.isDirectory())
|
|
158
|
+
.map((d) => d.name);
|
|
159
|
+
|
|
160
|
+
for (const skillName of skillDirs) {
|
|
161
|
+
const skillMdPath = join(skillsDir, skillName, 'SKILL.md');
|
|
162
|
+
expect(existsSync(skillMdPath)).toBe(true);
|
|
163
|
+
|
|
164
|
+
const content = readFileSync(skillMdPath, 'utf-8');
|
|
165
|
+
const frontmatter = parseFrontmatter(content);
|
|
166
|
+
|
|
167
|
+
expect(frontmatter).not.toBeNull();
|
|
168
|
+
expect(frontmatter?.name).toBe(skillName);
|
|
169
|
+
expect(typeof frontmatter?.description).toBe('string');
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('all skills should have SKILL.yaml manifest', () => {
|
|
174
|
+
const skillsDir = getBundledSkillsDir();
|
|
175
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
176
|
+
.filter((d) => d.isDirectory())
|
|
177
|
+
.map((d) => d.name);
|
|
178
|
+
|
|
179
|
+
for (const skillName of skillDirs) {
|
|
180
|
+
const yamlPath = join(skillsDir, skillName, 'SKILL.yaml');
|
|
181
|
+
expect(existsSync(yamlPath)).toBe(true);
|
|
182
|
+
|
|
183
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
184
|
+
const manifest = YAML.parse(content);
|
|
185
|
+
|
|
186
|
+
expect(manifest.name).toBe(skillName);
|
|
187
|
+
expect(typeof manifest.description).toBe('string');
|
|
188
|
+
expect(typeof manifest.version).toBe('string');
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('SKILL.md frontmatter should match SKILL.yaml', () => {
|
|
193
|
+
const skillsDir = getBundledSkillsDir();
|
|
194
|
+
const skillDirs = readdirSync(skillsDir, { withFileTypes: true })
|
|
195
|
+
.filter((d) => d.isDirectory())
|
|
196
|
+
.map((d) => d.name);
|
|
197
|
+
|
|
198
|
+
for (const skillName of skillDirs) {
|
|
199
|
+
const mdPath = join(skillsDir, skillName, 'SKILL.md');
|
|
200
|
+
const yamlPath = join(skillsDir, skillName, 'SKILL.yaml');
|
|
201
|
+
|
|
202
|
+
const mdContent = readFileSync(mdPath, 'utf-8');
|
|
203
|
+
const yamlContent = readFileSync(yamlPath, 'utf-8');
|
|
204
|
+
|
|
205
|
+
const frontmatter = parseFrontmatter(mdContent);
|
|
206
|
+
const manifest = YAML.parse(yamlContent);
|
|
207
|
+
|
|
208
|
+
expect(frontmatter?.name).toBe(manifest.name);
|
|
209
|
+
expect(frontmatter?.description).toBe(manifest.description);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
package/src/lib/types.ts
CHANGED
|
@@ -45,6 +45,11 @@ export interface InstalledSkill {
|
|
|
45
45
|
installed_at: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
export interface SkillExample {
|
|
49
|
+
title: string;
|
|
50
|
+
code: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
export interface SkillManifest {
|
|
49
54
|
name: string;
|
|
50
55
|
description: string;
|
|
@@ -54,6 +59,8 @@ export interface SkillManifest {
|
|
|
54
59
|
config_schema?: Record<string, ConfigOption>;
|
|
55
60
|
// Skills can declare they provide an output target
|
|
56
61
|
provides_output?: boolean;
|
|
62
|
+
// Usage examples to show in the TUI
|
|
63
|
+
examples?: SkillExample[];
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
export interface ConfigOption {
|
package/src/lib/version.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'bun:test';
|
|
2
|
-
import { getVersion } from './version.js';
|
|
2
|
+
import { getVersion, compareSemver } from './version.js';
|
|
3
3
|
|
|
4
4
|
describe('getVersion', () => {
|
|
5
5
|
it('should return a version string', () => {
|
|
@@ -21,3 +21,22 @@ describe('getVersion', () => {
|
|
|
21
21
|
expect(version).not.toBe('0.0.0');
|
|
22
22
|
});
|
|
23
23
|
});
|
|
24
|
+
|
|
25
|
+
describe('compareSemver', () => {
|
|
26
|
+
it('should return 1 when first version is greater', () => {
|
|
27
|
+
expect(compareSemver('1.0.0', '0.9.9')).toBe(1);
|
|
28
|
+
expect(compareSemver('0.2.1', '0.2.0')).toBe(1);
|
|
29
|
+
expect(compareSemver('2.0.0', '1.9.9')).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return -1 when first version is lesser', () => {
|
|
33
|
+
expect(compareSemver('0.9.9', '1.0.0')).toBe(-1);
|
|
34
|
+
expect(compareSemver('0.2.0', '0.2.1')).toBe(-1);
|
|
35
|
+
expect(compareSemver('1.9.9', '2.0.0')).toBe(-1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should return 0 when versions are equal', () => {
|
|
39
|
+
expect(compareSemver('1.0.0', '1.0.0')).toBe(0);
|
|
40
|
+
expect(compareSemver('0.2.1', '0.2.1')).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
});
|
package/src/lib/version.ts
CHANGED
|
@@ -19,6 +19,23 @@ export function getVersion(): string {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Compare two semver versions
|
|
24
|
+
* Returns: 1 if a > b, -1 if a < b, 0 if equal
|
|
25
|
+
*/
|
|
26
|
+
export function compareSemver(a: string, b: string): number {
|
|
27
|
+
const partsA = a.split('.').map(Number);
|
|
28
|
+
const partsB = b.split('.').map(Number);
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < 3; i++) {
|
|
31
|
+
const numA = partsA[i] || 0;
|
|
32
|
+
const numB = partsB[i] || 0;
|
|
33
|
+
if (numA > numB) return 1;
|
|
34
|
+
if (numA < numB) return -1;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
/**
|
|
23
40
|
* Check for updates (non-blocking)
|
|
24
41
|
* Shows a message if a new version is available
|
|
@@ -33,7 +50,8 @@ export async function checkForUpdates(): Promise<void> {
|
|
|
33
50
|
timeout: 3000,
|
|
34
51
|
}).trim();
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
// Only show update message if latest is actually newer
|
|
54
|
+
if (latestVersion && compareSemver(latestVersion, currentVersion) > 0) {
|
|
37
55
|
console.log(
|
|
38
56
|
chalk.yellow(
|
|
39
57
|
`\n⚠️ A new version of droid is available (${currentVersion} → ${latestVersion})`
|
|
@@ -16,3 +16,35 @@ config_schema:
|
|
|
16
16
|
type: boolean
|
|
17
17
|
description: Keep original comments after addressing (vs removing them)
|
|
18
18
|
default: false
|
|
19
|
+
examples:
|
|
20
|
+
- title: "Action request"
|
|
21
|
+
code: |
|
|
22
|
+
// @droid use PascalCase for enum keys
|
|
23
|
+
enum Status {
|
|
24
|
+
PENDING = 'pending'
|
|
25
|
+
}
|
|
26
|
+
- title: "Ask a question"
|
|
27
|
+
code: |
|
|
28
|
+
> @droid Should we cache this?
|
|
29
|
+
|
|
30
|
+
> @fry Yes, add Redis caching here...
|
|
31
|
+
- title: "TODO with context"
|
|
32
|
+
code: |
|
|
33
|
+
// @droid TODO: refactor to async/await
|
|
34
|
+
// The callback pattern is unwieldy
|
|
35
|
+
function fetchData(callback) {
|
|
36
|
+
api.get('/data', callback);
|
|
37
|
+
}
|
|
38
|
+
- title: "Code review note"
|
|
39
|
+
code: |
|
|
40
|
+
// @droid potential memory leak here
|
|
41
|
+
// Clear interval on unmount?
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setInterval(poll, 1000);
|
|
44
|
+
}, []);
|
|
45
|
+
- title: "Multi-line discussion"
|
|
46
|
+
code: |
|
|
47
|
+
> @droid Best approach for pagination?
|
|
48
|
+
> We have ~10k records.
|
|
49
|
+
|
|
50
|
+
> @fry Use cursor-based, limit 50.
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2022",
|
|
4
|
-
"module": "
|
|
5
|
-
"moduleResolution": "
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
6
|
"outDir": "./dist",
|
|
7
7
|
"rootDir": "./src",
|
|
8
8
|
"strict": true,
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"declaration": true,
|
|
13
13
|
"declarationMap": true,
|
|
14
14
|
"sourceMap": true,
|
|
15
|
-
"resolveJsonModule": true
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"jsxImportSource": "react"
|
|
16
18
|
},
|
|
17
19
|
"include": ["src/**/*"],
|
|
18
20
|
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|