@ericrisco/rsc 0.1.5 β†’ 0.1.7

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/README.md CHANGED
@@ -80,13 +80,15 @@ assistant proposes new skills on its own from then on.
80
80
 
81
81
  ```
82
82
  $ rsc
83
- πŸ‘‹ rsc β€” the skill catalog for your assistant.
83
+ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— ← animated gradient wordmark
84
+ β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•
85
+ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘
86
+ 231 skills Β· one CLI Β· zero bloat
84
87
 
85
88
  What do you want to do? ↑↓ move Β· enter select
86
89
  ❯ Base install β€” the essentials (orient + suggest + harness + init)
87
90
  Base + Spec-Driven Development β€” specify β†’ plan β†’ implement β†’ ship
88
91
  Pick skills by hand, by area
89
- Describe my project and let rsc choose
90
92
  ```
91
93
 
92
94
  Pick **by area** and you get a checkbox list β€” **↑↓ to move, space to toggle,
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.5",
2
+ "version": "0.1.7",
3
3
  "counts": {
4
4
  "skills": 231
5
5
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/lib/ui.js CHANGED
@@ -1,6 +1,9 @@
1
- import { createInterface, emitKeypressEvents } from 'node:readline';
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { emitKeypressEvents } from 'node:readline';
2
3
  import { stdin, stdout } from 'node:process';
3
4
 
5
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6
+
4
7
  export async function ask(question) {
5
8
  const rl = createInterface({ input: stdin, output: stdout });
6
9
  const a = await rl.question(question);
@@ -33,20 +36,52 @@ const C = {
33
36
  green: (s) => `\x1b[32m${s}\x1b[39m`,
34
37
  };
35
38
 
36
- // ASCII wordmark shown at the top of the wizard.
37
- export function banner() {
38
- const tty = Boolean(stdout.isTTY);
39
- const art = [
40
- '',
41
- ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—',
42
- ' β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•',
43
- ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ ',
44
- ' β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ ',
45
- ' β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—',
46
- ' β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β•',
47
- ];
48
- for (const l of art) say(tty ? C.cyan(l) : l);
49
- say((tty ? C.dim(' 231 skills Β· one CLI Β· zero bloat') : ' 231 skills Β· one CLI Β· zero bloat'));
39
+ const ART = [
40
+ ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—',
41
+ ' β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•',
42
+ ' β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ ',
43
+ ' β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ ',
44
+ ' β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—',
45
+ ' β•šβ•β• β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β•',
46
+ ];
47
+
48
+ // Vertical truecolor gradient (sky β†’ violet), phase-shiftable for the wave.
49
+ function gradientLine(text, row, phase) {
50
+ const a = [56, 189, 248]; const b = [167, 139, 250];
51
+ const t = ((row + phase) % ART.length) / (ART.length - 1);
52
+ const r = Math.round(a[0] + (b[0] - a[0]) * t);
53
+ const g = Math.round(a[1] + (b[1] - a[1]) * t);
54
+ const bl = Math.round(a[2] + (b[2] - a[2]) * t);
55
+ return `\x1b[38;2;${r};${g};${bl}m${text}\x1b[39m`;
56
+ }
57
+
58
+ // Animated ASCII wordmark β€” the "wooow". Static plain text when not a TTY.
59
+ export async function banner() {
60
+ if (!stdout.isTTY) {
61
+ say('');
62
+ for (const l of ART) say(l);
63
+ say(' 231 skills Β· one CLI Β· zero bloat');
64
+ return;
65
+ }
66
+ const W = Math.max(...ART.map((l) => l.length));
67
+ stdout.write('\x1b[?25l'); // hide cursor
68
+ say('');
69
+ // 1) letters slide in left β†’ right, column by column
70
+ let first = true;
71
+ for (let w = 1; w <= W; w++) {
72
+ if (!first) stdout.write(`\x1b[${ART.length}A`);
73
+ first = false;
74
+ stdout.write(ART.map((l, i) => `\x1b[2K${gradientLine(l.slice(0, w), i, 0)}`).join('\n') + '\n');
75
+ await sleep(20);
76
+ }
77
+ // 2) one flowing color wave to settle
78
+ for (let phase = 1; phase <= ART.length; phase++) {
79
+ stdout.write(`\x1b[${ART.length}A`);
80
+ stdout.write(ART.map((l, i) => `\x1b[2K${gradientLine(l, i, phase)}`).join('\n') + '\n');
81
+ await sleep(45);
82
+ }
83
+ say(C.dim(' 231 skills Β· one CLI Β· zero bloat'));
84
+ stdout.write('\x1b[?25h'); // show cursor
50
85
  }
51
86
 
52
87
  // Repaint a fixed block of lines in place (cursor ends just below the block).
@@ -142,6 +177,18 @@ export async function select(question, options) {
142
177
  return byKey ? byKey.key : null;
143
178
  }
144
179
 
180
+ // Public: yes/no confirmation. Arrow TUI when interactive, typed prompt otherwise.
181
+ export async function confirm(question) {
182
+ if (interactive()) {
183
+ const k = await tuiSelect(question, [
184
+ { key: 'yes', label: 'Yes, install it' },
185
+ { key: 'no', label: 'No, cancel' },
186
+ ]);
187
+ return k === 'yes';
188
+ }
189
+ return yes(await ask(`${question} (yes / no) > `));
190
+ }
191
+
145
192
  // Public: multi-select. items: array of strings or { id, label }. Returns ids.
146
193
  export async function pickFrom(title, items) {
147
194
  const norm = items.map((it) => (typeof it === 'string' ? { id: it, label: it } : it));
package/scripts/rsc.js CHANGED
@@ -6,7 +6,7 @@ import { rank } from './consult.js';
6
6
  import { expandRecommends, toOutcomes, hasOutcome } from './lib/recommend.js';
7
7
  import { applyInstall, listInstalled, uninstall } from './install-apply.js';
8
8
  import { doctor } from './doctor.js';
9
- import { ask, say, yes, select, pickFrom, banner } from './lib/ui.js';
9
+ import { say, select, pickFrom, banner, confirm } from './lib/ui.js';
10
10
  import { refreshRegistry, registryStatus } from './lib/registry.js';
11
11
  import { DOMAINS } from './lib/domains.js';
12
12
 
@@ -45,19 +45,6 @@ async function manualSelect() {
45
45
  return [...chosen];
46
46
  }
47
47
 
48
- // Describe-your-project flow: rsc reads repo + words and proposes skills.
49
- async function describeFlow() {
50
- const goal = await ask('\nTell me in one sentence what you want to build or run:\n> ');
51
- const ids = await recommendIds(goal, { labeledOnly: true });
52
- if (!ids.length) {
53
- say("I'm not sure. Try describing it in more detail, or pick by hand (option 3).");
54
- return [];
55
- }
56
- say('\nI recommend installing:');
57
- for (const o of toOutcomes(ids)) say(` β€’ ${o.label}`);
58
- return ids;
59
- }
60
-
61
48
  // After installing, remind the user how to actually start β€” per IDE β€” and that
62
49
  // rsc keeps recommending skills as they work. The harness/SDD *init* runs INSIDE
63
50
  // the assistant (with the user present), never blindly from this CLI.
@@ -94,20 +81,18 @@ function printNextSteps(target, ids) {
94
81
 
95
82
  async function wizard() {
96
83
  const m = loadManifest();
97
- banner();
84
+ await banner();
98
85
  say(' the skill catalog for your assistant (Claude Code Β· Cursor Β· Codex Β· Gemini)\n');
99
86
  const choice = await select('What do you want to do?', [
100
87
  { key: 'base', label: 'Base install β€” the essentials (orient + suggest + harness + init)' },
101
88
  { key: 'sdd', label: 'Base + Spec-Driven Development β€” the specify β†’ plan β†’ implement β†’ ship flow' },
102
89
  { key: 'manual', label: 'Pick skills by hand, by area' },
103
- { key: 'describe', label: 'Describe my project and let rsc choose' },
104
90
  ]);
105
91
 
106
92
  let ids = [];
107
93
  if (choice === 'base') ids = skillsForProfile(m, 'minimal');
108
94
  else if (choice === 'sdd') ids = skillsForProfile(m, 'core');
109
95
  else if (choice === 'manual') ids = await manualSelect();
110
- else if (choice === 'describe') ids = await describeFlow();
111
96
  else { say("Didn't catch that. Run again: npx @ericrisco/rsc"); return; }
112
97
 
113
98
  // The floor is always installed: the compass + the detector.
@@ -120,7 +105,7 @@ async function wizard() {
120
105
  const target = detectTarget();
121
106
  say(`\nI'll install ${ids.length} skills into ${target}:`);
122
107
  say(' ' + ids.join(', '));
123
- if (!yes(await ask('\nInstall it? (yes / no) > '))) {
108
+ if (!(await confirm('Install it?'))) {
124
109
  say('No problem. Anytime: npx @ericrisco/rsc');
125
110
  return;
126
111
  }
@@ -23,12 +23,18 @@ into them rather than reinventing them.
23
23
  ## Before you write a line of code
24
24
 
25
25
  Run this short gate. Skipping it is the most common way an implement session goes sideways.
26
-
27
- 1. **Read the inputs.** Open the spec (`02-DOCS/wiki/sdd/specs/<slug>.md`), the plan
28
- (`02-DOCS/wiki/sdd/plans/<slug>.md`, which holds the task list), and the constitution
29
- (`02-DOCS/wiki/sdd/constitution.md`). If any is missing, you are not ready to implement β€” route
30
- back: no plan β†’ `plan`; no task list inside the plan β†’ `tasks`; unresolved `analyze` findings β†’
31
- resolve them first. Do not start coding to "discover the plan as you go".
26
+ **This gate is a hard refuse, not a checklist to feel good about:** if it fails you stop and route
27
+ back β€” you do not write feature code anyway.
28
+
29
+ 1. **The spec + plan must exist AND be approved.** Open the spec (`02-DOCS/wiki/sdd/specs/<slug>.md`),
30
+ the plan (`02-DOCS/wiki/sdd/plans/<slug>.md`, which holds the task list), and the constitution
31
+ (`02-DOCS/wiki/sdd/constitution.md`). If any is missing, **you are not allowed to implement** β€”
32
+ route back: no spec β†’ `specify`; no plan β†’ `plan`; no task list inside the plan β†’ `tasks`;
33
+ unresolved `analyze` findings β†’ resolve them first. If a spec/plan exists but the user has not
34
+ actually seen and **approved** it, get that approval first. Never "start coding to discover the
35
+ plan as you go", and never accept "just build it, skip the spec" on a non-trivial feature β€” name
36
+ the gate in one friendly line and route to `specify`. (Only a true one-line, low-risk change
37
+ earns a skip, and you say so.)
32
38
  2. **Read the SDD runtime config.** Open `02-DOCS/wiki/sdd/config.yaml`. If it is missing on
33
39
  non-trivial work, stop and route to `sdd-init`. Use `testing.strict_tdd`,
34
40
  `testing.commands.apply`, `testing.commands.verify`, `sdd.review_budget` and
@@ -70,6 +70,8 @@ constitution ─(once per project)─┐
70
70
 
71
71
  When a request lands, do not start typing code. Place it on the map first, then invoke the phase skill that owns it.
72
72
 
73
+ > **The new-feature gate (hard).** The moment the user is thinking about a new feature or change β€” "add…", "build…", "it should also…", "quiero aΓ±adir…" β€” it goes to `specify` first, *even if a stack skill (nextjs/fastapi/flutter/react…) also fired and could just build it*. **No feature code is written β€” by any skill β€” until a spec AND a plan exist and the user has approved them.** A stack skill about to build an unspec'd, non-trivial feature must stop and route here. The only exception is a genuinely one-line, low-risk change; name it and skip.
74
+
73
75
  1. **No `02-DOCS/wiki/sdd/config.yaml` and this is non-trivial?** β†’ `sdd-init`.
74
76
  2. **No constitution yet AND this project will grow?** β†’ `constitution` once, then come back to the chain.
75
77
  3. **Ambiguous / architectural / risky change before spec?** β†’ optional proposal artifact via `specify`.
@@ -13,6 +13,15 @@ This is the **specify** phase of the rsc-sdd chain: `constitution` β†’ **`specif
13
13
 
14
14
  A spec is a contract about behaviour and outcomes, readable by a non-technical stakeholder and precise enough that a `plan` can be derived from it. The output is one file: `02-DOCS/wiki/sdd/specs/<slug>.md`, indexed in the root `CLAUDE.md` Knowledge map.
15
15
 
16
+ ## Detect the moment β€” and hold the gate
17
+
18
+ The instant the user is **thinking about a new feature or change** β€” "I want to add…", "can we build…", "it should also…", "wouldn't it be nice if…", "necesito que haga…", "quiero aΓ±adir…" β€” this phase owns it, *even if a stack skill (nextjs/fastapi/flutter…) also fired and is itching to build it*. Catch it here first.
19
+
20
+ **The hard gate (non-negotiable for any non-trivial feature):**
21
+ > No feature code gets written until (1) this spec exists and the user has **approved** it, and (2) `plan` has produced a technical plan + task list. If the user tries to jump straight to "just build it", do not. Name the gate in one friendly line, capture the spec, and walk the chain. The only bypass is a genuinely one-line, low-risk change (typo/copy/config) β€” say so out loud and skip.
22
+
23
+ You are not slowing them down; you are making the intent reviewable *before* code exists, which is cheaper than discovering the misunderstanding in a PR. End every spec by handing to `clarify`/`plan` β€” never to `implement`.
24
+
16
25
  ## The one rule that defines this skill
17
26
 
18
27
  **No implementation leaks.** The moment the spec names a framework, a table schema, a library, an endpoint shape, a file path, or an algorithm, it has stopped being a spec. Those decisions belong to `plan` and the stack skills. Specify describes the *observable behaviour and the reason for it*; the system that delivers it is deliberately left open.