@orderful/droid 0.4.0 → 0.5.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/CLAUDE.md +4 -4
- package/.github/workflows/changeset-check.yml +40 -11
- package/.github/workflows/release.yml +0 -1
- package/CHANGELOG.md +33 -0
- package/bun.lock +2 -182
- package/dist/commands/tui.d.ts.map +1 -1
- package/dist/commands/tui.js +55 -16
- package/dist/commands/tui.js.map +1 -1
- package/dist/lib/skills.d.ts +38 -0
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +130 -4
- package/dist/lib/skills.js.map +1 -1
- package/dist/skills/comments/SKILL.md +28 -5
- package/dist/skills/comments/SKILL.yaml +6 -2
- package/dist/skills/comments/commands/comments.md +15 -34
- package/dist/skills/project/SKILL.md +93 -0
- package/dist/skills/project/SKILL.yaml +35 -0
- package/dist/skills/project/commands/README.md +26 -0
- package/dist/skills/project/commands/project.md +39 -0
- package/dist/skills/project/references/changelog.md +70 -0
- package/dist/skills/project/references/creating.md +58 -0
- package/dist/skills/project/references/loading.md +40 -0
- package/dist/skills/project/references/templates.md +124 -0
- package/dist/skills/project/references/updating.md +64 -0
- package/dist/skills/project/references/versioning.md +36 -0
- package/package.json +1 -3
- package/src/commands/tui.tsx +65 -19
- package/src/lib/skills.ts +160 -4
- package/src/skills/comments/SKILL.md +28 -5
- package/src/skills/comments/SKILL.yaml +6 -2
- package/src/skills/comments/commands/comments.md +15 -34
- package/src/skills/project/SKILL.md +93 -0
- package/src/skills/project/SKILL.yaml +35 -0
- package/src/skills/project/commands/README.md +26 -0
- package/src/skills/project/commands/project.md +39 -0
- package/src/skills/project/references/changelog.md +70 -0
- package/src/skills/project/references/creating.md +58 -0
- package/src/skills/project/references/loading.md +40 -0
- package/src/skills/project/references/templates.md +124 -0
- package/src/skills/project/references/updating.md +64 -0
- package/src/skills/project/references/versioning.md +36 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Updating a Project
|
|
2
|
+
|
|
3
|
+
**Trigger:** `/project update` or user asks to capture/save learnings
|
|
4
|
+
|
|
5
|
+
## Procedure
|
|
6
|
+
|
|
7
|
+
1. **Identify the project:**
|
|
8
|
+
- If name provided: Find matching project (same logic as loading)
|
|
9
|
+
- If no name: Check conversation for prior project load
|
|
10
|
+
- If still unclear: List projects and ask which to update
|
|
11
|
+
|
|
12
|
+
2. **Analyze the conversation for:**
|
|
13
|
+
- New implementation details or patterns
|
|
14
|
+
- Key decisions and rationale
|
|
15
|
+
- Technical constraints discovered
|
|
16
|
+
- Progress on features
|
|
17
|
+
- Important file paths or code locations
|
|
18
|
+
- Bug fixes or gotchas encountered
|
|
19
|
+
|
|
20
|
+
3. **If significant new information found:**
|
|
21
|
+
- Propose specific sections/content to add or update
|
|
22
|
+
- Show what will be changed
|
|
23
|
+
- Ask for confirmation before proceeding
|
|
24
|
+
|
|
25
|
+
4. **If no obvious updates:**
|
|
26
|
+
- Ask what the user would like to update
|
|
27
|
+
- Suggest sections: metadata, implementation details, technical decisions, notes
|
|
28
|
+
|
|
29
|
+
5. **After approval:**
|
|
30
|
+
- Read current project file
|
|
31
|
+
- Make approved updates, preserving existing structure
|
|
32
|
+
- Determine version bump (see `versioning.md`)
|
|
33
|
+
- Add changelog entry (see `changelog.md`)
|
|
34
|
+
- Confirm what was updated
|
|
35
|
+
|
|
36
|
+
## What to Capture
|
|
37
|
+
|
|
38
|
+
Good project updates include:
|
|
39
|
+
|
|
40
|
+
| Type | Examples |
|
|
41
|
+
|------|----------|
|
|
42
|
+
| **Decisions** | "Chose cursor-based pagination over offset" |
|
|
43
|
+
| **Patterns** | "Using repository pattern for data access" |
|
|
44
|
+
| **Constraints** | "Must support backwards compatibility with v1 API" |
|
|
45
|
+
| **Gotchas** | "Watch out for timezone handling in date fields" |
|
|
46
|
+
| **Progress** | "Completed validation layer, starting on UI" |
|
|
47
|
+
| **File locations** | "Key files: `src/services/template.service.ts`" |
|
|
48
|
+
|
|
49
|
+
## Example
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
User: /project update
|
|
53
|
+
|
|
54
|
+
Claude: I'll update #project-transaction-templates with learnings from this session.
|
|
55
|
+
|
|
56
|
+
Proposed changes:
|
|
57
|
+
- Add "Permission Controls" section documenting the new RBAC approach
|
|
58
|
+
- Update Technical Details with the Handlebars helper patterns we established
|
|
59
|
+
- Note the decision to use soft deletes
|
|
60
|
+
|
|
61
|
+
This looks like a minor version bump (0.16.0 → 0.17.0).
|
|
62
|
+
|
|
63
|
+
Proceed with these updates?
|
|
64
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Version Bumping Rules
|
|
2
|
+
|
|
3
|
+
Use semantic versioning for project docs.
|
|
4
|
+
|
|
5
|
+
## When to Bump
|
|
6
|
+
|
|
7
|
+
| Type | When | Examples |
|
|
8
|
+
|------|------|----------|
|
|
9
|
+
| **Major** | Breaking changes, complete rewrites, fundamental architecture changes | New approach that invalidates previous docs |
|
|
10
|
+
| **Minor** | New features, new sections, significant additions | New helper added, new endpoint, new workflow |
|
|
11
|
+
| **Patch** | Clarifications, small updates, typo fixes, status changes | Wording tweaks, deferred features, bug notes |
|
|
12
|
+
|
|
13
|
+
## Auto-Determination
|
|
14
|
+
|
|
15
|
+
Analyze the changes being made:
|
|
16
|
+
|
|
17
|
+
1. Default to **minor** for most content additions
|
|
18
|
+
2. Use **patch** for small clarifications or status updates
|
|
19
|
+
3. Use **major** only for fundamental changes (rare)
|
|
20
|
+
4. Only ask user if genuinely ambiguous
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
**Patch (0.1.0 -> 0.1.1):**
|
|
25
|
+
- Fixed typo in technical details
|
|
26
|
+
- Updated status from active to reference
|
|
27
|
+
- Added note about known limitation
|
|
28
|
+
|
|
29
|
+
**Minor (0.1.1 -> 0.2.0):**
|
|
30
|
+
- Added new "Authentication" section
|
|
31
|
+
- Documented new API endpoint
|
|
32
|
+
- Added architecture diagram
|
|
33
|
+
|
|
34
|
+
**Major (0.2.0 -> 1.0.0):**
|
|
35
|
+
- Complete rewrite of implementation approach
|
|
36
|
+
- Switched from REST to GraphQL (invalidates prior docs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orderful/droid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AI workflow toolkit for sharing skills, commands, and agents across the team",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,8 +41,6 @@
|
|
|
41
41
|
"registry": "https://registry.npmjs.org/"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@opentui/core": "^0.1.59",
|
|
45
|
-
"@opentui/react": "^0.1.59",
|
|
46
44
|
"chalk": "^5.3.0",
|
|
47
45
|
"commander": "^12.1.0",
|
|
48
46
|
"ink": "^6.5.1",
|
package/src/commands/tui.tsx
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
getInstalledSkill,
|
|
12
12
|
installSkill,
|
|
13
13
|
uninstallSkill,
|
|
14
|
+
updateSkill,
|
|
15
|
+
getSkillUpdateStatus,
|
|
14
16
|
isCommandInstalled,
|
|
15
17
|
installCommand,
|
|
16
18
|
uninstallCommand,
|
|
@@ -62,7 +64,23 @@ function getCommandsFromSkills(): Command[] {
|
|
|
62
64
|
const content = readFileSync(join(commandsDir, file), 'utf-8');
|
|
63
65
|
const lines = content.split('\n');
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
// Skip YAML frontmatter if present
|
|
68
|
+
let startLine = 0;
|
|
69
|
+
if (lines[0]?.trim() === '---') {
|
|
70
|
+
for (let i = 1; i < lines.length; i++) {
|
|
71
|
+
if (lines[i]?.trim() === '---') {
|
|
72
|
+
startLine = i + 1;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Find first H1 header
|
|
79
|
+
let headerMatch: RegExpMatchArray | null = null;
|
|
80
|
+
for (let i = startLine; i < lines.length; i++) {
|
|
81
|
+
headerMatch = lines[i]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
|
|
82
|
+
if (headerMatch) break;
|
|
83
|
+
}
|
|
66
84
|
if (!headerMatch) continue;
|
|
67
85
|
|
|
68
86
|
const usage: string[] = [];
|
|
@@ -393,6 +411,7 @@ function SkillItem({
|
|
|
393
411
|
}) {
|
|
394
412
|
const installed = isSkillInstalled(skill.name);
|
|
395
413
|
const installedInfo = getInstalledSkill(skill.name);
|
|
414
|
+
const updateStatus = getSkillUpdateStatus(skill.name);
|
|
396
415
|
|
|
397
416
|
return (
|
|
398
417
|
<Box paddingX={1} backgroundColor={isActive ? colors.bgSelected : undefined}>
|
|
@@ -401,6 +420,7 @@ function SkillItem({
|
|
|
401
420
|
<Text color={isSelected || isActive ? colors.text : colors.textMuted}>{skill.name}</Text>
|
|
402
421
|
{installed && installedInfo && <Text color={colors.textDim}> v{installedInfo.version}</Text>}
|
|
403
422
|
{installed && <Text color={colors.success}> *</Text>}
|
|
423
|
+
{updateStatus.hasUpdate && <Text color={colors.primary}> ↑</Text>}
|
|
404
424
|
</Text>
|
|
405
425
|
</Box>
|
|
406
426
|
);
|
|
@@ -444,12 +464,16 @@ function SkillDetails({
|
|
|
444
464
|
}
|
|
445
465
|
|
|
446
466
|
const installed = isSkillInstalled(skill.name);
|
|
467
|
+
const updateStatus = getSkillUpdateStatus(skill.name);
|
|
447
468
|
const skillCommands = getCommandsFromSkills().filter((c) => c.skillName === skill.name);
|
|
448
469
|
|
|
449
470
|
const actions = installed
|
|
450
471
|
? [
|
|
451
472
|
{ id: 'view', label: 'View', variant: 'default' },
|
|
452
|
-
|
|
473
|
+
...(updateStatus.hasUpdate
|
|
474
|
+
? [{ id: 'update', label: `Update (${updateStatus.bundledVersion})`, variant: 'primary' }]
|
|
475
|
+
: []),
|
|
476
|
+
{ id: 'configure', label: 'Configure', variant: 'default' },
|
|
453
477
|
{ id: 'uninstall', label: 'Uninstall', variant: 'danger' },
|
|
454
478
|
]
|
|
455
479
|
: [
|
|
@@ -466,6 +490,9 @@ function SkillDetails({
|
|
|
466
490
|
{skill.version}
|
|
467
491
|
{skill.status && ` · ${skill.status}`}
|
|
468
492
|
{installed && <Text color={colors.success}> · installed</Text>}
|
|
493
|
+
{updateStatus.hasUpdate && (
|
|
494
|
+
<Text color={colors.primary}> · update available ({updateStatus.installedVersion} → {updateStatus.bundledVersion})</Text>
|
|
495
|
+
)}
|
|
469
496
|
</Text>
|
|
470
497
|
</Box>
|
|
471
498
|
|
|
@@ -1184,7 +1211,9 @@ function App() {
|
|
|
1184
1211
|
if (activeTab === 'skills') {
|
|
1185
1212
|
const skill = skills[selectedIndex];
|
|
1186
1213
|
const installed = skill ? isSkillInstalled(skill.name) : false;
|
|
1187
|
-
|
|
1214
|
+
const hasUpdate = skill ? getSkillUpdateStatus(skill.name).hasUpdate : false;
|
|
1215
|
+
// View, [Update], Configure, Uninstall or View, Install
|
|
1216
|
+
maxActions = installed ? (hasUpdate ? 3 : 2) : 1;
|
|
1188
1217
|
} else if (activeTab === 'agents') {
|
|
1189
1218
|
maxActions = 1; // View, Install/Uninstall
|
|
1190
1219
|
} else if (activeTab === 'commands') {
|
|
@@ -1199,20 +1228,38 @@ function App() {
|
|
|
1199
1228
|
const skill = skills[selectedIndex];
|
|
1200
1229
|
if (skill) {
|
|
1201
1230
|
const installed = isSkillInstalled(skill.name);
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1231
|
+
const skillUpdateStatus = getSkillUpdateStatus(skill.name);
|
|
1232
|
+
// Build actions array to match SkillDetails
|
|
1233
|
+
const skillActions = installed
|
|
1234
|
+
? [
|
|
1235
|
+
{ id: 'view' },
|
|
1236
|
+
...(skillUpdateStatus.hasUpdate ? [{ id: 'update' }] : []),
|
|
1237
|
+
{ id: 'configure' },
|
|
1238
|
+
{ id: 'uninstall' },
|
|
1239
|
+
]
|
|
1240
|
+
: [{ id: 'view' }, { id: 'install' }];
|
|
1241
|
+
|
|
1242
|
+
const actionId = skillActions[selectedAction]?.id;
|
|
1243
|
+
|
|
1244
|
+
if (actionId === 'view') {
|
|
1205
1245
|
const skillMdPath = join(getBundledSkillsDir(), skill.name, 'SKILL.md');
|
|
1206
1246
|
if (existsSync(skillMdPath)) {
|
|
1207
1247
|
const content = readFileSync(skillMdPath, 'utf-8');
|
|
1208
1248
|
setReadmeContent({ title: `${skill.name}/SKILL.md`, content });
|
|
1209
1249
|
setView('readme');
|
|
1210
1250
|
}
|
|
1211
|
-
} else if (
|
|
1212
|
-
|
|
1251
|
+
} else if (actionId === 'update') {
|
|
1252
|
+
const result = updateSkill(skill.name);
|
|
1253
|
+
setMessage({
|
|
1254
|
+
text: result.success ? `✓ ${result.message}` : `✗ ${result.message}`,
|
|
1255
|
+
type: result.success ? 'success' : 'error',
|
|
1256
|
+
});
|
|
1257
|
+
if (result.success) {
|
|
1258
|
+
setSelectedAction(0);
|
|
1259
|
+
}
|
|
1260
|
+
} else if (actionId === 'configure') {
|
|
1213
1261
|
setView('configure');
|
|
1214
|
-
} else if (
|
|
1215
|
-
// Uninstall
|
|
1262
|
+
} else if (actionId === 'uninstall') {
|
|
1216
1263
|
const result = uninstallSkill(skill.name);
|
|
1217
1264
|
setMessage({
|
|
1218
1265
|
text: result.success ? `✓ Uninstalled ${skill.name}` : `✗ ${result.message}`,
|
|
@@ -1222,8 +1269,7 @@ function App() {
|
|
|
1222
1269
|
setView('menu');
|
|
1223
1270
|
setSelectedAction(0);
|
|
1224
1271
|
}
|
|
1225
|
-
} else if (
|
|
1226
|
-
// Install
|
|
1272
|
+
} else if (actionId === 'install') {
|
|
1227
1273
|
const result = installSkill(skill.name);
|
|
1228
1274
|
setMessage({
|
|
1229
1275
|
text: result.success ? `✓ Installed ${skill.name}` : `✗ ${result.message}`,
|
|
@@ -1486,6 +1532,13 @@ function App() {
|
|
|
1486
1532
|
)}
|
|
1487
1533
|
</Box>
|
|
1488
1534
|
|
|
1535
|
+
{/* Message */}
|
|
1536
|
+
{message && (
|
|
1537
|
+
<Box paddingX={1} marginTop={1}>
|
|
1538
|
+
<Text color={message.type === 'success' ? colors.success : colors.error}>{message.text}</Text>
|
|
1539
|
+
</Box>
|
|
1540
|
+
)}
|
|
1541
|
+
|
|
1489
1542
|
{/* Footer */}
|
|
1490
1543
|
<Box paddingX={1} marginTop={1}>
|
|
1491
1544
|
<Text color={colors.textDim}>
|
|
@@ -1510,13 +1563,6 @@ function App() {
|
|
|
1510
1563
|
{activeTab === 'agents' && (
|
|
1511
1564
|
<AgentDetails agent={selectedAgent} isFocused={view === 'detail'} selectedAction={selectedAction} />
|
|
1512
1565
|
)}
|
|
1513
|
-
|
|
1514
|
-
{/* Message */}
|
|
1515
|
-
{message && (
|
|
1516
|
-
<Box position="absolute" marginTop={12}>
|
|
1517
|
-
<Text color={message.type === 'success' ? colors.success : colors.error}>{message.text}</Text>
|
|
1518
|
-
</Box>
|
|
1519
|
-
)}
|
|
1520
1566
|
</Box>
|
|
1521
1567
|
);
|
|
1522
1568
|
}
|
package/src/lib/skills.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync,
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
@@ -156,6 +156,129 @@ export function getInstalledSkill(skillName: string): InstalledSkill | null {
|
|
|
156
156
|
return config.skills[skillName] || null;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Check if a skill has an update available
|
|
161
|
+
*/
|
|
162
|
+
export function getSkillUpdateStatus(skillName: string): {
|
|
163
|
+
hasUpdate: boolean;
|
|
164
|
+
installedVersion: string | null;
|
|
165
|
+
bundledVersion: string | null;
|
|
166
|
+
} {
|
|
167
|
+
const installed = getInstalledSkill(skillName);
|
|
168
|
+
const bundledSkillDir = join(BUNDLED_SKILLS_DIR, skillName);
|
|
169
|
+
const manifest = existsSync(bundledSkillDir) ? loadSkillManifest(bundledSkillDir) : null;
|
|
170
|
+
|
|
171
|
+
if (!installed || !manifest) {
|
|
172
|
+
return {
|
|
173
|
+
hasUpdate: false,
|
|
174
|
+
installedVersion: installed?.version || null,
|
|
175
|
+
bundledVersion: manifest?.version || null,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
hasUpdate: manifest.version !== installed.version,
|
|
181
|
+
installedVersion: installed.version,
|
|
182
|
+
bundledVersion: manifest.version,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get all installed skills that have updates available
|
|
188
|
+
*/
|
|
189
|
+
export function getSkillsWithUpdates(): Array<{
|
|
190
|
+
name: string;
|
|
191
|
+
installedVersion: string;
|
|
192
|
+
bundledVersion: string;
|
|
193
|
+
}> {
|
|
194
|
+
const config = loadConfig();
|
|
195
|
+
const updates: Array<{ name: string; installedVersion: string; bundledVersion: string }> = [];
|
|
196
|
+
|
|
197
|
+
for (const skillName of Object.keys(config.skills)) {
|
|
198
|
+
const status = getSkillUpdateStatus(skillName);
|
|
199
|
+
if (status.hasUpdate && status.installedVersion && status.bundledVersion) {
|
|
200
|
+
updates.push({
|
|
201
|
+
name: skillName,
|
|
202
|
+
installedVersion: status.installedVersion,
|
|
203
|
+
bundledVersion: status.bundledVersion,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return updates;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Update a skill to the latest bundled version
|
|
213
|
+
*/
|
|
214
|
+
export function updateSkill(skillName: string): { success: boolean; message: string } {
|
|
215
|
+
const status = getSkillUpdateStatus(skillName);
|
|
216
|
+
|
|
217
|
+
if (!status.installedVersion) {
|
|
218
|
+
return { success: false, message: `Skill '${skillName}' is not installed` };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!status.bundledVersion) {
|
|
222
|
+
return { success: false, message: `Skill '${skillName}' not found in bundled skills` };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (!status.hasUpdate) {
|
|
226
|
+
return { success: false, message: `Skill '${skillName}' is already at latest version (${status.installedVersion})` };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Reinstall the skill (install handles overwriting existing files)
|
|
230
|
+
const result = installSkill(skillName);
|
|
231
|
+
if (result.success) {
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
message: `Updated ${skillName} from ${status.installedVersion} to ${status.bundledVersion}`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Update all installed skills that have newer bundled versions
|
|
243
|
+
*/
|
|
244
|
+
export function updateAllSkills(): {
|
|
245
|
+
updated: Array<{ name: string; from: string; to: string }>;
|
|
246
|
+
failed: Array<{ name: string; error: string }>;
|
|
247
|
+
upToDate: number;
|
|
248
|
+
} {
|
|
249
|
+
const config = loadConfig();
|
|
250
|
+
const result = {
|
|
251
|
+
updated: [] as Array<{ name: string; from: string; to: string }>,
|
|
252
|
+
failed: [] as Array<{ name: string; error: string }>,
|
|
253
|
+
upToDate: 0,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
for (const skillName of Object.keys(config.skills)) {
|
|
257
|
+
const status = getSkillUpdateStatus(skillName);
|
|
258
|
+
|
|
259
|
+
if (!status.hasUpdate) {
|
|
260
|
+
result.upToDate++;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const updateResult = updateSkill(skillName);
|
|
265
|
+
if (updateResult.success) {
|
|
266
|
+
result.updated.push({
|
|
267
|
+
name: skillName,
|
|
268
|
+
from: status.installedVersion!,
|
|
269
|
+
to: status.bundledVersion!,
|
|
270
|
+
});
|
|
271
|
+
} else {
|
|
272
|
+
result.failed.push({
|
|
273
|
+
name: skillName,
|
|
274
|
+
error: updateResult.message,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
159
282
|
/**
|
|
160
283
|
* Install a skill
|
|
161
284
|
*/
|
|
@@ -186,6 +309,34 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
186
309
|
|
|
187
310
|
const skillsPath = getSkillsInstallPath(config.ai_tool);
|
|
188
311
|
const targetSkillDir = join(skillsPath, skillName);
|
|
312
|
+
const commandsPath = getCommandsInstallPath(config.ai_tool);
|
|
313
|
+
|
|
314
|
+
// Check for collisions BEFORE installing (only if not already installed by droid)
|
|
315
|
+
if (!config.skills[skillName]) {
|
|
316
|
+
// Check skill folder collision
|
|
317
|
+
if (existsSync(targetSkillDir)) {
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
message: `Cannot install: skill folder '${skillName}' already exists at ${targetSkillDir}`,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Check command file collisions
|
|
325
|
+
const commandsSource = join(bundledSkillDir, 'commands');
|
|
326
|
+
if (existsSync(commandsSource)) {
|
|
327
|
+
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
328
|
+
for (const file of commandFiles) {
|
|
329
|
+
const targetCommandPath = join(commandsPath, file);
|
|
330
|
+
if (existsSync(targetCommandPath)) {
|
|
331
|
+
const commandName = file.replace('.md', '');
|
|
332
|
+
return {
|
|
333
|
+
success: false,
|
|
334
|
+
message: `Cannot install: command /${commandName} already exists at ${targetCommandPath}`,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
189
340
|
|
|
190
341
|
// Ensure skills directory exists
|
|
191
342
|
if (!existsSync(skillsPath)) {
|
|
@@ -206,11 +357,16 @@ export function installSkill(skillName: string): { success: boolean; message: st
|
|
|
206
357
|
// Copy commands if present
|
|
207
358
|
const commandsSource = join(bundledSkillDir, 'commands');
|
|
208
359
|
if (existsSync(commandsSource)) {
|
|
209
|
-
const commandsPath = getCommandsInstallPath(config.ai_tool);
|
|
210
360
|
if (!existsSync(commandsPath)) {
|
|
211
361
|
mkdirSync(commandsPath, { recursive: true });
|
|
212
362
|
}
|
|
213
|
-
|
|
363
|
+
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
364
|
+
for (const file of commandFiles) {
|
|
365
|
+
const sourcePath = join(commandsSource, file);
|
|
366
|
+
const targetPath = join(commandsPath, file);
|
|
367
|
+
const content = readFileSync(sourcePath, 'utf-8');
|
|
368
|
+
writeFileSync(targetPath, content);
|
|
369
|
+
}
|
|
214
370
|
}
|
|
215
371
|
|
|
216
372
|
// Update config
|
|
@@ -249,7 +405,7 @@ export function uninstallSkill(skillName: string): { success: boolean; message:
|
|
|
249
405
|
const commandsSource = join(bundledSkillDir, 'commands');
|
|
250
406
|
if (existsSync(commandsSource)) {
|
|
251
407
|
const commandsPath = getCommandsInstallPath(config.ai_tool);
|
|
252
|
-
const commandFiles = readdirSync(commandsSource);
|
|
408
|
+
const commandFiles = readdirSync(commandsSource).filter(f => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
|
|
253
409
|
for (const file of commandFiles) {
|
|
254
410
|
const commandPath = join(commandsPath, file);
|
|
255
411
|
if (existsSync(commandPath)) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: comments
|
|
3
|
-
description:
|
|
3
|
+
description: >-
|
|
4
|
+
Enable inline code conversations using @droid markers. Leave comments like
|
|
5
|
+
"> @droid should we cache this?" and get responses inline. Use /comments check
|
|
6
|
+
to scan for markers and address them, /comments cleanup to remove resolved threads.
|
|
7
|
+
Ideal for code review notes, quick questions, and async collaboration in any file.
|
|
4
8
|
globs:
|
|
5
9
|
- "**/*"
|
|
6
10
|
alwaysApply: false
|
|
@@ -28,10 +32,29 @@ The AI will respond with `> @{user_mention}` (configured in droid setup, e.g., `
|
|
|
28
32
|
|
|
29
33
|
## Tag Convention
|
|
30
34
|
|
|
31
|
-
| Who |
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
+
| Tag | Who's Speaking | Purpose |
|
|
36
|
+
|-----|----------------|---------|
|
|
37
|
+
| `@droid` | User | User leaving notes/questions for AI |
|
|
38
|
+
| `@{user}` (e.g., `@fry`) | AI | AI leaving responses/questions for user |
|
|
39
|
+
|
|
40
|
+
**The pattern:** The tag indicates who you're addressing, not who's writing. When you write `> @droid`, you're talking TO the AI. When the AI writes `> @fry`, it's talking TO you.
|
|
41
|
+
|
|
42
|
+
## When NOT to Use
|
|
43
|
+
|
|
44
|
+
- **Multi-file refactors** → describe in a task or issue, not scattered comments
|
|
45
|
+
- **Formal code review** → use PR review process
|
|
46
|
+
- **Questions needing persistent answers** → capture in dedicated documentation
|
|
47
|
+
|
|
48
|
+
Inline comments shine for localized questions and actions within a file. For longer back-and-forth discussions in documents, they work great too - just be mindful that very long threads may be better moved to dedicated docs.
|
|
49
|
+
|
|
50
|
+
## Context Gathering
|
|
51
|
+
|
|
52
|
+
When you find a `@droid` marker:
|
|
53
|
+
|
|
54
|
+
1. **Read surrounding context** - 20-30 lines around the comment
|
|
55
|
+
2. **Check git status** - Prioritize files with uncommitted changes
|
|
56
|
+
3. **Look for related markers** - Multiple comments in same file may be connected
|
|
57
|
+
4. **Consider file type** - Match response format to the file (code comments vs markdown)
|
|
35
58
|
|
|
36
59
|
## Commands
|
|
37
60
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
name: comments
|
|
2
|
-
description:
|
|
3
|
-
|
|
2
|
+
description: >-
|
|
3
|
+
Enable inline code conversations using @droid markers. Leave comments like
|
|
4
|
+
"> @droid should we cache this?" and get responses inline. Use /comments check
|
|
5
|
+
to scan for markers and address them, /comments cleanup to remove resolved threads.
|
|
6
|
+
Ideal for code review notes, quick questions, and async collaboration in any file.
|
|
7
|
+
version: 0.2.0
|
|
4
8
|
status: beta
|
|
5
9
|
dependencies: []
|
|
6
10
|
provides_output: false
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Check and respond to inline @droid comments in code and docs
|
|
3
|
+
argument-hint: [check|cleanup] [path]
|
|
4
|
+
allowed-tools: Read, Edit, Grep, Glob, Bash(git diff:*), Bash(git status:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# /comments - Check and respond to inline comments
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
Entry point for inline comment workflows. See the **comments skill** for full behavior.
|
|
10
|
+
|
|
11
|
+
## Arguments
|
|
12
|
+
|
|
13
|
+
$ARGUMENTS
|
|
4
14
|
|
|
5
15
|
## Usage
|
|
6
16
|
|
|
@@ -11,38 +21,9 @@ Check for `> @droid` comments (and configured aliases like `@claude`) in files a
|
|
|
11
21
|
/comments cleanup # Remove resolved comment threads
|
|
12
22
|
```
|
|
13
23
|
|
|
14
|
-
## Arguments
|
|
15
|
-
|
|
16
|
-
$ACTION - The action to perform: check (default) or cleanup
|
|
17
|
-
$PATH - Optional path to scope the search
|
|
18
|
-
|
|
19
24
|
## Behavior
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- If it's an **action request** (e.g., "add error handling", "refactor this"):
|
|
26
|
-
- Execute the requested action
|
|
27
|
-
- Remove the comment (unless preserve_comments is true)
|
|
28
|
-
- If it's a **question** (e.g., "what do you think?", "is this approach good?"):
|
|
29
|
-
- Respond with `> @{user_mention}` on a new line below
|
|
30
|
-
- Keep the original comment for context
|
|
31
|
-
3. **Check git diff first** - If there's uncommitted changes, prioritize checking those files
|
|
32
|
-
|
|
33
|
-
## Response Format
|
|
34
|
-
|
|
35
|
-
When responding to questions, use the configured user tag:
|
|
36
|
-
|
|
37
|
-
```markdown
|
|
38
|
-
> @droid Should we use async/await here?
|
|
39
|
-
|
|
40
|
-
> @fry Yes, async/await would be cleaner here because...
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Configuration
|
|
44
|
-
|
|
45
|
-
Check `~/.droid/skills/comments/overrides.yaml` for:
|
|
46
|
-
- `user_mention` - Your @mention for responses
|
|
47
|
-
- `ai_mentions` - Additional mentions to recognize (e.g., `@claude`)
|
|
48
|
-
- `preserve_comments` - Keep or remove addressed comments
|
|
26
|
+
Refer to the comments skill for:
|
|
27
|
+
- **Check**: How to find markers, determine intent (action vs question), respond appropriately
|
|
28
|
+
- **Cleanup**: How to identify resolved threads and summarize decisions
|
|
29
|
+
- **Configuration**: `user_mention`, `ai_mentions`, `preserve_comments` settings
|