@hanzlaa/rcode 3.2.1 → 3.3.0
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/cli/generate-command-skills.cjs +203 -0
- package/cli/install.js +18 -1
- package/dist/rcode.js +12 -1
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Install-time generator: skill stubs that mirror slash commands.
|
|
4
|
+
*
|
|
5
|
+
* WHY: VS Code's Claude Code extension lists `.claude/skills/` in its
|
|
6
|
+
* sidebar, but slash commands at `.claude/commands/rihal/` are only
|
|
7
|
+
* reachable via the `/` autocomplete. Users coming from GSD (which ships
|
|
8
|
+
* a skill per command) expect to see lifecycle commands like `rihal-do`
|
|
9
|
+
* in the sidebar.
|
|
10
|
+
*
|
|
11
|
+
* This script creates ONE skill stub per slash command at install time,
|
|
12
|
+
* unless a real skill with the same name already exists at the source.
|
|
13
|
+
* The stubs live at the install destination only (`.claude/skills/`) —
|
|
14
|
+
* they are NEVER committed to the rcode source tree. That keeps the
|
|
15
|
+
* source codebase free of duplication while letting the sidebar feel
|
|
16
|
+
* full.
|
|
17
|
+
*
|
|
18
|
+
* Each generated stub is marked with `generated: true` and
|
|
19
|
+
* `generated-by: rcode-install` in frontmatter so it can be detected
|
|
20
|
+
* and refreshed on subsequent installs without confusing real skills.
|
|
21
|
+
*
|
|
22
|
+
* Run: node scripts/generate-command-skills.cjs <package-root> <target-skills-dir>
|
|
23
|
+
* (called from cli/install.js)
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Curated list of commands that get skill stubs. Not every command
|
|
31
|
+
* deserves a sidebar entry — only the user-facing entry points and
|
|
32
|
+
* lifecycle verbs. Internal sub-commands stay slash-only.
|
|
33
|
+
*/
|
|
34
|
+
const SIDEBAR_COMMANDS = new Set([
|
|
35
|
+
'do', 'status', 'progress', 'next', 'plan', 'execute',
|
|
36
|
+
'council', 'discuss', 'ship', 'audit', 'autonomous',
|
|
37
|
+
'session-report', 'forensics', 'health', 'debug',
|
|
38
|
+
'verify-phase', 'verify-work', 'review', 'code-review',
|
|
39
|
+
'new-project', 'new-milestone', 'milestone-summary',
|
|
40
|
+
'note', 'add-todo', 'check-todos', 'pause-work', 'resume-work',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
function parseFrontmatter(text) {
|
|
44
|
+
if (!text.startsWith('---\n')) return {};
|
|
45
|
+
const end = text.indexOf('\n---\n', 4);
|
|
46
|
+
if (end === -1) return {};
|
|
47
|
+
const block = text.slice(4, end);
|
|
48
|
+
const fm = {};
|
|
49
|
+
for (const raw of block.split('\n')) {
|
|
50
|
+
const m = raw.match(/^([a-zA-Z_-]+):\s*(.+)$/);
|
|
51
|
+
if (m) fm[m[1].trim()] = m[2].trim().replace(/^["']|["']$/g, '');
|
|
52
|
+
}
|
|
53
|
+
return fm;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function discoverRealSkills(packageRoot) {
|
|
57
|
+
const out = new Set();
|
|
58
|
+
const buckets = ['core', 'agents'];
|
|
59
|
+
for (const bucket of buckets) {
|
|
60
|
+
const dir = path.join(packageRoot, 'rihal', 'skills', bucket);
|
|
61
|
+
if (!fs.existsSync(dir)) continue;
|
|
62
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
63
|
+
if (entry.startsWith('rihal-')) out.add(entry);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// actions are nested by phase
|
|
67
|
+
const actionsDir = path.join(packageRoot, 'rihal', 'skills', 'actions');
|
|
68
|
+
if (fs.existsSync(actionsDir)) {
|
|
69
|
+
for (const phase of fs.readdirSync(actionsDir)) {
|
|
70
|
+
const phaseDir = path.join(actionsDir, phase);
|
|
71
|
+
if (!fs.statSync(phaseDir).isDirectory()) continue;
|
|
72
|
+
for (const entry of fs.readdirSync(phaseDir)) {
|
|
73
|
+
if (entry.startsWith('rihal-')) out.add(entry);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function generateStub(cmdName, commandFm, version) {
|
|
81
|
+
const desc = commandFm.description || `Slash command shortcut for /rihal:${cmdName}.`;
|
|
82
|
+
const triggers = [
|
|
83
|
+
`rihal ${cmdName}`,
|
|
84
|
+
`rihal:${cmdName}`,
|
|
85
|
+
`/rihal:${cmdName}`,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
return `---
|
|
89
|
+
name: rihal-${cmdName}
|
|
90
|
+
description: >
|
|
91
|
+
${desc.replace(/\n/g, ' ')}
|
|
92
|
+
Sidebar entry — invokes the same workflow as /rihal:${cmdName}.
|
|
93
|
+
triggers:
|
|
94
|
+
${triggers.map((t) => ` - "${t}"`).join('\n')}
|
|
95
|
+
user-invocable: true
|
|
96
|
+
generated: true
|
|
97
|
+
generated-by: rcode-install-v${version}
|
|
98
|
+
source: rihal/commands/${cmdName}.md
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
<!--
|
|
102
|
+
AUTO-GENERATED at install time by scripts/generate-command-skills.cjs.
|
|
103
|
+
Do NOT edit this file directly — your changes will be overwritten on the next
|
|
104
|
+
install or update. To customise, create a sibling \`.local.md\` file (which
|
|
105
|
+
is preserved across installs) or modify the source command at
|
|
106
|
+
rihal/commands/${cmdName}.md.
|
|
107
|
+
|
|
108
|
+
This stub exists so the slash command /rihal:${cmdName} appears in VS Code's
|
|
109
|
+
Claude Code sidebar (which lists skills only, not slash commands). The
|
|
110
|
+
behaviour is identical to /rihal:${cmdName} — both invoke the workflow at
|
|
111
|
+
.rihal/workflows/${cmdName}.md.
|
|
112
|
+
-->
|
|
113
|
+
|
|
114
|
+
## Overview
|
|
115
|
+
|
|
116
|
+
Sidebar entry for the \`/rihal:${cmdName}\` slash command. Phrase-activated alternative invocation route.
|
|
117
|
+
|
|
118
|
+
## Workflow
|
|
119
|
+
|
|
120
|
+
Read and execute \`@.rihal/workflows/${cmdName}.md\` end-to-end. The behaviour is the same as invoking \`/rihal:${cmdName}\` directly.
|
|
121
|
+
|
|
122
|
+
## Output Format
|
|
123
|
+
|
|
124
|
+
Identical to \`/rihal:${cmdName}\`. See the workflow file for the canonical output spec.
|
|
125
|
+
|
|
126
|
+
## Examples
|
|
127
|
+
|
|
128
|
+
**Happy path** — say "rihal ${cmdName}" or "/rihal:${cmdName}" → workflow runs identically either way.
|
|
129
|
+
|
|
130
|
+
**Negative — looking for the canonical command file** — see \`rihal/commands/${cmdName}.md\` (the source of truth) and \`rihal/workflows/${cmdName}.md\` (the orchestration logic).
|
|
131
|
+
|
|
132
|
+
## Memory Bank Hooks
|
|
133
|
+
|
|
134
|
+
- **Reads:** whatever \`.rihal/workflows/${cmdName}.md\` reads
|
|
135
|
+
- **Writes:** whatever the workflow writes
|
|
136
|
+
- This stub itself is read-only — it just dispatches to the workflow.
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function main(packageRoot, targetSkillsDir, version) {
|
|
141
|
+
if (!fs.existsSync(targetSkillsDir)) {
|
|
142
|
+
fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const realSkills = discoverRealSkills(packageRoot);
|
|
146
|
+
const commandsDir = path.join(packageRoot, 'rihal', 'commands');
|
|
147
|
+
if (!fs.existsSync(commandsDir)) {
|
|
148
|
+
console.warn(`[generate-command-skills] commands dir not found: ${commandsDir}`);
|
|
149
|
+
return { generated: 0, skipped: 0 };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let generated = 0;
|
|
153
|
+
let skipped = 0;
|
|
154
|
+
|
|
155
|
+
for (const file of fs.readdirSync(commandsDir)) {
|
|
156
|
+
if (!file.endsWith('.md')) continue;
|
|
157
|
+
if (file.startsWith('_')) continue; // internal aliases / metadata
|
|
158
|
+
const cmdName = file.replace(/\.md$/, '');
|
|
159
|
+
if (!SIDEBAR_COMMANDS.has(cmdName)) { skipped++; continue; }
|
|
160
|
+
|
|
161
|
+
const skillName = `rihal-${cmdName}`;
|
|
162
|
+
if (realSkills.has(skillName)) {
|
|
163
|
+
// A real skill with this name exists in the source tree — don't shadow it
|
|
164
|
+
skipped++;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const cmdPath = path.join(commandsDir, file);
|
|
169
|
+
const cmdText = fs.readFileSync(cmdPath, 'utf8');
|
|
170
|
+
const cmdFm = parseFrontmatter(cmdText);
|
|
171
|
+
|
|
172
|
+
const stubDir = path.join(targetSkillsDir, skillName);
|
|
173
|
+
const stubFile = path.join(stubDir, 'SKILL.md');
|
|
174
|
+
|
|
175
|
+
// Idempotency: only overwrite if the file is missing OR is a previously
|
|
176
|
+
// generated stub (has the marker). Never clobber a user's hand-edited file.
|
|
177
|
+
if (fs.existsSync(stubFile)) {
|
|
178
|
+
const existing = fs.readFileSync(stubFile, 'utf8');
|
|
179
|
+
if (!/^generated:\s*true/m.test(existing)) {
|
|
180
|
+
skipped++;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fs.mkdirSync(stubDir, { recursive: true });
|
|
186
|
+
fs.writeFileSync(stubFile, generateStub(cmdName, cmdFm, version), 'utf8');
|
|
187
|
+
generated++;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { generated, skipped };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (require.main === module) {
|
|
194
|
+
const [packageRoot, targetSkillsDir, version = '0.0.0'] = process.argv.slice(2);
|
|
195
|
+
if (!packageRoot || !targetSkillsDir) {
|
|
196
|
+
console.error('Usage: generate-command-skills.cjs <package-root> <target-skills-dir> [version]');
|
|
197
|
+
process.exit(2);
|
|
198
|
+
}
|
|
199
|
+
const { generated, skipped } = main(packageRoot, targetSkillsDir, version);
|
|
200
|
+
console.log(`✓ ${generated} sidebar skill stub${generated === 1 ? '' : 's'} generated, ${skipped} skipped (already real skills or out of curated set)`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { main, SIDEBAR_COMMANDS };
|
package/cli/install.js
CHANGED
|
@@ -1619,7 +1619,24 @@ async function install(opts) {
|
|
|
1619
1619
|
|
|
1620
1620
|
// Install v1-style phrase-activated skills (scaffold-project, create-prd,
|
|
1621
1621
|
// retrospective, etc.) into .claude/skills/ alongside the v2 agents/commands.
|
|
1622
|
-
|
|
1622
|
+
let skillsInstalled = installSkills(PACKAGE_ROOT, opts.target);
|
|
1623
|
+
|
|
1624
|
+
// Generate install-time skill stubs that mirror sidebar-worthy slash commands.
|
|
1625
|
+
// Source codebase stays clean — these stubs only exist at the install
|
|
1626
|
+
// destination, marked with `generated: true` so they refresh idempotently.
|
|
1627
|
+
// See cli/generate-command-skills.cjs for rationale.
|
|
1628
|
+
try {
|
|
1629
|
+
const { main: generateCommandSkills } = require(path.join(PACKAGE_ROOT, 'cli', 'generate-command-skills.cjs'));
|
|
1630
|
+
const stubsDir = path.join(opts.target, '.claude', 'skills');
|
|
1631
|
+
const result = generateCommandSkills(PACKAGE_ROOT, stubsDir, readPackageVersion());
|
|
1632
|
+
if (result.generated > 0) {
|
|
1633
|
+
console.log(' ' + dim(`${result.generated} sidebar skill stub${result.generated === 1 ? '' : 's'} generated for command discoverability`));
|
|
1634
|
+
skillsInstalled += result.generated;
|
|
1635
|
+
}
|
|
1636
|
+
} catch (err) {
|
|
1637
|
+
// Non-fatal: install succeeds without sidebar stubs
|
|
1638
|
+
console.log(' ' + dim(`(sidebar stub generation skipped: ${err.message})`));
|
|
1639
|
+
}
|
|
1623
1640
|
|
|
1624
1641
|
// Seed .planning/ with starter ROADMAP + STATE so workflows work immediately
|
|
1625
1642
|
const starterSeeded = seedStarterPlanning(opts.target, opts.projectName);
|
package/dist/rcode.js
CHANGED
|
@@ -17015,7 +17015,18 @@ Say "plan a sprint" or run \`/rihal:sprint-planning\` to break Phase 01 into sto
|
|
|
17015
17015
|
path2.join(configDir, "files-manifest.csv"),
|
|
17016
17016
|
generateFilesManifest(plan, opts.target)
|
|
17017
17017
|
);
|
|
17018
|
-
|
|
17018
|
+
let skillsInstalled = installSkills(PACKAGE_ROOT2, opts.target);
|
|
17019
|
+
try {
|
|
17020
|
+
const { main: generateCommandSkills } = require(path2.join(PACKAGE_ROOT2, "cli", "generate-command-skills.cjs"));
|
|
17021
|
+
const stubsDir = path2.join(opts.target, ".claude", "skills");
|
|
17022
|
+
const result = generateCommandSkills(PACKAGE_ROOT2, stubsDir, readPackageVersion());
|
|
17023
|
+
if (result.generated > 0) {
|
|
17024
|
+
console.log(" " + dim(`${result.generated} sidebar skill stub${result.generated === 1 ? "" : "s"} generated for command discoverability`));
|
|
17025
|
+
skillsInstalled += result.generated;
|
|
17026
|
+
}
|
|
17027
|
+
} catch (err) {
|
|
17028
|
+
console.log(" " + dim(`(sidebar stub generation skipped: ${err.message})`));
|
|
17029
|
+
}
|
|
17019
17030
|
const starterSeeded = seedStarterPlanning(opts.target, opts.projectName);
|
|
17020
17031
|
installBrainScaffold(PACKAGE_ROOT2, opts.target);
|
|
17021
17032
|
const gitignoreReport = ensureRcodeGitignore(opts.target, { commitPlanning: opts.commitPlanning });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzlaa/rcode",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "rcode — the memory bank for AI-driven SaaS teams. Persistent project context, distinctive engineering personas, and phase-based workflows. Built by Rihal. Works in Claude Code, Cursor, Gemini, VS Code, and Antigravity.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|