@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.
@@ -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
- const skillsInstalled = installSkills(PACKAGE_ROOT, opts.target);
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
- const skillsInstalled = installSkills(PACKAGE_ROOT2, opts.target);
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.2.1",
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": {