@ericrisco/rsc 0.1.26 → 0.1.28
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/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/install-apply.js +33 -15
- package/scripts/rsc.js +20 -0
- package/skills/suggest/SKILL.md +23 -18
- package/skills/suggest/evals/cases.yaml +6 -6
package/manifest.json
CHANGED
package/package.json
CHANGED
package/scripts/install-apply.js
CHANGED
|
@@ -14,22 +14,37 @@ const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
|
|
|
14
14
|
// materialized at — the single, target-agnostic source of truth for "installed
|
|
15
15
|
// skills version" (read by the SessionStart update check too).
|
|
16
16
|
const versionFile = (cwd) => join(cwd, '.rsc', '.version');
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
// Per-skill base version. A single global `.rsc/.version` cannot represent a
|
|
19
|
+
// partially-refreshed base set, which broke multi-target sync: the first target's pass
|
|
20
|
+
// bumped `.rsc/.version`, so later targets saw "current" and skipped refreshing their
|
|
21
|
+
// exclusive skills' bases. Tracking the version each base was materialized at makes the
|
|
22
|
+
// refresh decision per skill, independent of target ordering. (Absent file → every base
|
|
23
|
+
// is treated as stale and refreshed once, which self-heals installs from before this.)
|
|
24
|
+
const baseVersionsFile = (cwd) => join(cwd, '.rsc', '.base-versions.json');
|
|
25
|
+
function readBaseVersions(cwd) {
|
|
26
|
+
try { return JSON.parse(readFileSync(baseVersionsFile(cwd), 'utf8')); } catch { return {}; }
|
|
27
|
+
}
|
|
28
|
+
function writeBaseVersions(cwd, versions) {
|
|
29
|
+
mkdirSync(dirname(baseVersionsFile(cwd)), { recursive: true });
|
|
30
|
+
writeFileSync(baseVersionsFile(cwd), JSON.stringify(versions, null, 2) + '\n');
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
// Materialize the real skill files into the project-local base.
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
33
|
+
// Materialize the real skill files into the project-local base. Copied once and reused;
|
|
34
|
+
// when the recorded base version for THIS skill differs from the CLI version, the base is
|
|
35
|
+
// re-copied so a reinstall/sync actually updates content. Tracked per skill (see
|
|
36
|
+
// baseVersionsFile) so a multi-target sync refreshes every target's bases, not just the
|
|
37
|
+
// first target's. Skills are read-only catalog (user customization lives in 02-DOCS), so
|
|
38
|
+
// overwriting on a version change is safe. Mutates `baseVersions` with the new mark.
|
|
39
|
+
function ensureBase(id, cwd, baseVersions) {
|
|
27
40
|
const dest = baseDir(id, cwd);
|
|
28
|
-
|
|
41
|
+
const stale = baseVersions[id] !== CLI_VERSION;
|
|
42
|
+
if (stale && existsSync(dest)) rmSync(dest, { recursive: true, force: true });
|
|
29
43
|
if (!existsSync(dest)) {
|
|
30
44
|
mkdirSync(dirname(dest), { recursive: true });
|
|
31
45
|
cpSync(join(ROOT, 'skills', id), dest, { recursive: true });
|
|
32
46
|
}
|
|
47
|
+
baseVersions[id] = CLI_VERSION;
|
|
33
48
|
return dest;
|
|
34
49
|
}
|
|
35
50
|
|
|
@@ -46,7 +61,7 @@ function generatedHookFiles({ target, cwd }) {
|
|
|
46
61
|
function managedPathsForInstall({ skillIds, target, home, cwd }) {
|
|
47
62
|
const paths = targetPaths(target, home, cwd);
|
|
48
63
|
const plan = planInstall({ skillIds, target, home, cwd });
|
|
49
|
-
const out = [paths.stateFile, versionFile(cwd)];
|
|
64
|
+
const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
|
|
50
65
|
for (const step of plan) {
|
|
51
66
|
if (step.kind === 'skill') {
|
|
52
67
|
out.push(step.to, baseDir(step.id, cwd));
|
|
@@ -64,18 +79,21 @@ export async function applyInstall({ skillIds, target, home, cwd = process.cwd()
|
|
|
64
79
|
if (dryRun) return { dryRun: true, skills: skillIds, paths: managedPaths };
|
|
65
80
|
const state = readState(paths.stateFile);
|
|
66
81
|
const backup = createBackup({ cwd, operation, target, paths: managedPaths, cliVersion: CLI_VERSION });
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
82
|
+
// Decide base refresh per skill (see baseVersionsFile): a base is re-materialized when
|
|
83
|
+
// its recorded version differs from the CLI version. Robust to multi-target installs/
|
|
84
|
+
// syncs — a single global marker would be bumped by the first target and make later
|
|
85
|
+
// targets skip refreshing their exclusive skills' bases.
|
|
86
|
+
const baseVersions = readBaseVersions(cwd);
|
|
70
87
|
for (const step of plan) {
|
|
71
88
|
if (step.kind === 'skill') {
|
|
72
|
-
const base = ensureBase(step.id, cwd,
|
|
89
|
+
const base = ensureBase(step.id, cwd, baseVersions);
|
|
73
90
|
const files = await writeSkill(target, step.id, base, step.to);
|
|
74
91
|
state.skills[step.id] = { files, base };
|
|
75
92
|
} else if (step.kind === 'hook') {
|
|
76
|
-
await wireHook(target, paths, join(ensureBase('suggest', cwd,
|
|
93
|
+
await wireHook(target, paths, join(ensureBase('suggest', cwd, baseVersions), 'SKILL.md'));
|
|
77
94
|
}
|
|
78
95
|
}
|
|
96
|
+
writeBaseVersions(cwd, baseVersions);
|
|
79
97
|
state.version = CLI_VERSION;
|
|
80
98
|
writeState(paths.stateFile, state);
|
|
81
99
|
mkdirSync(dirname(versionFile(cwd)), { recursive: true });
|
package/scripts/rsc.js
CHANGED
|
@@ -187,6 +187,26 @@ async function main() {
|
|
|
187
187
|
for (const o of toOutcomes(ids)) say(`${o.id}\t${o.label}`);
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
|
+
case 'catalog': {
|
|
191
|
+
// Full catalog dump for SEMANTIC in-agent discovery: every skill as
|
|
192
|
+
// `id <installed|available> short description`, unranked. `consult` ranks
|
|
193
|
+
// lexically and returns nothing for natural-language / Catalan intent; `catalog`
|
|
194
|
+
// hands the agent the whole candidate set so the MODEL picks the best-fit missing
|
|
195
|
+
// skill by meaning. `--available` drops what's already installed for this target.
|
|
196
|
+
const m = loadManifest();
|
|
197
|
+
const installed = new Set(listInstalled({ target }));
|
|
198
|
+
const onlyAvailable = argv.includes('--available');
|
|
199
|
+
const short = (d) => {
|
|
200
|
+
const s = String(d || '').split('. ')[0].replace(/\s+/g, ' ').trim();
|
|
201
|
+
return s.length > 160 ? `${s.slice(0, 159)}…` : s;
|
|
202
|
+
};
|
|
203
|
+
for (const sk of [...m.skills].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
204
|
+
const state = installed.has(sk.id) ? 'installed' : 'available';
|
|
205
|
+
if (onlyAvailable && state === 'installed') continue;
|
|
206
|
+
say(`${sk.id}\t${state}\t${short(sk.description)}`);
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
190
210
|
case 'audit': {
|
|
191
211
|
const report = audit();
|
|
192
212
|
const written = writeAuditReport(report);
|
package/skills/suggest/SKILL.md
CHANGED
|
@@ -56,8 +56,8 @@ When the current task would clearly benefit from an rsc skill that is **not inst
|
|
|
56
56
|
Rules:
|
|
57
57
|
|
|
58
58
|
- Installing changes the user's environment — always confirm first.
|
|
59
|
-
- To know what exists, run `npx @ericrisco/rsc
|
|
60
|
-
-
|
|
59
|
+
- To know what exists, run `npx @ericrisco/rsc catalog --available` (every NOT-installed skill as `id available short description`) and **pick the best fit yourself, by meaning** — see the detector below. Don't guess from memory.
|
|
60
|
+
- `npx @ericrisco/rsc consult "<task>"` is only a cheap **lexical** hint: it keyword-matches and silently returns nothing for natural-language or non-English/Catalan intent (e.g. it finds no email skill for "mandar emails"). Never let it be the decider; if it's empty, scan `catalog` and judge semantically.
|
|
61
61
|
- Never recommend something already installed (`npx @ericrisco/rsc list`).
|
|
62
62
|
- One suggestion at a time. Don't interrupt the flow for nice-to-haves.
|
|
63
63
|
|
|
@@ -78,24 +78,29 @@ This is broader than "start a project". It includes mid-conversation requests su
|
|
|
78
78
|
- Knowledge and research: "documenta cómo funciona esto", "crea una wiki", "procesa este inbox", "research competitors", "turn this into SOPs".
|
|
79
79
|
- Other languages: any equivalent phrasing. Match the user's intent, not exact words.
|
|
80
80
|
|
|
81
|
-
When a capability intent appears:
|
|
82
|
-
|
|
83
|
-
1. Run `npx @ericrisco/rsc
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
When a capability intent appears, **you** make the match semantically — do not delegate the decision to a keyword ranker:
|
|
82
|
+
|
|
83
|
+
1. Run `npx @ericrisco/rsc catalog --available` to get every NOT-installed skill as
|
|
84
|
+
`id available short description`. (It already excludes what's installed.)
|
|
85
|
+
2. **Read the catalog and pick the single best-fit skill by MEANING** — match the user's
|
|
86
|
+
intent to a skill's *purpose*, semantically, in any language (CA/ES/EN/…), the way you'd
|
|
87
|
+
match a request to a teammate's expertise. Judge meaning, not shared keywords:
|
|
88
|
+
"mandar emails de bienvenida" → `email-connector` even though no words overlap its tags;
|
|
89
|
+
"login con Google" → an auth/security skill, not `flutter`; "transcripció de veu" → a
|
|
90
|
+
speech/audio skill *if one exists*.
|
|
91
|
+
3. If nothing in the catalog genuinely fits, **say so and move on** — don't force a weak
|
|
92
|
+
match, and don't propose a tangential skill just to have an answer. (`npx @ericrisco/rsc
|
|
93
|
+
consult "<intent>"` is available as a lexical hint, but it misses natural-language and
|
|
94
|
+
Catalan intent and is silent for many real needs — never read its silence as "no skill
|
|
95
|
+
exists." The catalog + your judgment is the source of truth.)
|
|
87
96
|
4. Ask before installing: "Para esto instalaría `<id>`, que aún no tienes. ¿La instalo? (sí/no)".
|
|
88
|
-
5. On yes, run `npx @ericrisco/rsc add <id>` and
|
|
89
|
-
|
|
90
|
-
Example: if the user says "quiero montar una pagina web para vender cursos online",
|
|
91
|
-
do not just start building. Check the installed list, consult that exact phrase, and
|
|
92
|
-
recommend the first missing skill from the returned queue. In a base install, `init`
|
|
93
|
-
and `harness` may already exist, so the first missing skill is usually `nextjs`. In a
|
|
94
|
-
bare install, `init` may be first. Install one skill at a time.
|
|
97
|
+
5. On yes, run `npx @ericrisco/rsc add <id>` and continue the original request. One at a time.
|
|
95
98
|
|
|
96
|
-
Example:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
Example: "quiero montar una pagina web para vender cursos online" → scan the catalog and
|
|
100
|
+
pick the web stack skill (usually `nextjs`) by meaning; don't start building before offering it.
|
|
101
|
+
Example: "hazme una secuencia de cold emails para vender mi SaaS" → pick the email/outreach
|
|
102
|
+
skill (`cold-outreach` / `email-connector`) because it *means* email outreach — not because
|
|
103
|
+
the keywords happen to match (they often won't).
|
|
99
104
|
|
|
100
105
|
## Onboarding gate (first contact)
|
|
101
106
|
|
|
@@ -62,17 +62,17 @@ capability:
|
|
|
62
62
|
- scenario: "Mid-conversation, after base install, the user says 'quiero montar una pagina web para vender cursos online'. `init`, `harness`, `orient`, and `suggest` are already installed; `nextjs` is not. Show how rsc-suggest behaves."
|
|
63
63
|
must_include:
|
|
64
64
|
- "Treats the message as in-agent capability intent, not as a CLI-only consult task"
|
|
65
|
-
- "Runs `npx @ericrisco/rsc
|
|
66
|
-
- "
|
|
67
|
-
- "Names `nextjs` as the
|
|
65
|
+
- "Runs `npx @ericrisco/rsc catalog --available` to see the not-installed skills (already excludes installed `init`/`harness`)"
|
|
66
|
+
- "Picks the best-fit skill by MEANING from the catalog, not by relying on lexical `consult` ranking"
|
|
67
|
+
- "Names `nextjs` as the missing useful skill for the website work"
|
|
68
68
|
- "Asks one short confirmation before installing and does not auto-install"
|
|
69
69
|
- "On confirmation, runs `npx @ericrisco/rsc add nextjs`, then resumes the original website task"
|
|
70
70
|
|
|
71
71
|
- scenario: "Mid-conversation, the user says 'Automatiza el flujo de leads desde el formulario hasta mi CRM'. The base skills are installed, but `automation-flows` and CRM/connector skills are not. Show how rsc-suggest behaves."
|
|
72
72
|
must_include:
|
|
73
73
|
- "Treats the message as in-agent capability intent for automation/integration, not only as a coding task"
|
|
74
|
-
- "Runs `npx @ericrisco/rsc
|
|
75
|
-
- "
|
|
76
|
-
- "Names
|
|
74
|
+
- "Runs `npx @ericrisco/rsc catalog --available` to see the not-installed skills"
|
|
75
|
+
- "Picks the best-fit missing automation/connector skill by MEANING (not by lexical `consult` ranking, which may return nothing)"
|
|
76
|
+
- "Names that one missing automation/connector skill"
|
|
77
77
|
- "Asks one short confirmation before installing and does not auto-install"
|
|
78
78
|
- "On confirmation, runs `npx @ericrisco/rsc add <id>` for that one missing skill, then resumes the automation task"
|