claudecode-omc 5.6.4 → 5.6.5
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/.local/skills/prompt-optimizer/SKILL.md +23 -4
- package/bundled/manifest.json +1 -1
- package/package.json +1 -1
- package/src/cli/index.js +1 -0
- package/src/cli/setup.js +9 -0
- package/src/cli/skill-index.js +209 -0
- package/src/cli/skill.js +4 -0
|
@@ -119,7 +119,25 @@ Many users run OMC, superpowers, or their own custom installs where the same
|
|
|
119
119
|
capability lives under a **different skill name**. Recommending a phantom skill
|
|
120
120
|
wastes the user's time.
|
|
121
121
|
|
|
122
|
-
**
|
|
122
|
+
**Preferred check (live index):** before listing a skill in Section 2 / Section 3,
|
|
123
|
+
read the auto-generated catalog:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
cat ~/.claude/skills/_index.md
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This file is regenerated by `omc-manage setup` (or manually via
|
|
130
|
+
`omc-manage skill index`) and contains one row per installed skill with its
|
|
131
|
+
`name | description`. Grep it for keywords from the user's intent:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
grep -i "test\|tdd\|verification" ~/.claude/skills/_index.md
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
If a recommended skill does **not** appear in `_index.md`, replace it with a
|
|
138
|
+
matching installed skill or fall back to the alias table below.
|
|
139
|
+
|
|
140
|
+
**Fallback rule (when index is missing or empty):** mentally check
|
|
123
141
|
*"Is this skill likely installed locally?"* If unsure, follow the alias table
|
|
124
142
|
or add a verification note.
|
|
125
143
|
|
|
@@ -145,9 +163,10 @@ referencing them in Section 3, write:
|
|
|
145
163
|
|
|
146
164
|
> If you don't have `<skill-name>` installed, recommend the universal `coding-standards` skill instead, or use the conventions in the project's `CLAUDE.md`.
|
|
147
165
|
|
|
148
|
-
**Discovery shortcut for the user:** if
|
|
149
|
-
|
|
150
|
-
|
|
166
|
+
**Discovery shortcut for the user:** if `_index.md` is missing entirely, suggest
|
|
167
|
+
they run `omc-manage skill index --scope user` (or just `omc-manage setup`,
|
|
168
|
+
which regenerates it as a side effect). For a quality audit, use
|
|
169
|
+
`/oh-my-claudecode:skill-stocktake`.
|
|
151
170
|
|
|
152
171
|
### Phase 1: Intent Detection
|
|
153
172
|
|
package/bundled/manifest.json
CHANGED
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -31,6 +31,7 @@ function showHelp() {
|
|
|
31
31
|
console.log(' evaluate [name] — quality score (Anthropic-aligned)');
|
|
32
32
|
console.log(' compare [--threshold N] — cross-source overlap analysis');
|
|
33
33
|
console.log(' recommend [--apply] — preference recommendations');
|
|
34
|
+
console.log(' index [--scope user|project] — generate _index.md catalog');
|
|
34
35
|
console.log(' help Show this help');
|
|
35
36
|
console.log('');
|
|
36
37
|
console.log('Artifact types: skills, agents, hooks, commands, guidelines, claude-md, settings, hud');
|
package/src/cli/setup.js
CHANGED
|
@@ -505,6 +505,15 @@ async function setup(args, flags = {}) {
|
|
|
505
505
|
if (result.conflicts > 0) {
|
|
506
506
|
console.log(` resolved ${result.conflicts} conflicts`);
|
|
507
507
|
}
|
|
508
|
+
|
|
509
|
+
if (artifactType === 'skills' && !flags.dryRun) {
|
|
510
|
+
try {
|
|
511
|
+
const { buildAndWriteIndex } = require('./skill-index');
|
|
512
|
+
await buildAndWriteIndex(installTarget, { quiet: false });
|
|
513
|
+
} catch (err) {
|
|
514
|
+
console.log(` warning: skill index generation failed: ${err.message}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
508
517
|
}
|
|
509
518
|
|
|
510
519
|
if (!flags.dryRun && scope === 'user') {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const fsp = require('fs/promises');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { getProjectRoot } = require('../config/paths');
|
|
7
|
+
|
|
8
|
+
const INDEX_FILENAME = '_index.md';
|
|
9
|
+
const MAX_DESCRIPTION_CHARS = 280;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse YAML frontmatter from a SKILL.md, returning { name, description }.
|
|
13
|
+
*
|
|
14
|
+
* Handles:
|
|
15
|
+
* - `key: value` single-line
|
|
16
|
+
* - `key: >-` or `key: |` folded/literal block scalars (collects indented
|
|
17
|
+
* continuation lines, joined with spaces)
|
|
18
|
+
* - quoted values: stripped of surrounding quotes
|
|
19
|
+
*
|
|
20
|
+
* Returns null if frontmatter is missing or name field is absent.
|
|
21
|
+
*/
|
|
22
|
+
function parseFrontmatter(skillFile) {
|
|
23
|
+
if (!fs.existsSync(skillFile)) return null;
|
|
24
|
+
|
|
25
|
+
const content = fs.readFileSync(skillFile, 'utf8');
|
|
26
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
27
|
+
if (!match) return null;
|
|
28
|
+
|
|
29
|
+
const lines = match[1].split('\n');
|
|
30
|
+
const meta = {};
|
|
31
|
+
let currentKey = null;
|
|
32
|
+
let blockMode = false; // when value is `>-` or `|`
|
|
33
|
+
|
|
34
|
+
for (const raw of lines) {
|
|
35
|
+
if (!raw.trim()) continue;
|
|
36
|
+
|
|
37
|
+
// top-level key: detect by leading non-whitespace + colon
|
|
38
|
+
const keyMatch = raw.match(/^([a-zA-Z_-][\w-]*)\s*:\s*(.*)$/);
|
|
39
|
+
const isIndented = /^\s/.test(raw);
|
|
40
|
+
|
|
41
|
+
if (keyMatch && !isIndented) {
|
|
42
|
+
currentKey = keyMatch[1];
|
|
43
|
+
const valueRaw = keyMatch[2].trim();
|
|
44
|
+
if (valueRaw === '>-' || valueRaw === '>' || valueRaw === '|' || valueRaw === '|-') {
|
|
45
|
+
meta[currentKey] = '';
|
|
46
|
+
blockMode = true;
|
|
47
|
+
} else {
|
|
48
|
+
meta[currentKey] = stripQuotes(valueRaw);
|
|
49
|
+
blockMode = false;
|
|
50
|
+
}
|
|
51
|
+
} else if (currentKey && (blockMode || isIndented)) {
|
|
52
|
+
// continuation line
|
|
53
|
+
const continued = raw.trim();
|
|
54
|
+
if (continued) {
|
|
55
|
+
meta[currentKey] = (meta[currentKey] ? meta[currentKey] + ' ' : '') + continued;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!meta.name) return null;
|
|
61
|
+
return meta;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function stripQuotes(s) {
|
|
65
|
+
if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
|
|
66
|
+
return s.slice(1, -1);
|
|
67
|
+
}
|
|
68
|
+
return s;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function truncateDescription(desc) {
|
|
72
|
+
if (!desc) return '';
|
|
73
|
+
// Collapse whitespace
|
|
74
|
+
const flat = desc.replace(/\s+/g, ' ').trim();
|
|
75
|
+
if (flat.length <= MAX_DESCRIPTION_CHARS) return flat;
|
|
76
|
+
// Truncate at sentence boundary if possible
|
|
77
|
+
const truncated = flat.slice(0, MAX_DESCRIPTION_CHARS);
|
|
78
|
+
const lastPeriod = truncated.lastIndexOf('.');
|
|
79
|
+
if (lastPeriod > MAX_DESCRIPTION_CHARS * 0.6) {
|
|
80
|
+
return truncated.slice(0, lastPeriod + 1);
|
|
81
|
+
}
|
|
82
|
+
return truncated + '…';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function escapeTableCell(s) {
|
|
86
|
+
return (s || '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Scan a directory of skills (each subdir contains SKILL.md) and return
|
|
91
|
+
* sorted list of { name, description }.
|
|
92
|
+
*/
|
|
93
|
+
function buildEntries(skillsDir) {
|
|
94
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
95
|
+
|
|
96
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
97
|
+
const items = [];
|
|
98
|
+
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (!entry.isDirectory()) continue;
|
|
101
|
+
if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
|
|
102
|
+
|
|
103
|
+
const skillFile = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
104
|
+
const meta = parseFrontmatter(skillFile);
|
|
105
|
+
if (!meta) {
|
|
106
|
+
process.stderr.write(` skipped: ${entry.name} (no frontmatter)\n`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
items.push({
|
|
110
|
+
name: meta.name,
|
|
111
|
+
description: truncateDescription(meta.description),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
116
|
+
return items;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render entries as a Markdown table with a header preamble.
|
|
121
|
+
*/
|
|
122
|
+
function renderIndex(skillsDir, entries) {
|
|
123
|
+
const lines = [];
|
|
124
|
+
lines.push('# Skill Index');
|
|
125
|
+
lines.push('');
|
|
126
|
+
lines.push(`Auto-generated by \`omc-manage skill index\`. Do not edit by hand —`);
|
|
127
|
+
lines.push(`rerun \`omc-manage skill index\` after adding or removing skills.`);
|
|
128
|
+
lines.push('');
|
|
129
|
+
lines.push(`Source directory: \`${skillsDir.replace(os.homedir(), '~')}\``);
|
|
130
|
+
lines.push(`Generated: ${new Date().toISOString()}`);
|
|
131
|
+
lines.push(`Entries: ${entries.length}`);
|
|
132
|
+
lines.push('');
|
|
133
|
+
lines.push('## Usage for LLMs');
|
|
134
|
+
lines.push('');
|
|
135
|
+
lines.push('`grep -i "<keyword>" _index.md` to find skills matching an intent.');
|
|
136
|
+
lines.push('Each row is `name | description` — both fields searchable.');
|
|
137
|
+
lines.push('');
|
|
138
|
+
lines.push('| Skill | Description |');
|
|
139
|
+
lines.push('|---|---|');
|
|
140
|
+
for (const e of entries) {
|
|
141
|
+
lines.push(`| \`${escapeTableCell(e.name)}\` | ${escapeTableCell(e.description)} |`);
|
|
142
|
+
}
|
|
143
|
+
lines.push('');
|
|
144
|
+
return lines.join('\n');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build and write the index. Returns { path, count }.
|
|
149
|
+
*/
|
|
150
|
+
async function buildAndWriteIndex(skillsDir, { quiet = false } = {}) {
|
|
151
|
+
const entries = buildEntries(skillsDir);
|
|
152
|
+
const indexPath = path.join(skillsDir, INDEX_FILENAME);
|
|
153
|
+
await fsp.writeFile(indexPath, renderIndex(skillsDir, entries), 'utf8');
|
|
154
|
+
if (!quiet) {
|
|
155
|
+
console.log(` wrote ${indexPath.replace(os.homedir(), '~')} (${entries.length} entries)`);
|
|
156
|
+
}
|
|
157
|
+
return { path: indexPath, count: entries.length };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the skills install directory for the given scope.
|
|
162
|
+
*/
|
|
163
|
+
function resolveSkillsDir(scope) {
|
|
164
|
+
if (scope === 'project') {
|
|
165
|
+
const root = getProjectRoot();
|
|
166
|
+
return path.join(root, '.claude', 'skills');
|
|
167
|
+
}
|
|
168
|
+
// default: user scope
|
|
169
|
+
return path.join(os.homedir(), '.claude', 'skills');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* CLI entrypoint: `omc-manage skill index [--scope user|project] [--quiet]`
|
|
174
|
+
*/
|
|
175
|
+
async function indexCommand(args, flags = {}) {
|
|
176
|
+
const scope = flags.scope || 'user';
|
|
177
|
+
if (scope !== 'user' && scope !== 'project') {
|
|
178
|
+
console.error(`Error: invalid --scope "${scope}" (expected user or project)`);
|
|
179
|
+
process.exitCode = 1;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const skillsDir = resolveSkillsDir(scope);
|
|
184
|
+
if (!fs.existsSync(skillsDir)) {
|
|
185
|
+
console.error(`Error: skills directory not found: ${skillsDir}`);
|
|
186
|
+
console.error('Run `omc-manage setup` first to install skills.');
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log(`omc-manage skill index`);
|
|
192
|
+
console.log(`======================`);
|
|
193
|
+
console.log(`Scope: ${scope}`);
|
|
194
|
+
console.log(`Target: ${skillsDir.replace(os.homedir(), '~')}`);
|
|
195
|
+
console.log('');
|
|
196
|
+
|
|
197
|
+
const result = await buildAndWriteIndex(skillsDir, { quiet: flags.quiet });
|
|
198
|
+
|
|
199
|
+
console.log('');
|
|
200
|
+
console.log(`Indexed ${result.count} skills.`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
indexCommand,
|
|
205
|
+
buildAndWriteIndex,
|
|
206
|
+
resolveSkillsDir,
|
|
207
|
+
parseFrontmatter, // exported for tests
|
|
208
|
+
truncateDescription, // exported for tests
|
|
209
|
+
};
|
package/src/cli/skill.js
CHANGED
|
@@ -271,6 +271,10 @@ async function skill(args, flags = {}) {
|
|
|
271
271
|
if (cmd === 'recommend') {
|
|
272
272
|
return recommend(args, flags);
|
|
273
273
|
}
|
|
274
|
+
if (cmd === 'index') {
|
|
275
|
+
const { indexCommand } = require('./skill-index');
|
|
276
|
+
return indexCommand(args.slice(1), flags);
|
|
277
|
+
}
|
|
274
278
|
|
|
275
279
|
// Fall through to artifact subcommands (list, prefer, conflicts)
|
|
276
280
|
flags.type = 'skills';
|