@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.
Files changed (41) hide show
  1. package/.claude/CLAUDE.md +4 -4
  2. package/.github/workflows/changeset-check.yml +40 -11
  3. package/.github/workflows/release.yml +0 -1
  4. package/CHANGELOG.md +33 -0
  5. package/bun.lock +2 -182
  6. package/dist/commands/tui.d.ts.map +1 -1
  7. package/dist/commands/tui.js +55 -16
  8. package/dist/commands/tui.js.map +1 -1
  9. package/dist/lib/skills.d.ts +38 -0
  10. package/dist/lib/skills.d.ts.map +1 -1
  11. package/dist/lib/skills.js +130 -4
  12. package/dist/lib/skills.js.map +1 -1
  13. package/dist/skills/comments/SKILL.md +28 -5
  14. package/dist/skills/comments/SKILL.yaml +6 -2
  15. package/dist/skills/comments/commands/comments.md +15 -34
  16. package/dist/skills/project/SKILL.md +93 -0
  17. package/dist/skills/project/SKILL.yaml +35 -0
  18. package/dist/skills/project/commands/README.md +26 -0
  19. package/dist/skills/project/commands/project.md +39 -0
  20. package/dist/skills/project/references/changelog.md +70 -0
  21. package/dist/skills/project/references/creating.md +58 -0
  22. package/dist/skills/project/references/loading.md +40 -0
  23. package/dist/skills/project/references/templates.md +124 -0
  24. package/dist/skills/project/references/updating.md +64 -0
  25. package/dist/skills/project/references/versioning.md +36 -0
  26. package/package.json +1 -3
  27. package/src/commands/tui.tsx +65 -19
  28. package/src/lib/skills.ts +160 -4
  29. package/src/skills/comments/SKILL.md +28 -5
  30. package/src/skills/comments/SKILL.yaml +6 -2
  31. package/src/skills/comments/commands/comments.md +15 -34
  32. package/src/skills/project/SKILL.md +93 -0
  33. package/src/skills/project/SKILL.yaml +35 -0
  34. package/src/skills/project/commands/README.md +26 -0
  35. package/src/skills/project/commands/project.md +39 -0
  36. package/src/skills/project/references/changelog.md +70 -0
  37. package/src/skills/project/references/creating.md +58 -0
  38. package/src/skills/project/references/loading.md +40 -0
  39. package/src/skills/project/references/templates.md +124 -0
  40. package/src/skills/project/references/updating.md +64 -0
  41. 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.4.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",
@@ -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
- const headerMatch = lines[0]?.match(/^#\s+\/(\S+)\s*-?\s*(.*)/);
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
- { id: 'configure', label: 'Configure', variant: 'primary' },
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
- maxActions = installed ? 2 : 1; // View, Configure, Uninstall or View, Install
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
- // Actions: installed = [View, Configure, Uninstall], not installed = [View, Install]
1203
- if (selectedAction === 0) {
1204
- // View
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 (installed && selectedAction === 1) {
1212
- // Configure
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 (installed && selectedAction === 2) {
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 (!installed && selectedAction === 1) {
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, cpSync, rmSync } from 'fs';
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
- cpSync(commandsSource, commandsPath, { recursive: true });
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: Inline conversation in any file via @droid/@user markers
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 | Tag |
32
- |-----|-----|
33
- | AI | `@droid` (+ any configured aliases) |
34
- | User | Configured (e.g., `@fry`) |
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: Inline conversation in any file via @droid/@user markers
3
- version: 0.1.0
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
- Check for `> @droid` comments (and configured aliases like `@claude`) in files and address them.
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
- When you find a `> @droid` comment (or configured alias):
22
-
23
- 1. **Read the context** - Understand what the comment is asking by reading surrounding code/text
24
- 2. **Determine intent**:
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