@lifeaitools/rdc-skills 0.24.25 → 0.24.27
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-plugin/plugin.json +1 -1
- package/README.md +16 -0
- package/bin/rdc-skills-mcp.mjs +29 -6
- package/commands/help.md +6 -0
- package/git-sha.json +1 -1
- package/lib/catalog.mjs +31 -2
- package/package.json +1 -1
- package/skills/help/SKILL.md +7 -0
- package/tests/curl-surface.test.mjs +25 -0
- package/tests/help-surface.test.mjs +4 -0
- package/tests/mcp.test.mjs +23 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rdc",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.27",
|
|
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
|
@@ -124,6 +124,12 @@ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
|
124
124
|
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdc_skill_list","arguments":{}}}'
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
`rdc_skill_list` returns the full caller-facing catalog for each skill:
|
|
128
|
+
`name`, `slash`, accepted `aliases`, `category`, `summary`, `when_to_use`,
|
|
129
|
+
`usage`, `args`, `requires`, `produces`, `follows`, `leads_to`,
|
|
130
|
+
`default_model`, `sandbox_aware`, `output_contract`, `enabled_default`,
|
|
131
|
+
`codeflow_required`, and supported `variants` (`cli`, `cloud`).
|
|
132
|
+
|
|
127
133
|
Search by natural-language intent:
|
|
128
134
|
|
|
129
135
|
```bash
|
|
@@ -143,6 +149,16 @@ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
|
143
149
|
-d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli"}}}'
|
|
144
150
|
```
|
|
145
151
|
|
|
152
|
+
For direct API/curl clients that want metadata and rendered body in one parsed
|
|
153
|
+
payload, add `format:"json"`:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
157
|
+
-H 'Content-Type: application/json' \
|
|
158
|
+
-H 'Accept: application/json, text/event-stream' \
|
|
159
|
+
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli","format":"json"}}}'
|
|
160
|
+
```
|
|
161
|
+
|
|
146
162
|
Use `variant:"cli"` for Claude Code/Codex/local terminal instructions. Use
|
|
147
163
|
`variant:"cloud"` for claude.ai web sessions where local daemon, shell, or
|
|
148
164
|
filesystem steps need cloud-safe wording.
|
package/bin/rdc-skills-mcp.mjs
CHANGED
|
@@ -189,25 +189,48 @@ function buildMcpServer() {
|
|
|
189
189
|
'rdc_skill_get',
|
|
190
190
|
{
|
|
191
191
|
description:
|
|
192
|
-
"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).",
|
|
192
|
+
"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). format 'json' returns metadata plus rendered body for direct API/curl callers.",
|
|
193
193
|
inputSchema: {
|
|
194
194
|
name: z.string().describe('Skill name or slash form (e.g. "deploy", "rdc:build", "rdc:brochurify", "lifeai-brochure-author"). See rdc_skill_list.'),
|
|
195
195
|
variant: z.enum(['cli', 'cloud']).optional().describe('Force the rendered variant; overrides caller detection.'),
|
|
196
|
+
format: z.enum(['text', 'json']).optional().describe("Return 'text' (default) for agent-readable SKILL.md, or 'json' for metadata plus rendered body."),
|
|
196
197
|
},
|
|
197
198
|
},
|
|
198
|
-
async ({ name, variant }) => {
|
|
199
|
+
async ({ name, variant, format }) => {
|
|
199
200
|
const resolvedName = resolveSkillName(name);
|
|
200
201
|
if (!resolvedName) {
|
|
201
|
-
const valid = listSkills().map((s) =>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
const valid = listSkills().map((s) => ({
|
|
203
|
+
name: s.name,
|
|
204
|
+
slash: s.slash,
|
|
205
|
+
aliases: s.aliases,
|
|
206
|
+
usage: s.usage,
|
|
207
|
+
}));
|
|
208
|
+
if (format === 'json') {
|
|
209
|
+
return textResult(JSON.stringify({
|
|
210
|
+
error: 'unknown_skill',
|
|
211
|
+
requested: name,
|
|
212
|
+
message: `Unknown skill '${name}'.`,
|
|
213
|
+
valid_count: valid.length,
|
|
214
|
+
valid,
|
|
215
|
+
}, null, 2));
|
|
216
|
+
}
|
|
217
|
+
return textResult(`Unknown skill '${name}'. Valid skills (${valid.length}): ${valid.map((s) => `${s.name} (${s.slash})`).join(', ')}`);
|
|
205
218
|
}
|
|
206
219
|
const resolved = variant || lastDetectedVariant || 'cloud';
|
|
207
220
|
const rendered = renderSkill(resolvedName, resolved);
|
|
208
221
|
if (!rendered) {
|
|
209
222
|
return textResult(`Skill '${resolvedName}' exists in the catalog but has no SKILL.md body on disk.`);
|
|
210
223
|
}
|
|
224
|
+
if (format === 'json') {
|
|
225
|
+
return textResult(JSON.stringify({
|
|
226
|
+
skill: getSkill(resolvedName),
|
|
227
|
+
requested: name,
|
|
228
|
+
resolved_name: resolvedName,
|
|
229
|
+
variant: resolved,
|
|
230
|
+
header: rendered.header,
|
|
231
|
+
body: rendered.body,
|
|
232
|
+
}, null, 2));
|
|
233
|
+
}
|
|
211
234
|
return textResult(`${rendered.header}\n\n${rendered.body}`);
|
|
212
235
|
},
|
|
213
236
|
);
|
package/commands/help.md
CHANGED
|
@@ -47,6 +47,7 @@ Header: Accept: application/json, text/event-stream
|
|
|
47
47
|
Tools: rdc_skill_list, rdc_skill_search, rdc_skill_get
|
|
48
48
|
Variants: cli for Claude Code/Codex/local terminal; cloud for claude.ai
|
|
49
49
|
Response: SSE `data:` line contains the JSON-RPC envelope; tool text is result.content[0].text
|
|
50
|
+
Catalog: rdc_skill_list returns aliases, args, requires, produces, follows, leads_to, output_contract, codeflow_required, and variants
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
Minimal curl examples:
|
|
@@ -72,6 +73,11 @@ curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
|
72
73
|
-H 'Content-Type: application/json' \
|
|
73
74
|
-H 'Accept: application/json, text/event-stream' \
|
|
74
75
|
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli"}}}'
|
|
76
|
+
|
|
77
|
+
curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
78
|
+
-H 'Content-Type: application/json' \
|
|
79
|
+
-H 'Accept: application/json, text/event-stream' \
|
|
80
|
+
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli","format":"json"}}}'
|
|
75
81
|
```
|
|
76
82
|
|
|
77
83
|
## Hard Rules
|
package/git-sha.json
CHANGED
package/lib/catalog.mjs
CHANGED
|
@@ -51,9 +51,12 @@ function toCatalogEntry(name, meta, frontmatter) {
|
|
|
51
51
|
const metaTriggers = Array.isArray(meta.triggers) && meta.triggers.length ? meta.triggers : null;
|
|
52
52
|
const fmTriggers = Array.isArray(fm.triggers) ? fm.triggers : [];
|
|
53
53
|
const slash = meta.slash || fm.slash || `rdc:${name}`;
|
|
54
|
+
const aliases = [name, slash];
|
|
55
|
+
if (slash.startsWith('rdc:')) aliases.push(`/${slash}`);
|
|
54
56
|
return {
|
|
55
57
|
name,
|
|
56
58
|
slash,
|
|
59
|
+
aliases,
|
|
57
60
|
category: meta.category || fm.category || 'tooling',
|
|
58
61
|
summary: description,
|
|
59
62
|
when_to_use: metaTriggers || fmTriggers,
|
|
@@ -62,6 +65,18 @@ function toCatalogEntry(name, meta, frontmatter) {
|
|
|
62
65
|
args: meta.args || fm.args || { positional: [], flags: [] },
|
|
63
66
|
requires: Array.isArray(meta.requires) ? meta.requires
|
|
64
67
|
: Array.isArray(fm.requires) ? fm.requires : [],
|
|
68
|
+
produces: Array.isArray(meta.produces) ? meta.produces
|
|
69
|
+
: Array.isArray(fm.produces) ? fm.produces : [],
|
|
70
|
+
follows: Array.isArray(meta.follows) ? meta.follows
|
|
71
|
+
: Array.isArray(fm.follows) ? fm.follows : [],
|
|
72
|
+
leads_to: Array.isArray(meta.leads_to) ? meta.leads_to
|
|
73
|
+
: Array.isArray(fm.leads_to) ? fm.leads_to : [],
|
|
74
|
+
default_model: meta.default_model || fm.default_model || 'inherit',
|
|
75
|
+
sandbox_aware: Boolean(meta.sandbox_aware ?? fm.sandbox_aware ?? false),
|
|
76
|
+
output_contract: meta.output_contract || fm.output_contract || null,
|
|
77
|
+
enabled_default: Boolean(meta.enabled_default ?? fm.enabled_default ?? true),
|
|
78
|
+
codeflow_required: Boolean(meta.codeflow_required ?? fm.codeflow_required ?? false),
|
|
79
|
+
variants: ['cli', 'cloud'],
|
|
65
80
|
};
|
|
66
81
|
}
|
|
67
82
|
|
|
@@ -179,7 +194,8 @@ export function getCloudOverride(name) {
|
|
|
179
194
|
|
|
180
195
|
/**
|
|
181
196
|
* Fuzzy/substring search over name + slash + summary + triggers.
|
|
182
|
-
* Returns ranked
|
|
197
|
+
* Returns ranked rows with enough metadata for raw MCP/curl callers to choose
|
|
198
|
+
* and fetch a skill without a second catalog lookup. score: higher = better.
|
|
183
199
|
*/
|
|
184
200
|
export function searchSkills(query) {
|
|
185
201
|
const q = String(query || '').trim().toLowerCase();
|
|
@@ -201,7 +217,20 @@ export function searchSkills(query) {
|
|
|
201
217
|
if (hay.triggers.includes(term)) score += 12;
|
|
202
218
|
if (hay.summary.includes(term)) score += 6;
|
|
203
219
|
}
|
|
204
|
-
if (score > 0)
|
|
220
|
+
if (score > 0) {
|
|
221
|
+
results.push({
|
|
222
|
+
name: s.name,
|
|
223
|
+
slash: s.slash,
|
|
224
|
+
aliases: s.aliases,
|
|
225
|
+
category: s.category,
|
|
226
|
+
summary: s.summary,
|
|
227
|
+
usage: s.usage,
|
|
228
|
+
requires: s.requires,
|
|
229
|
+
codeflow_required: s.codeflow_required,
|
|
230
|
+
variants: s.variants,
|
|
231
|
+
score,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
205
234
|
}
|
|
206
235
|
return results.sort((a, b) => b.score - a.score);
|
|
207
236
|
}
|
package/package.json
CHANGED
package/skills/help/SKILL.md
CHANGED
|
@@ -95,6 +95,7 @@ RDC SKILLS — manifest: .claude-plugin/plugin.json @ v{version}
|
|
|
95
95
|
Tools: rdc_skill_list, rdc_skill_search, rdc_skill_get
|
|
96
96
|
Variants: cli for Claude Code/Codex/local terminal; cloud for claude.ai
|
|
97
97
|
Response: SSE `data:` line contains the JSON-RPC envelope; tool text is result.content[0].text
|
|
98
|
+
Catalog: rdc_skill_list returns aliases, args, requires, produces, follows, leads_to, output_contract, codeflow_required, and variants
|
|
98
99
|
|
|
99
100
|
Extract the JSON-RPC envelope:
|
|
100
101
|
curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
@@ -121,6 +122,12 @@ RDC SKILLS — manifest: .claude-plugin/plugin.json @ v{version}
|
|
|
121
122
|
-H 'Accept: application/json, text/event-stream' \
|
|
122
123
|
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli"}}}'
|
|
123
124
|
|
|
125
|
+
Fetch a structured skill payload:
|
|
126
|
+
curl -s -X POST https://rdc-skills.regendevcorp.com/mcp \
|
|
127
|
+
-H 'Content-Type: application/json' \
|
|
128
|
+
-H 'Accept: application/json, text/event-stream' \
|
|
129
|
+
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"rdc_skill_get","arguments":{"name":"rdc:build","variant":"cli","format":"json"}}}'
|
|
130
|
+
|
|
124
131
|
## Hard rules
|
|
125
132
|
|
|
126
133
|
- `cf: yes` skills MUST consult CodeFlow before acting. Hooks fail-closed on unreachable CodeFlow for these.
|
|
@@ -170,6 +170,12 @@ async function main() {
|
|
|
170
170
|
const listed = JSON.parse(resultText(latestEnvelope(list.stdout)));
|
|
171
171
|
check('rdc_skill_list exposes 29 skills', listed.count === 29, `count ${listed.count}`);
|
|
172
172
|
check('rdc_skill_list includes visible slash name', listed.skills.some((s) => s.slash === 'rdc:build'));
|
|
173
|
+
const buildRow = listed.skills.find((s) => s.name === 'build');
|
|
174
|
+
check('rdc_skill_list exposes aliases for slash callers', buildRow?.aliases?.includes('/rdc:build'));
|
|
175
|
+
check('rdc_skill_list exposes supported variants', JSON.stringify(buildRow?.variants) === JSON.stringify(['cli', 'cloud']));
|
|
176
|
+
check('rdc_skill_list exposes output contract metadata', typeof buildRow?.output_contract === 'string');
|
|
177
|
+
check('rdc_skill_list exposes CodeFlow requirement metadata', buildRow?.codeflow_required === true);
|
|
178
|
+
check('rdc_skill_list exposes side-effect metadata', Array.isArray(buildRow?.produces));
|
|
173
179
|
|
|
174
180
|
const search = postMcp({
|
|
175
181
|
jsonrpc: '2.0',
|
|
@@ -186,6 +192,10 @@ async function main() {
|
|
|
186
192
|
'natural language search finds channel formatter',
|
|
187
193
|
searchBody.results?.some((s) => s.name === 'channel-formatter' || s.slash === 'rdc:channel-formatter'),
|
|
188
194
|
);
|
|
195
|
+
const formatterHit = searchBody.results?.find((s) => s.name === 'channel-formatter');
|
|
196
|
+
check('rdc_skill_search exposes usage metadata', /rdc:channel-formatter/.test(formatterHit?.usage || ''));
|
|
197
|
+
check('rdc_skill_search exposes category metadata', formatterHit?.category === 'tooling');
|
|
198
|
+
check('rdc_skill_search exposes variant metadata', formatterHit?.variants?.includes('cloud'));
|
|
189
199
|
|
|
190
200
|
const get = postMcp({
|
|
191
201
|
jsonrpc: '2.0',
|
|
@@ -201,6 +211,21 @@ async function main() {
|
|
|
201
211
|
check('rdc_skill_get returns tool text path', getText.length > 500, `length ${getText.length}`);
|
|
202
212
|
check('rdc_skill_get accepts visible rdc:build alias', /rdc:build/i.test(getText));
|
|
203
213
|
|
|
214
|
+
const getJson = postMcp({
|
|
215
|
+
jsonrpc: '2.0',
|
|
216
|
+
id: 5,
|
|
217
|
+
method: 'tools/call',
|
|
218
|
+
params: {
|
|
219
|
+
name: 'rdc_skill_get',
|
|
220
|
+
arguments: { name: 'rdc:build', variant: 'cli', format: 'json' },
|
|
221
|
+
},
|
|
222
|
+
}, 'rdc_skill_get_json');
|
|
223
|
+
check('rdc_skill_get format=json curl exits 0', getJson.status === 0, getJson.stderr);
|
|
224
|
+
const getJsonBody = JSON.parse(resultText(latestEnvelope(getJson.stdout)));
|
|
225
|
+
check('rdc_skill_get format=json returns resolved name', getJsonBody.resolved_name === 'build');
|
|
226
|
+
check('rdc_skill_get format=json returns skill metadata', getJsonBody.skill?.slash === 'rdc:build' && getJsonBody.skill?.codeflow_required === true);
|
|
227
|
+
check('rdc_skill_get format=json returns rendered body', /rdc:build/i.test(getJsonBody.body || ''));
|
|
228
|
+
|
|
204
229
|
const getMcp = curlWithStatus([
|
|
205
230
|
'-s',
|
|
206
231
|
`${TARGET}/mcp`,
|
|
@@ -26,6 +26,10 @@ for (const [name, text] of Object.entries(docs)) {
|
|
|
26
26
|
assert.match(text, /rdc_skill_list/, `${name} must mention rdc_skill_list`);
|
|
27
27
|
assert.match(text, /rdc_skill_search/, `${name} must mention rdc_skill_search`);
|
|
28
28
|
assert.match(text, /rdc_skill_get/, `${name} must mention rdc_skill_get`);
|
|
29
|
+
assert.match(text, /output_contract/, `${name} must describe rdc_skill_list metadata fields`);
|
|
30
|
+
assert.match(text, /codeflow_required/, `${name} must describe CodeFlow metadata`);
|
|
31
|
+
assert.match(text, /variants/, `${name} must describe supported skill variants`);
|
|
32
|
+
assert.match(text, /"format":"json"/, `${name} must show structured rdc_skill_get format=json`);
|
|
29
33
|
assert.match(text, /turn this article into social posts/, `${name} must include a natural-language search example`);
|
|
30
34
|
assert.match(text, /"name":"rdc:build"/, `${name} must show rdc_skill_get accepts visible slash names`);
|
|
31
35
|
assert.doesNotMatch(text, /https:\/\/rdc-skills\.dev\.regendevcorp\.com\/mcp/, `${name} must not point callers at dev MCP`);
|
package/tests/mcp.test.mjs
CHANGED
|
@@ -89,6 +89,21 @@ function unitTests() {
|
|
|
89
89
|
check('unit: catalog non-empty', cat.length >= 20, `got ${cat.length}`);
|
|
90
90
|
check('unit: every entry has name+slash+category+summary field',
|
|
91
91
|
cat.every((s) => s.name && s.slash && s.category && 'summary' in s));
|
|
92
|
+
check('unit: every entry exposes caller metadata fields',
|
|
93
|
+
cat.every((s) => Array.isArray(s.aliases)
|
|
94
|
+
&& Array.isArray(s.produces)
|
|
95
|
+
&& Array.isArray(s.follows)
|
|
96
|
+
&& Array.isArray(s.leads_to)
|
|
97
|
+
&& Array.isArray(s.variants)
|
|
98
|
+
&& 'default_model' in s
|
|
99
|
+
&& 'sandbox_aware' in s
|
|
100
|
+
&& 'output_contract' in s
|
|
101
|
+
&& 'enabled_default' in s
|
|
102
|
+
&& 'codeflow_required' in s));
|
|
103
|
+
check('unit: every entry exposes cli/cloud variants',
|
|
104
|
+
cat.every((s) => JSON.stringify(s.variants) === JSON.stringify(['cli', 'cloud'])));
|
|
105
|
+
check('unit: every rdc slash entry includes leading slash alias',
|
|
106
|
+
cat.every((s) => !s.slash.startsWith('rdc:') || s.aliases.includes(`/${s.slash}`)));
|
|
92
107
|
check('unit: every slash is explicit caller-facing shape',
|
|
93
108
|
cat.every((s) => /^rdc:/.test(s.slash) || s.slash === s.name));
|
|
94
109
|
check('unit: no synthesized duplicate rdc prefixes',
|
|
@@ -186,10 +201,18 @@ async function sweep(url, label, { compareSource }) {
|
|
|
186
201
|
const aliasBody = strip(callText(aliasGet.json));
|
|
187
202
|
check(`${label}: rdc_skill_get accepts slash aliases`, /rdc:brochurify Orchestrator/.test(aliasBody));
|
|
188
203
|
|
|
204
|
+
const jsonGet = await mcp(url, { jsonrpc: '2.0', id: 23, method: 'tools/call', params: { name: 'rdc_skill_get', arguments: { name: 'rdc:build', variant: 'cli', format: 'json' } } });
|
|
205
|
+
let jsonBody; try { jsonBody = JSON.parse(callText(jsonGet.json)); } catch { jsonBody = {}; }
|
|
206
|
+
check(`${label}: rdc_skill_get format=json returns resolved name`, jsonBody.resolved_name === 'build');
|
|
207
|
+
check(`${label}: rdc_skill_get format=json returns metadata`, jsonBody.skill?.slash === 'rdc:build' && jsonBody.skill?.codeflow_required === true);
|
|
208
|
+
check(`${label}: rdc_skill_get format=json returns rendered body`, typeof jsonBody.body === 'string' && jsonBody.body.includes('rdc:build'));
|
|
209
|
+
|
|
189
210
|
// search
|
|
190
211
|
const se = await mcp(url, { jsonrpc: '2.0', id: 21, method: 'tools/call', params: { name: 'rdc_skill_search', arguments: { query: 'deploy' } } });
|
|
191
212
|
let sr; try { sr = JSON.parse(callText(se.json)); } catch { sr = { results: [] }; }
|
|
192
213
|
check(`${label}: search returns ranked results`, (sr.results || []).length > 0 && sr.results[0].name);
|
|
214
|
+
check(`${label}: search result includes caller metadata`,
|
|
215
|
+
Boolean(sr.results?.[0]?.usage && sr.results?.[0]?.category && Array.isArray(sr.results?.[0]?.variants)));
|
|
193
216
|
|
|
194
217
|
return names.length;
|
|
195
218
|
}
|