@lifeaitools/rdc-skills 0.35.9 → 0.35.10

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.35.9",
3
+ "version": "0.35.10",
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",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": "lifeai.plugin.v1",
3
3
  "id": "rdc-skills",
4
- "version": "0.35.9",
4
+ "version": "0.35.10",
5
5
  "publisher": "LIFEAI",
6
6
  "core": false,
7
7
  "documentation": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/rdc-skills",
3
- "version": "0.35.9",
3
+ "version": "0.35.10",
4
4
  "description": "RDC typed-agent dispatch skill suite for Claude Code - plan, build, review, overnight builds",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1000,30 +1000,60 @@ function runPreflight() {
1000
1000
  }
1001
1001
 
1002
1002
  // ── Commands listing ──────────────────────────────────────────────────────────
1003
+ /**
1004
+ * List EVERY `/rdc:*` verb — from commands/ AND skills/.
1005
+ *
1006
+ * This enumerated `commands/` only. That was harmless while every verb shipped
1007
+ * as both a command and a skill, and became actively misleading the moment the
1008
+ * duplicates were removed (2026-08-29): the printed surface fell from 32 to 13
1009
+ * while the real surface was unchanged at 56, because a skill provides the
1010
+ * `rdc:` slash form on its own.
1011
+ *
1012
+ * Verified live, not assumed: `commands/status.md` was deleted and `rdc:status`
1013
+ * still resolved, loading `skills/status/SKILL.md`.
1014
+ *
1015
+ * A listing that under-reports by 43 verbs is worse than no listing — it reads
1016
+ * as "those commands are gone", which is exactly the conclusion I drew from it
1017
+ * before checking. The installer's output IS the document an operator reads
1018
+ * after an install, so it has to describe what actually exists.
1019
+ */
1003
1020
  function listCommands() {
1004
- const cmdsDir = path.join(repoRoot, 'commands');
1005
- if (!fs.existsSync(cmdsDir)) return;
1006
- const files = fs.readdirSync(cmdsDir).filter(f => f.endsWith('.md')).sort();
1007
- const plugin = readJson(path.join(repoRoot, '.claude-plugin', 'plugin.json'), {});
1008
- const skillCount = Array.isArray(plugin.skills_meta)
1009
- ? plugin.skills_meta.length
1010
- : (plugin.skills_meta && typeof plugin.skills_meta === 'object' ? Object.keys(plugin.skills_meta).length : null);
1011
- console.log('');
1012
- if (skillCount !== null) {
1013
- console.log(` \x1b[32mAvailable MCP skills (${skillCount}) and /rdc:* command shorthands (${files.length}):\x1b[0m`);
1014
- console.log(' Use MCP tools rdc_skill_list, rdc_skill_search, and rdc_skill_get for the full skill catalog.');
1015
- } else {
1016
- console.log(` \x1b[32mAvailable /rdc:* command shorthands (${files.length}):\x1b[0m`);
1021
+ const cmdsDir = path.join(repoRoot, 'commands');
1022
+ const skillDir = path.join(repoRoot, 'skills');
1023
+
1024
+ const verbs = new Map(); // name -> { desc, from }
1025
+ const add = (name, desc, from) => {
1026
+ if (!verbs.has(name)) verbs.set(name, { desc: desc || '', from });
1027
+ };
1028
+
1029
+ if (fs.existsSync(cmdsDir)) {
1030
+ for (const f of fs.readdirSync(cmdsDir).filter((x) => x.endsWith('.md'))) {
1031
+ const fm = readFrontmatter(path.join(cmdsDir, f));
1032
+ add(f.replace(/\.md$/, ''), fm.description, 'command');
1033
+ }
1034
+ }
1035
+ if (fs.existsSync(skillDir)) {
1036
+ for (const d of fs.readdirSync(skillDir, { withFileTypes: true })) {
1037
+ if (!d.isDirectory()) continue;
1038
+ const skillFile = path.join(skillDir, d.name, 'SKILL.md');
1039
+ if (!fs.existsSync(skillFile)) continue; // `tests/` is a fixture dir, not a skill
1040
+ add(d.name, readFrontmatter(skillFile).description, 'skill');
1041
+ }
1017
1042
  }
1043
+ if (verbs.size === 0) return;
1044
+
1045
+ const names = [...verbs.keys()].sort();
1046
+ const nCmd = [...verbs.values()].filter((v) => v.from === 'command').length;
1018
1047
  console.log('');
1019
- const COL = 18;
1020
- for (const f of files) {
1021
- const name = 'rdc:' + f.replace(/\.md$/, '');
1022
- const fm = readFrontmatter(path.join(cmdsDir, f));
1023
- const desc = (fm.description || '').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
1024
- const short = desc.length > 70 ? desc.slice(0, 70) + '…' : desc;
1025
- const pad = ' '.repeat(Math.max(1, COL - name.length));
1026
- console.log(` \x1b[36m/${name}\x1b[0m${pad}${short}`);
1048
+ console.log(` \x1b[32mAvailable /rdc:* verbs (${names.length}) — ${nCmd} command, ${names.length - nCmd} skill:\x1b[0m`);
1049
+ console.log(' Use MCP tools rdc_skill_list, rdc_skill_search, and rdc_skill_get for the full catalog.');
1050
+ console.log('');
1051
+ const COL = 26;
1052
+ for (const n of names) {
1053
+ const desc = (verbs.get(n).desc || '').replace(/\s+/g, ' ').trim();
1054
+ const short = desc.length > 70 ? `${desc.slice(0, 70)}…` : desc;
1055
+ const label = `rdc:${n}`;
1056
+ console.log(` \x1b[36m/${label}\x1b[0m${' '.repeat(Math.max(1, COL - label.length))}${short}`);
1027
1057
  }
1028
1058
  console.log('');
1029
1059
  }
@@ -60,15 +60,33 @@ assert.equal(
60
60
  realSkillDirs.length,
61
61
  `plugin.json skills_meta (${skillCount}) must match the actual skill directories on disk (${realSkillDirs.length}): ${realSkillDirs.join(', ')}`,
62
62
  );
63
+ // The listing must report EVERY /rdc:* verb, and say which surface each comes
64
+ // from. It used to enumerate commands/ only, which was harmless while every verb
65
+ // shipped as both a command and a skill — and became actively misleading the
66
+ // moment the duplicates were removed (2026-08-29): the printed surface fell from
67
+ // 32 to 13 while the real surface was unchanged, because a skill provides the
68
+ // slash form on its own (verified live: commands/status.md deleted, rdc:status
69
+ // still resolved). A listing that under-reports by 43 verbs reads as "those
70
+ // commands are gone".
63
71
  assert.match(
64
72
  source,
65
- /Available MCP skills.*\/rdc:\* command shorthands/,
66
- 'installer should distinguish the full MCP skill catalog from slash-command shorthands',
73
+ /Available \/rdc:\* verbs/,
74
+ 'installer should list every /rdc:* verb, not only the command-backed ones',
67
75
  );
68
76
  assert.match(
69
77
  source,
70
- /Object\.keys\(plugin\.skills_meta\)\.length/,
71
- 'installer should count object-shaped skills_meta manifests',
78
+ /command, \$\{[^}]*\} skill/,
79
+ 'installer should say how many verbs come from commands and how many from skills',
80
+ );
81
+ assert.match(
82
+ source,
83
+ /readdirSync\(skillDir, \{ withFileTypes: true \}\)/,
84
+ 'installer should enumerate the real skill directories, not a manifest count that can drift from disk',
85
+ );
86
+ assert.match(
87
+ source,
88
+ /SKILL\.md'\)\)\) continue/,
89
+ 'installer must skip a directory with no SKILL.md — tests/ is a fixture dir, not a skill',
72
90
  );
73
91
  assert.match(
74
92
  source,