@orderful/droid 0.17.2 → 0.19.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/claude-code-review.yml +58 -0
- package/.github/workflows/claude.yml +50 -0
- package/CHANGELOG.md +37 -0
- package/dist/bin/droid.js +212 -79
- package/dist/index.js +188 -69
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/migrations.d.ts +30 -0
- package/dist/lib/migrations.d.ts.map +1 -0
- package/dist/lib/skill-config.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 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/brain/TOOL.yaml +3 -3
- package/{src/tools/brain/skills/brain → dist/tools/brain/skills/droid-brain}/SKILL.md +1 -1
- package/dist/tools/brain/skills/{brain-obsidian → droid-brain-obsidian}/SKILL.md +1 -1
- package/dist/tools/coach/TOOL.yaml +2 -2
- package/dist/tools/coach/skills/{coach → droid-coach}/SKILL.md +1 -1
- package/dist/tools/code-review/TOOL.yaml +2 -2
- package/dist/tools/code-review/skills/{code-review → droid-code-review}/SKILL.md +1 -1
- package/dist/tools/comments/TOOL.yaml +2 -2
- package/dist/tools/comments/skills/{comments → droid-comments}/SKILL.md +1 -1
- package/dist/tools/project/TOOL.yaml +2 -2
- package/{src/tools/project/skills/project → dist/tools/project/skills/droid-project}/SKILL.md +1 -1
- package/package.json +1 -1
- package/src/lib/config.ts +13 -2
- package/src/lib/migrations.test.ts +163 -0
- package/src/lib/migrations.ts +182 -0
- package/src/lib/skill-config.ts +3 -1
- package/src/lib/skills.ts +57 -1
- package/src/lib/tools.ts +16 -2
- package/src/lib/types.ts +11 -0
- package/src/tools/brain/TOOL.yaml +3 -3
- package/{dist/tools/brain/skills/brain → src/tools/brain/skills/droid-brain}/SKILL.md +1 -1
- package/src/tools/brain/skills/{brain-obsidian → droid-brain-obsidian}/SKILL.md +1 -1
- package/src/tools/coach/TOOL.yaml +2 -2
- package/src/tools/coach/skills/{coach → droid-coach}/SKILL.md +1 -1
- package/src/tools/code-review/TOOL.yaml +2 -2
- package/src/tools/code-review/skills/{code-review → droid-code-review}/SKILL.md +1 -1
- package/src/tools/comments/TOOL.yaml +2 -2
- package/src/tools/comments/skills/{comments → droid-comments}/SKILL.md +1 -1
- package/src/tools/project/TOOL.yaml +2 -2
- package/{dist/tools/project/skills/project → src/tools/project/skills/droid-project}/SKILL.md +1 -1
- /package/dist/tools/brain/skills/{brain → droid-brain}/references/metadata.md +0 -0
- /package/dist/tools/brain/skills/{brain → droid-brain}/references/naming.md +0 -0
- /package/dist/tools/brain/skills/{brain → droid-brain}/references/templates.md +0 -0
- /package/dist/tools/brain/skills/{brain → droid-brain}/references/workflows.md +0 -0
- /package/dist/tools/brain/skills/{brain-obsidian → droid-brain-obsidian}/references/templates.md +0 -0
- /package/dist/tools/brain/skills/{brain-obsidian → droid-brain-obsidian}/references/workflows.md +0 -0
- /package/dist/tools/project/skills/{project → droid-project}/references/changelog.md +0 -0
- /package/dist/tools/project/skills/{project → droid-project}/references/creating.md +0 -0
- /package/dist/tools/project/skills/{project → droid-project}/references/loading.md +0 -0
- /package/dist/tools/project/skills/{project → droid-project}/references/templates.md +0 -0
- /package/dist/tools/project/skills/{project → droid-project}/references/updating.md +0 -0
- /package/dist/tools/project/skills/{project → droid-project}/references/versioning.md +0 -0
- /package/src/tools/brain/skills/{brain → droid-brain}/references/metadata.md +0 -0
- /package/src/tools/brain/skills/{brain → droid-brain}/references/naming.md +0 -0
- /package/src/tools/brain/skills/{brain → droid-brain}/references/templates.md +0 -0
- /package/src/tools/brain/skills/{brain → droid-brain}/references/workflows.md +0 -0
- /package/src/tools/brain/skills/{brain-obsidian → droid-brain-obsidian}/references/templates.md +0 -0
- /package/src/tools/brain/skills/{brain-obsidian → droid-brain-obsidian}/references/workflows.md +0 -0
- /package/src/tools/project/skills/{project → droid-project}/references/changelog.md +0 -0
- /package/src/tools/project/skills/{project → droid-project}/references/creating.md +0 -0
- /package/src/tools/project/skills/{project → droid-project}/references/loading.md +0 -0
- /package/src/tools/project/skills/{project → droid-project}/references/templates.md +0 -0
- /package/src/tools/project/skills/{project → droid-project}/references/updating.md +0 -0
- /package/src/tools/project/skills/{project → droid-project}/references/versioning.md +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
name: project
|
|
2
2
|
description: "Manage project context files for persistent AI memory across sessions. Load, update, or create project context before working on multi-session features."
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.5
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
7
7
|
skills:
|
|
8
|
-
- name: project
|
|
8
|
+
- name: droid-project
|
|
9
9
|
required: true
|
|
10
10
|
commands:
|
|
11
11
|
- project
|
package/{src/tools/project/skills/project → dist/tools/project/skills/droid-project}/SKILL.md
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: project
|
|
2
|
+
name: droid-project
|
|
3
3
|
description: "Persistent project context for AI memory across sessions. Use when working on multi-session features, refactors, or any work that benefits from accumulated context. User prompts like 'load the project', 'update project context', 'what's the current project?'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/PROJECT.md"
|
package/package.json
CHANGED
package/src/lib/config.ts
CHANGED
|
@@ -160,11 +160,21 @@ export function getConfigDir(): string {
|
|
|
160
160
|
return CONFIG_DIR;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Normalize skill name for config paths.
|
|
165
|
+
* Strips 'droid-' prefix since that was added as a workaround for Claude Code bug.
|
|
166
|
+
* Config paths should stay intuitive (e.g., ~/.droid/skills/brain/ not ~/.droid/skills/droid-brain/)
|
|
167
|
+
*/
|
|
168
|
+
function normalizeSkillNameForConfig(skillName: string): string {
|
|
169
|
+
return skillName.replace(/^droid-/, '');
|
|
170
|
+
}
|
|
171
|
+
|
|
163
172
|
/**
|
|
164
173
|
* Get skill-specific overrides path
|
|
165
174
|
*/
|
|
166
175
|
export function getSkillOverridesPath(skillName: string): string {
|
|
167
|
-
|
|
176
|
+
const normalizedName = normalizeSkillNameForConfig(skillName);
|
|
177
|
+
return join(CONFIG_DIR, 'skills', normalizedName, 'overrides.yaml');
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
/**
|
|
@@ -189,8 +199,9 @@ export function loadSkillOverrides(skillName: string): SkillOverrides {
|
|
|
189
199
|
* Save skill-specific overrides
|
|
190
200
|
*/
|
|
191
201
|
export function saveSkillOverrides(skillName: string, overrides: SkillOverrides): void {
|
|
202
|
+
const normalizedName = normalizeSkillNameForConfig(skillName);
|
|
192
203
|
const overridesPath = getSkillOverridesPath(skillName);
|
|
193
|
-
const skillDir = join(CONFIG_DIR, 'skills',
|
|
204
|
+
const skillDir = join(CONFIG_DIR, 'skills', normalizedName);
|
|
194
205
|
|
|
195
206
|
if (!existsSync(skillDir)) {
|
|
196
207
|
mkdirSync(skillDir, { recursive: true });
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync, readFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { getToolMigrations } from './migrations';
|
|
6
|
+
|
|
7
|
+
describe('migrations', () => {
|
|
8
|
+
let testDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
testDir = join(tmpdir(), `droid-migration-test-${Date.now()}`);
|
|
12
|
+
mkdirSync(testDir, { recursive: true });
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (existsSync(testDir)) {
|
|
17
|
+
rmSync(testDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('getToolMigrations', () => {
|
|
22
|
+
it('should return migrations for known tools', () => {
|
|
23
|
+
const brainMigrations = getToolMigrations('brain');
|
|
24
|
+
expect(brainMigrations.length).toBeGreaterThan(0);
|
|
25
|
+
expect(brainMigrations[0].version).toBe('0.2.3');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return empty array for unknown tools', () => {
|
|
29
|
+
const migrations = getToolMigrations('unknown-tool');
|
|
30
|
+
expect(migrations).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should have migrations for all renamed tools', () => {
|
|
34
|
+
const tools = ['brain', 'comments', 'project', 'coach'];
|
|
35
|
+
for (const tool of tools) {
|
|
36
|
+
const migrations = getToolMigrations(tool);
|
|
37
|
+
expect(migrations.length).toBeGreaterThan(0);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('config directory migration', () => {
|
|
43
|
+
it('should move old prefixed directory to unprefixed', () => {
|
|
44
|
+
// Setup: create old prefixed directory with config
|
|
45
|
+
const skillsDir = join(testDir, 'skills');
|
|
46
|
+
const oldDir = join(skillsDir, 'droid-brain');
|
|
47
|
+
const newDir = join(skillsDir, 'brain');
|
|
48
|
+
|
|
49
|
+
mkdirSync(oldDir, { recursive: true });
|
|
50
|
+
writeFileSync(join(oldDir, 'overrides.yaml'), 'brain_dir: /test/path\n');
|
|
51
|
+
|
|
52
|
+
// Get the migration and run it
|
|
53
|
+
const migrations = getToolMigrations('brain');
|
|
54
|
+
const migration = migrations[0];
|
|
55
|
+
migration.up(testDir);
|
|
56
|
+
|
|
57
|
+
// Verify
|
|
58
|
+
expect(existsSync(oldDir)).toBe(false);
|
|
59
|
+
expect(existsSync(newDir)).toBe(true);
|
|
60
|
+
expect(readFileSync(join(newDir, 'overrides.yaml'), 'utf-8')).toBe('brain_dir: /test/path\n');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should be idempotent - no error if old directory already moved', () => {
|
|
64
|
+
// Setup: only new directory exists (already migrated)
|
|
65
|
+
const skillsDir = join(testDir, 'skills');
|
|
66
|
+
const newDir = join(skillsDir, 'brain');
|
|
67
|
+
|
|
68
|
+
mkdirSync(newDir, { recursive: true });
|
|
69
|
+
writeFileSync(join(newDir, 'overrides.yaml'), 'brain_dir: /test/path\n');
|
|
70
|
+
|
|
71
|
+
// Get the migration and run it - should not throw
|
|
72
|
+
const migrations = getToolMigrations('brain');
|
|
73
|
+
const migration = migrations[0];
|
|
74
|
+
|
|
75
|
+
expect(() => migration.up(testDir)).not.toThrow();
|
|
76
|
+
expect(existsSync(newDir)).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should handle collision by removing old directory', () => {
|
|
80
|
+
// Setup: both old and new directories exist
|
|
81
|
+
const skillsDir = join(testDir, 'skills');
|
|
82
|
+
const oldDir = join(skillsDir, 'droid-brain');
|
|
83
|
+
const newDir = join(skillsDir, 'brain');
|
|
84
|
+
|
|
85
|
+
mkdirSync(oldDir, { recursive: true });
|
|
86
|
+
mkdirSync(newDir, { recursive: true });
|
|
87
|
+
writeFileSync(join(oldDir, 'overrides.yaml'), 'old: true\n');
|
|
88
|
+
writeFileSync(join(newDir, 'overrides.yaml'), 'new: true\n');
|
|
89
|
+
|
|
90
|
+
// Run migration
|
|
91
|
+
const migrations = getToolMigrations('brain');
|
|
92
|
+
const migration = migrations[0];
|
|
93
|
+
migration.up(testDir);
|
|
94
|
+
|
|
95
|
+
// Old should be removed, new should be preserved
|
|
96
|
+
expect(existsSync(oldDir)).toBe(false);
|
|
97
|
+
expect(existsSync(newDir)).toBe(true);
|
|
98
|
+
expect(readFileSync(join(newDir, 'overrides.yaml'), 'utf-8')).toBe('new: true\n');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should do nothing if neither directory exists', () => {
|
|
102
|
+
// Setup: empty skills directory
|
|
103
|
+
const skillsDir = join(testDir, 'skills');
|
|
104
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
105
|
+
|
|
106
|
+
// Run migration - should not throw
|
|
107
|
+
const migrations = getToolMigrations('brain');
|
|
108
|
+
const migration = migrations[0];
|
|
109
|
+
|
|
110
|
+
expect(() => migration.up(testDir)).not.toThrow();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should create parent directory if needed', () => {
|
|
114
|
+
// Setup: old directory exists but skills/ parent doesn't exist for new
|
|
115
|
+
const oldDir = join(testDir, 'skills', 'droid-brain');
|
|
116
|
+
mkdirSync(oldDir, { recursive: true });
|
|
117
|
+
writeFileSync(join(oldDir, 'overrides.yaml'), 'test: true\n');
|
|
118
|
+
|
|
119
|
+
// Run migration
|
|
120
|
+
const migrations = getToolMigrations('brain');
|
|
121
|
+
const migration = migrations[0];
|
|
122
|
+
migration.up(testDir);
|
|
123
|
+
|
|
124
|
+
// Should have moved successfully
|
|
125
|
+
const newDir = join(testDir, 'skills', 'brain');
|
|
126
|
+
expect(existsSync(newDir)).toBe(true);
|
|
127
|
+
expect(existsSync(oldDir)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('migration versions', () => {
|
|
132
|
+
it('brain migration should be version 0.2.3', () => {
|
|
133
|
+
const migrations = getToolMigrations('brain');
|
|
134
|
+
expect(migrations[0].version).toBe('0.2.3');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('comments migration should be version 0.2.6', () => {
|
|
138
|
+
const migrations = getToolMigrations('comments');
|
|
139
|
+
expect(migrations[0].version).toBe('0.2.6');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('project migration should be version 0.1.5', () => {
|
|
143
|
+
const migrations = getToolMigrations('project');
|
|
144
|
+
expect(migrations[0].version).toBe('0.1.5');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('coach migration should be version 0.1.3', () => {
|
|
148
|
+
const migrations = getToolMigrations('coach');
|
|
149
|
+
expect(migrations[0].version).toBe('0.1.3');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('migration descriptions', () => {
|
|
154
|
+
it('should have descriptive migration descriptions', () => {
|
|
155
|
+
const tools = ['brain', 'comments', 'project', 'coach'];
|
|
156
|
+
for (const tool of tools) {
|
|
157
|
+
const migrations = getToolMigrations(tool);
|
|
158
|
+
expect(migrations[0].description).toContain('config');
|
|
159
|
+
expect(migrations[0].description).toContain('unprefixed');
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync, appendFileSync, mkdirSync, renameSync, rmSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { loadConfig, saveConfig, getConfigDir } from './config';
|
|
4
|
+
import { type Migration } from './types';
|
|
5
|
+
import { compareSemver } from './version';
|
|
6
|
+
|
|
7
|
+
const MIGRATIONS_LOG_FILE = '.migrations.log';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get the path to the migrations log file
|
|
11
|
+
*/
|
|
12
|
+
function getMigrationsLogPath(): string {
|
|
13
|
+
return join(getConfigDir(), MIGRATIONS_LOG_FILE);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Log a migration event to the hidden log file
|
|
18
|
+
*/
|
|
19
|
+
function logMigration(
|
|
20
|
+
toolName: string,
|
|
21
|
+
fromVersion: string,
|
|
22
|
+
toVersion: string,
|
|
23
|
+
status: 'OK' | 'FAILED',
|
|
24
|
+
error?: string
|
|
25
|
+
): void {
|
|
26
|
+
const timestamp = new Date().toISOString();
|
|
27
|
+
const logEntry = error
|
|
28
|
+
? `${timestamp} ${toolName} ${fromVersion} → ${toVersion} ${status}: ${error}\n`
|
|
29
|
+
: `${timestamp} ${toolName} ${fromVersion} → ${toVersion} ${status}\n`;
|
|
30
|
+
|
|
31
|
+
const logPath = getMigrationsLogPath();
|
|
32
|
+
const logDir = dirname(logPath);
|
|
33
|
+
|
|
34
|
+
if (!existsSync(logDir)) {
|
|
35
|
+
mkdirSync(logDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
appendFileSync(logPath, logEntry);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Tool Migrations Registry
|
|
43
|
+
// Define migrations here, keyed by tool name
|
|
44
|
+
// ============================================================================
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Migration: Move droid-prefixed config directories to unprefixed names
|
|
48
|
+
* e.g., ~/.droid/skills/droid-brain/ → ~/.droid/skills/brain/
|
|
49
|
+
*
|
|
50
|
+
* This fixes a bug where config was saved to prefixed paths but SKILL.md
|
|
51
|
+
* tells Claude to read from unprefixed paths.
|
|
52
|
+
*/
|
|
53
|
+
function createConfigDirMigration(skillName: string, version: string): Migration {
|
|
54
|
+
const unprefixedName = skillName.replace(/^droid-/, '');
|
|
55
|
+
return {
|
|
56
|
+
version,
|
|
57
|
+
description: `Move ${skillName} config to unprefixed location`,
|
|
58
|
+
up: (configDir: string) => {
|
|
59
|
+
const oldDir = join(configDir, 'skills', skillName);
|
|
60
|
+
const newDir = join(configDir, 'skills', unprefixedName);
|
|
61
|
+
|
|
62
|
+
// Only migrate if old exists and new doesn't
|
|
63
|
+
if (existsSync(oldDir) && !existsSync(newDir)) {
|
|
64
|
+
// Ensure parent directory exists
|
|
65
|
+
const parentDir = dirname(newDir);
|
|
66
|
+
if (!existsSync(parentDir)) {
|
|
67
|
+
mkdirSync(parentDir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
renameSync(oldDir, newDir);
|
|
70
|
+
} else if (existsSync(oldDir) && existsSync(newDir)) {
|
|
71
|
+
// Both exist - remove the old one (new takes precedence)
|
|
72
|
+
rmSync(oldDir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Registry of all tool migrations
|
|
80
|
+
* Key: tool name (e.g., "brain", "comments")
|
|
81
|
+
* Value: array of migrations sorted by version
|
|
82
|
+
*
|
|
83
|
+
* Migration versions are set to the next version after each tool's current version:
|
|
84
|
+
* - brain: 0.2.2 → 0.2.3
|
|
85
|
+
* - comments: 0.2.5 → 0.2.6
|
|
86
|
+
* - project: 0.1.4 → 0.1.5
|
|
87
|
+
* - coach: 0.1.2 → 0.1.3
|
|
88
|
+
*/
|
|
89
|
+
const TOOL_MIGRATIONS: Record<string, Migration[]> = {
|
|
90
|
+
brain: [createConfigDirMigration('droid-brain', '0.2.3')],
|
|
91
|
+
comments: [createConfigDirMigration('droid-comments', '0.2.6')],
|
|
92
|
+
project: [createConfigDirMigration('droid-project', '0.1.5')],
|
|
93
|
+
coach: [createConfigDirMigration('droid-coach', '0.1.3')],
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get migrations for a tool
|
|
98
|
+
*/
|
|
99
|
+
export function getToolMigrations(toolName: string): Migration[] {
|
|
100
|
+
return TOOL_MIGRATIONS[toolName] ?? [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get the last migrated version for a tool
|
|
105
|
+
*/
|
|
106
|
+
export function getLastMigratedVersion(toolName: string): string {
|
|
107
|
+
const config = loadConfig();
|
|
108
|
+
return config.migrations?.[toolName] ?? '0.0.0';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Update the last migrated version for a tool
|
|
113
|
+
*/
|
|
114
|
+
export function setLastMigratedVersion(toolName: string, version: string): void {
|
|
115
|
+
const config = loadConfig();
|
|
116
|
+
if (!config.migrations) {
|
|
117
|
+
config.migrations = {};
|
|
118
|
+
}
|
|
119
|
+
config.migrations[toolName] = version;
|
|
120
|
+
saveConfig(config);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Run migrations for a tool between two versions
|
|
125
|
+
* Returns true if all migrations succeeded, false otherwise
|
|
126
|
+
*/
|
|
127
|
+
export function runMigrations(
|
|
128
|
+
toolName: string,
|
|
129
|
+
fromVersion: string,
|
|
130
|
+
toVersion: string
|
|
131
|
+
): { success: boolean; error?: string } {
|
|
132
|
+
const migrations = getToolMigrations(toolName);
|
|
133
|
+
|
|
134
|
+
// Filter migrations that need to run (version > fromVersion && version <= toVersion)
|
|
135
|
+
const pendingMigrations = migrations.filter((m) => {
|
|
136
|
+
const afterFrom = compareSemver(m.version, fromVersion) > 0;
|
|
137
|
+
const beforeOrAtTo = compareSemver(m.version, toVersion) <= 0;
|
|
138
|
+
return afterFrom && beforeOrAtTo;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (pendingMigrations.length === 0) {
|
|
142
|
+
// No migrations to run, but still update the version marker
|
|
143
|
+
setLastMigratedVersion(toolName, toVersion);
|
|
144
|
+
return { success: true };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const configDir = getConfigDir();
|
|
148
|
+
|
|
149
|
+
for (const migration of pendingMigrations) {
|
|
150
|
+
try {
|
|
151
|
+
migration.up(configDir);
|
|
152
|
+
logMigration(toolName, fromVersion, migration.version, 'OK');
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
155
|
+
logMigration(toolName, fromVersion, migration.version, 'FAILED', errorMessage);
|
|
156
|
+
// Don't update version marker on failure - will retry next time
|
|
157
|
+
return { success: false, error: `Migration ${migration.version} failed: ${errorMessage}` };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// All migrations succeeded, update version marker
|
|
162
|
+
setLastMigratedVersion(toolName, toVersion);
|
|
163
|
+
return { success: true };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Run migrations for a tool after install/update
|
|
168
|
+
* Call this after installSkill() completes
|
|
169
|
+
*/
|
|
170
|
+
export function runToolMigrations(
|
|
171
|
+
toolName: string,
|
|
172
|
+
installedVersion: string
|
|
173
|
+
): { success: boolean; error?: string } {
|
|
174
|
+
const lastMigrated = getLastMigratedVersion(toolName);
|
|
175
|
+
|
|
176
|
+
// Only run if the installed version is newer than last migrated
|
|
177
|
+
if (compareSemver(installedVersion, lastMigrated) <= 0) {
|
|
178
|
+
return { success: true };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return runMigrations(toolName, lastMigrated, installedVersion);
|
|
182
|
+
}
|
package/src/lib/skill-config.ts
CHANGED
|
@@ -86,7 +86,9 @@ export async function promptForSkillConfig(
|
|
|
86
86
|
|
|
87
87
|
if (Object.keys(overrides).length > 0) {
|
|
88
88
|
saveSkillOverrides(skillName, overrides);
|
|
89
|
-
|
|
89
|
+
// Strip droid- prefix for display since that's where the file actually goes
|
|
90
|
+
const displayName = skillName.replace(/^droid-/, '');
|
|
91
|
+
console.log(chalk.green(`\n✓ Configuration saved to ~/.droid/skills/${displayName}/overrides.yaml`));
|
|
90
92
|
} else {
|
|
91
93
|
console.log(chalk.gray('\nNo custom configuration set (using defaults).'));
|
|
92
94
|
}
|
package/src/lib/skills.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
2
|
+
import { join, dirname, basename } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
5
|
import { loadConfig, saveConfig } from './config';
|
|
@@ -7,6 +7,7 @@ import { Platform, SkillStatus, type SkillManifest, type InstalledSkill, getPlat
|
|
|
7
7
|
import { getInstalledAgentsDir, installAgentFromPath, uninstallAgent, isAgentInstalled } from './agents';
|
|
8
8
|
import { getSkillsPath, getCommandsPath, getConfigPath } from './platforms';
|
|
9
9
|
import { loadToolManifest } from './tools';
|
|
10
|
+
import { runToolMigrations } from './migrations';
|
|
10
11
|
|
|
11
12
|
// Marker comments for CLAUDE.md skill registration
|
|
12
13
|
const DROID_SKILLS_START = '<!-- droid-skills-start -->';
|
|
@@ -378,6 +379,53 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
378
379
|
const commandsPath = getCommandsInstallPath(config.platform);
|
|
379
380
|
const tools = getPlatformTools(config);
|
|
380
381
|
|
|
382
|
+
// Clean up old skill directory if this is a renamed skill (v0.18.0 workaround for Claude Code bug)
|
|
383
|
+
// Renamed skills have 'droid-' prefix, so check for old directory without prefix
|
|
384
|
+
// Bug: https://github.com/anthropics/claude-code/issues/14945
|
|
385
|
+
//
|
|
386
|
+
// TO REVERSE WHEN BUG IS FIXED:
|
|
387
|
+
// 1. Rename directories: droid-comments/ → comments/, droid-brain/ → brain/, etc.
|
|
388
|
+
// 2. Update TOOL.yaml manifests back to original skill names
|
|
389
|
+
// 3. Update SKILL.md frontmatter back to original names
|
|
390
|
+
// 4. Replace this cleanup logic with inverse:
|
|
391
|
+
// const renamedSkills = ['comments', 'brain', 'project', 'coach', 'code-review', 'brain-obsidian'];
|
|
392
|
+
// if (renamedSkills.includes(skillName)) {
|
|
393
|
+
// const droidPrefixedName = `droid-${skillName}`;
|
|
394
|
+
// const droidPrefixedDir = join(skillsPath, droidPrefixedName);
|
|
395
|
+
// if (existsSync(droidPrefixedDir)) {
|
|
396
|
+
// rmSync(droidPrefixedDir, { recursive: true });
|
|
397
|
+
// }
|
|
398
|
+
// if (tools[droidPrefixedName]) {
|
|
399
|
+
// delete tools[droidPrefixedName];
|
|
400
|
+
// }
|
|
401
|
+
// }
|
|
402
|
+
// 5. Bump tool versions and create changeset
|
|
403
|
+
// Migration logic: Handle skills renamed from {name} to droid-{name} (v0.18.0 workaround)
|
|
404
|
+
// This handles both fresh installs and updates where old directory/config entries exist
|
|
405
|
+
if (skillName.startsWith('droid-')) {
|
|
406
|
+
const oldSkillName = skillName.replace(/^droid-/, '');
|
|
407
|
+
const oldSkillDir = join(skillsPath, oldSkillName);
|
|
408
|
+
|
|
409
|
+
// Remove or migrate old skill directory
|
|
410
|
+
if (existsSync(oldSkillDir)) {
|
|
411
|
+
try {
|
|
412
|
+
rmSync(oldSkillDir, { recursive: true });
|
|
413
|
+
} catch (error) {
|
|
414
|
+
// Non-fatal: Log warning but continue installation
|
|
415
|
+
console.warn(`Warning: Could not remove old skill directory ${oldSkillDir}: ${error}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Migrate old config entry if it exists
|
|
420
|
+
// This allows tools to show as "update available" rather than "not installed"
|
|
421
|
+
if (tools[oldSkillName]) {
|
|
422
|
+
delete tools[oldSkillName];
|
|
423
|
+
// Save immediately to ensure cleanup is persisted (matches pattern in tools.ts)
|
|
424
|
+
setPlatformTools(config, tools);
|
|
425
|
+
saveConfig(config);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
381
429
|
// Commands and agents are at the tool level, not skill level
|
|
382
430
|
const commandsSource = join(toolDir, 'commands');
|
|
383
431
|
const agentsSource = join(toolDir, 'agents');
|
|
@@ -495,6 +543,14 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
495
543
|
const installedSkillNames = Object.keys(updatedTools);
|
|
496
544
|
updatePlatformConfigSkills(config.platform, installedSkillNames);
|
|
497
545
|
|
|
546
|
+
// Run tool migrations (keyed by tool name, not skill name)
|
|
547
|
+
const toolName = basename(toolDir);
|
|
548
|
+
const migrationResult = runToolMigrations(toolName, manifest.version);
|
|
549
|
+
if (!migrationResult.success) {
|
|
550
|
+
// Log warning but don't fail the install - migration will retry next time
|
|
551
|
+
console.warn(`Warning: Migration failed for ${toolName}: ${migrationResult.error}`);
|
|
552
|
+
}
|
|
553
|
+
|
|
498
554
|
return { success: true, message: `Installed ${skillName} v${manifest.version}` };
|
|
499
555
|
}
|
|
500
556
|
|
package/src/lib/tools.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import YAML from 'yaml';
|
|
5
|
-
import { loadConfig } from './config';
|
|
6
|
-
import { type ToolManifest, type ToolIncludes, getPlatformTools } from './types';
|
|
5
|
+
import { loadConfig, saveConfig } from './config';
|
|
6
|
+
import { type ToolManifest, type ToolIncludes, getPlatformTools, setPlatformTools } from './types';
|
|
7
7
|
import { compareSemver } from './version';
|
|
8
8
|
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -108,6 +108,20 @@ export function getInstalledToolVersion(toolName: string): string | null {
|
|
|
108
108
|
if (installedTools[skillName]) {
|
|
109
109
|
return installedTools[skillName].version;
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
// Migration fallback (v0.18.0): Check for old skill name without droid- prefix
|
|
113
|
+
// This allows tools to show as "update available" rather than "not installed"
|
|
114
|
+
if (skillName.startsWith('droid-')) {
|
|
115
|
+
const oldSkillName = skillName.replace(/^droid-/, '');
|
|
116
|
+
if (installedTools[oldSkillName]) {
|
|
117
|
+
const version = installedTools[oldSkillName].version;
|
|
118
|
+
// Clean up stale config entry now that we've detected it
|
|
119
|
+
delete installedTools[oldSkillName];
|
|
120
|
+
setPlatformTools(config, installedTools);
|
|
121
|
+
saveConfig(config);
|
|
122
|
+
return version;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
return null;
|
package/src/lib/types.ts
CHANGED
|
@@ -54,6 +54,7 @@ export interface DroidConfig {
|
|
|
54
54
|
git_username: string;
|
|
55
55
|
platforms: Record<string, PlatformConfig>;
|
|
56
56
|
auto_update?: AutoUpdateConfig;
|
|
57
|
+
migrations?: Record<string, string>; // tool name -> last migrated version
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// Legacy config structure for migration
|
|
@@ -120,6 +121,16 @@ export interface SkillOverrides {
|
|
|
120
121
|
[key: string]: string | boolean | number;
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Tool migration function
|
|
126
|
+
* Should be idempotent - safe to run multiple times
|
|
127
|
+
*/
|
|
128
|
+
export interface Migration {
|
|
129
|
+
version: string;
|
|
130
|
+
description: string;
|
|
131
|
+
up: (configDir: string) => Promise<void> | void;
|
|
132
|
+
}
|
|
133
|
+
|
|
123
134
|
/**
|
|
124
135
|
* Tool manifest structure (TOOL.yaml)
|
|
125
136
|
*/
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
name: brain
|
|
2
2
|
description: "Your scratchpad (or brain) - a collaborative space for planning and research. Create docs with /brain plan, /brain research, or /brain review. Use @mentions for async discussion. Docs persist across sessions."
|
|
3
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.3
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
7
7
|
skills:
|
|
8
|
-
- name: brain
|
|
8
|
+
- name: droid-brain
|
|
9
9
|
required: true
|
|
10
|
-
- name: brain-obsidian
|
|
10
|
+
- name: droid-brain-obsidian
|
|
11
11
|
required: false
|
|
12
12
|
description: "Obsidian vault integration with YAML frontmatter and wikilinks"
|
|
13
13
|
commands:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: brain
|
|
2
|
+
name: droid-brain
|
|
3
3
|
description: "Collaborative scratchpad for planning and research. Use when planning a feature, exploring a problem, or capturing thinking that should persist across sessions. User prompts like 'let's think through', 'open a scratchpad', 'plan this out', 'use our brain'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/brain/**/*.md"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
name: coach
|
|
2
2
|
description: "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."
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.3
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
7
7
|
skills:
|
|
8
|
-
- name: coach
|
|
8
|
+
- name: droid-coach
|
|
9
9
|
required: true
|
|
10
10
|
commands:
|
|
11
11
|
- coach
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: coach
|
|
2
|
+
name: droid-coach
|
|
3
3
|
description: "Learning-mode AI assistance - scaffolds don't implement, questions don't fix. Use when learning a new codebase, wanting to understand deeply, or building skills to retain. User prompts like 'coach me on', 'help me think through', 'I want to learn how to', 'don't just give me the answer'."
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
allowed-tools: Read, Grep, Glob
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
name: code-review
|
|
2
2
|
description: "Comprehensive code review using specialized agents. Reviews PRs, staged changes, branches, or specific files with confidence scoring."
|
|
3
|
-
version: 0.1.
|
|
3
|
+
version: 0.1.2
|
|
4
4
|
status: alpha
|
|
5
5
|
|
|
6
6
|
includes:
|
|
7
7
|
skills:
|
|
8
|
-
- name: code-review
|
|
8
|
+
- name: droid-code-review
|
|
9
9
|
required: true
|
|
10
10
|
commands:
|
|
11
11
|
- code-review
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: code-review
|
|
2
|
+
name: droid-code-review
|
|
3
3
|
description: "Comprehensive code review using specialized agents. Use when reviewing PRs, checking staged changes, or analysing code quality. User prompts like 'review this PR', 'check my changes', 'review before I commit'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
name: comments
|
|
2
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
|
-
version: 0.2.
|
|
3
|
+
version: 0.2.6
|
|
4
4
|
status: beta
|
|
5
5
|
|
|
6
6
|
includes:
|
|
7
7
|
skills:
|
|
8
|
-
- name: comments
|
|
8
|
+
- name: droid-comments
|
|
9
9
|
required: true
|
|
10
10
|
commands:
|
|
11
11
|
- comments
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: comments
|
|
2
|
+
name: droid-comments
|
|
3
3
|
description: "Inline conversations using @droid/@user markers in any file. Use when leaving async questions in code or having discussion that should stay near the code. User prompts like 'check for comments', 'address the @droid markers', 'clean up resolved comments'."
|
|
4
4
|
globs:
|
|
5
5
|
- "**/*"
|