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.
@@ -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
- **Rule:** before listing a skill in Section 2 / Section 3, mentally check:
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 you suspect the skill catalog is sparse,
149
- suggest they run `/oh-my-claudecode:skill-stocktake` (OMC) or `ls ~/.claude/skills/`
150
- to enumerate what they actually have.
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
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "bundledAt": "2026-05-13T02:21:48Z",
2
+ "bundledAt": "2026-05-13T02:46:15Z",
3
3
  "sources": {
4
4
  "anthropic-skills": { "artifacts": 2 },
5
5
  "ecc": { "artifacts": 131 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudecode-omc",
3
- "version": "5.6.4",
3
+ "version": "5.6.5",
4
4
  "description": "Claude Code harness — best-practice skills, agents, hooks, and configs from multiple sources",
5
5
  "bin": {
6
6
  "omc-manage": "bin/omc-manage.js"
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';