@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 +4 -2
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/lib/ui.js +62 -15
- package/scripts/rsc.js +3 -18
- package/skills/implement/SKILL.md +12 -6
- package/skills/sdd/SKILL.md +2 -0
- package/skills/specify/SKILL.md +9 -0
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
|
-
|
|
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
package/package.json
CHANGED
package/scripts/lib/ui.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import { createInterface
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
|
|
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 {
|
|
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 (!
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
package/skills/sdd/SKILL.md
CHANGED
|
@@ -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`.
|
package/skills/specify/SKILL.md
CHANGED
|
@@ -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.
|