@lifeaitools/rdc-skills 0.24.16 → 0.24.18

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rdc",
3
- "version": "0.24.16",
3
+ "version": "0.24.18",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code — plan, build, review, overnight unattended builds with work-item tracking and TDD enforcement.",
5
5
  "author": {
6
6
  "name": "LIFEAI",
package/README.md CHANGED
@@ -108,13 +108,23 @@ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
108
108
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdc_skill_list","arguments":{}}}'
109
109
  ```
110
110
 
111
- Fetch a skill body for a specific caller surface:
111
+ Search by natural-language intent:
112
112
 
113
113
  ```bash
114
114
  curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
115
115
  -H 'Content-Type: application/json' \
116
116
  -H 'Accept: application/json, text/event-stream' \
117
- -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"build","variant":"cli"}}}'
117
+ -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"rdc_skill_search","arguments":{"query":"turn this article into social posts"}}}'
118
+ ```
119
+
120
+ Fetch a skill body for a specific caller surface. `name` accepts either the
121
+ catalog `name` (`build`) or the visible slash form (`rdc:build`):
122
+
123
+ ```bash
124
+ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
125
+ -H 'Content-Type: application/json' \
126
+ -H 'Accept: application/json, text/event-stream' \
127
+ -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli"}}}'
118
128
  ```
119
129
 
120
130
  Use `variant:"cli"` for Claude Code/Codex/local terminal instructions. Use
@@ -46,7 +46,7 @@ import {
46
46
  getSkill,
47
47
  getSkillBody,
48
48
  getCloudOverride,
49
- skillNames,
49
+ resolveSkillName,
50
50
  searchSkills,
51
51
  } from '../lib/catalog.mjs';
52
52
  import { toCloudBody } from '../lib/cloud-rewrite.mjs';
@@ -161,21 +161,22 @@ function buildMcpServer() {
161
161
  description:
162
162
  "Get a skill's SKILL.md body rendered for the caller. variant 'cli' returns the body unchanged; 'cloud' rewrites local-shell/clauth-daemon steps for the claude.ai web client. Omit variant to use auto-detection (defaults to cloud).",
163
163
  inputSchema: {
164
- name: z.string().describe('Skill name (e.g. "deploy", "build"). See rdc_skill_list.'),
164
+ name: z.string().describe('Skill name or slash form (e.g. "deploy", "rdc:build", "rdc:brochurify", "lifeai-brochure-author"). See rdc_skill_list.'),
165
165
  variant: z.enum(['cli', 'cloud']).optional().describe('Force the rendered variant; overrides caller detection.'),
166
166
  },
167
167
  },
168
168
  async ({ name, variant }) => {
169
- const valid = skillNames();
170
- if (!valid.includes(name)) {
169
+ const resolvedName = resolveSkillName(name);
170
+ if (!resolvedName) {
171
+ const valid = listSkills().map((s) => `${s.name} (${s.slash})`);
171
172
  return textResult(
172
173
  `Unknown skill '${name}'. Valid skills (${valid.length}): ${valid.join(', ')}`,
173
174
  );
174
175
  }
175
176
  const resolved = variant || lastDetectedVariant || 'cloud';
176
- const rendered = renderSkill(name, resolved);
177
+ const rendered = renderSkill(resolvedName, resolved);
177
178
  if (!rendered) {
178
- return textResult(`Skill '${name}' exists in the catalog but has no SKILL.md body on disk.`);
179
+ return textResult(`Skill '${resolvedName}' exists in the catalog but has no SKILL.md body on disk.`);
179
180
  }
180
181
  return textResult(`${rendered.header}\n\n${rendered.body}`);
181
182
  },
package/commands/help.md CHANGED
@@ -59,7 +59,12 @@ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
59
59
  curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
60
60
  -H 'Content-Type: application/json' \
61
61
  -H 'Accept: application/json, text/event-stream' \
62
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"build","variant":"cli"}}}'
62
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"rdc_skill_search","arguments":{"query":"turn this article into social posts"}}}'
63
+
64
+ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
65
+ -H 'Content-Type: application/json' \
66
+ -H 'Accept: application/json, text/event-stream' \
67
+ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli"}}}'
63
68
  ```
64
69
 
65
70
  ## Hard Rules
package/git-sha.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "sha": "36691cfc1928d1024db4b6e4c40b4a6c13668ac6"
2
+ "sha": "2a43eed5f23588339a213ea05dc96cdcbce3b4b6"
3
3
  }
package/lib/catalog.mjs CHANGED
@@ -115,7 +115,8 @@ export function listSkills() {
115
115
 
116
116
  /** Full catalog row for one skill, or null if unknown. */
117
117
  export function getSkill(name) {
118
- return getCatalog().find((s) => s.name === name) || null;
118
+ const resolved = resolveSkillName(name);
119
+ return resolved ? getCatalog().find((s) => s.name === resolved) || null : null;
119
120
  }
120
121
 
121
122
  /** List of valid skill names (for error messages). */
@@ -123,12 +124,35 @@ export function skillNames() {
123
124
  return getCatalog().map((s) => s.name);
124
125
  }
125
126
 
127
+ /** Return the canonical skill directory name for a bare name, slash, or usage token. */
128
+ export function resolveSkillName(input) {
129
+ const raw = String(input || '').trim();
130
+ if (!raw) return null;
131
+ const token = raw.split(/\s+/)[0];
132
+ const catalog = getCatalog();
133
+ const direct = catalog.find((s) => s.name === token || s.slash === token);
134
+ if (direct) return direct.name;
135
+ if (token.startsWith('/')) {
136
+ const withoutSlash = token.slice(1);
137
+ const slashMatch = catalog.find((s) => s.slash === withoutSlash);
138
+ if (slashMatch) return slashMatch.name;
139
+ }
140
+ if (token.startsWith('rdc:')) {
141
+ const bare = token.slice(4);
142
+ const bareMatch = catalog.find((s) => s.name === bare || s.name === `rdc-${bare}`);
143
+ if (bareMatch) return bareMatch.name;
144
+ }
145
+ return null;
146
+ }
147
+
126
148
  /**
127
149
  * Read the raw SKILL.md body (frontmatter stripped) for a skill, live from disk.
128
150
  * Returns null if the file does not exist.
129
151
  */
130
152
  export function getSkillBody(name) {
131
- const skillMd = path.join(SKILLS_DIR, name, 'SKILL.md');
153
+ const resolved = resolveSkillName(name);
154
+ if (!resolved) return null;
155
+ const skillMd = path.join(SKILLS_DIR, resolved, 'SKILL.md');
132
156
  if (!fs.existsSync(skillMd)) return null;
133
157
  const raw = fs.readFileSync(skillMd, 'utf8').replace(/\r\n/g, '\n');
134
158
  // Strip the leading frontmatter block if present.
@@ -138,7 +162,9 @@ export function getSkillBody(name) {
138
162
 
139
163
  /** Absolute path to a hand-tuned cloud override, or null if absent. */
140
164
  export function cloudOverridePath(name) {
141
- const p = path.join(SKILLS_DIR, name, 'SKILL.cloud.md');
165
+ const resolved = resolveSkillName(name);
166
+ if (!resolved) return null;
167
+ const p = path.join(SKILLS_DIR, resolved, 'SKILL.cloud.md');
142
168
  return fs.existsSync(p) ? p : null;
143
169
  }
144
170
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.24.16",
3
+ "version": "0.24.18",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -101,11 +101,17 @@ RDC SKILLS — manifest: .claude-plugin/plugin.json @ v{version}
101
101
  -H 'Accept: application/json, text/event-stream' \
102
102
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"rdc_skill_list","arguments":{}}}'
103
103
 
104
+ Search by intent:
105
+ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
106
+ -H 'Content-Type: application/json' \
107
+ -H 'Accept: application/json, text/event-stream' \
108
+ -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"rdc_skill_search","arguments":{"query":"turn this article into social posts"}}}'
109
+
104
110
  Fetch a skill:
105
111
  curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
106
112
  -H 'Content-Type: application/json' \
107
113
  -H 'Accept: application/json, text/event-stream' \
108
- -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"build","variant":"cli"}}}'
114
+ -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli"}}}'
109
115
 
110
116
  ## Hard rules
111
117
 
@@ -21,7 +21,10 @@ for (const [name, text] of Object.entries(docs)) {
21
21
  assert.match(text, /https:\/\/rdc-skills\.regendevcorp\.com\/mcp/, `${name} must expose production MCP endpoint`);
22
22
  assert.match(text, /Accept: application\/json, text\/event-stream/, `${name} must show Streamable HTTP Accept header`);
23
23
  assert.match(text, /rdc_skill_list/, `${name} must mention rdc_skill_list`);
24
+ assert.match(text, /rdc_skill_search/, `${name} must mention rdc_skill_search`);
24
25
  assert.match(text, /rdc_skill_get/, `${name} must mention rdc_skill_get`);
26
+ assert.match(text, /turn this article into social posts/, `${name} must include a natural-language search example`);
27
+ assert.match(text, /"name":"rdc:build"/, `${name} must show rdc_skill_get accepts visible slash names`);
25
28
  assert.doesNotMatch(text, /https:\/\/rdc-skills\.dev\.regendevcorp\.com\/mcp/, `${name} must not point callers at dev MCP`);
26
29
  }
27
30
 
@@ -32,6 +32,7 @@ import {
32
32
  getSkillBody,
33
33
  getCloudOverride,
34
34
  searchSkills,
35
+ resolveSkillName,
35
36
  } from '../lib/catalog.mjs';
36
37
  import { toCloudBody } from '../lib/cloud-rewrite.mjs';
37
38
 
@@ -96,6 +97,11 @@ function unitTests() {
96
97
  check('unit: getSkill(nonexistent) is null', getSkill('___nope___') === null);
97
98
  check('unit: search ranks exact name first', searchSkills('deploy')[0]?.name === 'deploy');
98
99
  check('unit: empty search returns []', searchSkills('').length === 0);
100
+ check('unit: resolves bare skill name', resolveSkillName('build') === 'build');
101
+ check('unit: resolves rdc slash skill name', resolveSkillName('rdc:build') === 'build');
102
+ check('unit: resolves leading slash command form', resolveSkillName('/rdc:build') === 'build');
103
+ check('unit: resolves specialist slash alias', resolveSkillName('rdc:brochurify') === 'rdc-brochurify');
104
+ check('unit: resolves non-rdc specialist name', resolveSkillName('lifeai-brochure-author') === 'lifeai-brochure-author');
99
105
 
100
106
  // Searchability gate — the dimension the first "100% coverage" missed. Every
101
107
  // catalog entry MUST carry triggers + usage (frontmatter or skills_meta) so
@@ -175,6 +181,11 @@ async function sweep(url, label, { compareSource }) {
175
181
  const unk = await mcp(url, { jsonrpc: '2.0', id: 20, method: 'tools/call', params: { name: 'rdc_skill_get', arguments: { name: '___nope___' } } });
176
182
  check(`${label}: unknown skill returns helpful error`, /unknown skill/i.test(callText(unk.json)));
177
183
 
184
+ // Alias path: callers naturally copy the slash form from rdc_skill_list.
185
+ const aliasGet = await mcp(url, { jsonrpc: '2.0', id: 22, method: 'tools/call', params: { name: 'rdc_skill_get', arguments: { name: 'rdc:brochurify', variant: 'cli' } } });
186
+ const aliasBody = strip(callText(aliasGet.json));
187
+ check(`${label}: rdc_skill_get accepts slash aliases`, /rdc:brochurify Orchestrator/.test(aliasBody));
188
+
178
189
  // search
179
190
  const se = await mcp(url, { jsonrpc: '2.0', id: 21, method: 'tools/call', params: { name: 'rdc_skill_search', arguments: { query: 'deploy' } } });
180
191
  let sr; try { sr = JSON.parse(callText(se.json)); } catch { sr = { results: [] }; }