@ericrisco/rsc 1.1.5 → 1.2.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/README.md +72 -14
- package/manifest.json +888 -231
- package/package.json +3 -2
- package/schema/ecc-originality.json +16 -0
- package/schema/ecc-originality.u64 +0 -0
- package/scripts/build-manifest.js +43 -3
- package/scripts/doctor.js +47 -1
- package/scripts/install-apply.js +148 -31
- package/scripts/lib/capabilities.js +21 -0
- package/scripts/lib/domains.js +3 -3
- package/scripts/lib/manifest-file.js +4 -1
- package/scripts/originality-check.js +137 -0
- package/scripts/rsc.js +122 -14
- package/skills/design/SKILL.md +3 -3
- package/skills/design/references/design-systems.md +1 -1
- package/skills/design/references/inspiration-sources.md +2 -2
- package/skills/design/references/motion-and-interaction.md +2 -2
- package/skills/design-dna/evals/cases.yaml +1 -1
- package/skills/design-loop/SKILL.md +3 -3
- package/skills/design-loop/evals/cases.yaml +3 -3
- package/skills/expo/SKILL.md +1 -1
- package/skills/motion-craft/SKILL.md +125 -0
- package/skills/motion-craft/evals/cases.yaml +38 -0
- package/skills/motion-craft/scripts/verify.sh +75 -0
- package/skills/react/SKILL.md +2 -2
- package/skills/react-native/SKILL.md +1 -1
- package/skills/swift-ios/SKILL.md +2 -2
- package/skills/ui-engineering/SKILL.md +114 -0
- package/skills/ui-engineering/evals/cases.yaml +38 -0
- package/skills/variant-explorer/SKILL.md +93 -0
- package/skills/variant-explorer/evals/cases.yaml +38 -0
- package/targets/agent-catalog.js +220 -0
- package/targets/agent-sources.json +48 -0
- package/targets/agents.js +61 -13
- package/targets/commands.js +212 -0
- package/targets/memory.js +276 -0
- package/targets/opencode-memory-plugin.js +65 -0
- package/targets/session-memory-adapter.mjs +117 -0
- package/targets/session-memory-core.mjs +477 -0
- package/targets/session-memory.mjs +27 -0
- package/skills/animate/SKILL.md +0 -851
- package/skills/animate/evals/cases.yaml +0 -36
- package/skills/animate/references/recipes.md +0 -324
- package/skills/animate-expo/SKILL.md +0 -281
- package/skills/animate-expo/evals/cases.yaml +0 -36
- package/skills/animate-expo/references/recipes.md +0 -385
- package/skills/animation-vocabulary/SKILL.md +0 -189
- package/skills/animation-vocabulary/evals/cases.yaml +0 -31
- package/skills/apple-design/SKILL.md +0 -301
- package/skills/apple-design/evals/cases.yaml +0 -33
- package/skills/ask-sonner/SKILL.md +0 -104
- package/skills/ask-sonner/evals/cases.yaml +0 -33
- package/skills/ask-sonner/references/api.md +0 -64
- package/skills/design-eng/SKILL.md +0 -88
- package/skills/design-eng/evals/cases.yaml +0 -36
- package/skills/find-animation-opportunities/SKILL.md +0 -150
- package/skills/find-animation-opportunities/evals/cases.yaml +0 -31
- package/skills/improve-animations/SKILL.md +0 -126
- package/skills/improve-animations/evals/cases.yaml +0 -31
- package/skills/improve-animations/references/audit.md +0 -115
- package/skills/improve-animations/references/plan-template.md +0 -73
- package/skills/pick-ui-library/SKILL.md +0 -101
- package/skills/pick-ui-library/evals/cases.yaml +0 -33
- package/skills/pick-ui-library/references/component-galleries.md +0 -59
- package/skills/prototype/SKILL.md +0 -113
- package/skills/prototype/evals/cases.yaml +0 -31
- package/skills/prototype/references/picker.md +0 -197
- package/skills/review-animations/SKILL.md +0 -188
- package/skills/review-animations/evals/cases.yaml +0 -34
- package/skills/review-animations/references/standards.md +0 -187
- package/skills/review-animations/scripts/verify.sh +0 -164
- package/skills/write-swift/SKILL.md +0 -413
- package/skills/write-swift/evals/cases.yaml +0 -36
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ericrisco/rsc",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
"knowledge:doctor": "node scripts/knowledge-doctor.js",
|
|
20
20
|
"spec:gate": "node scripts/spec-gate.js",
|
|
21
21
|
"routing:audit": "node scripts/routing-audit.js",
|
|
22
|
+
"originality:check": "node scripts/originality-check.js",
|
|
22
23
|
"test": "node --test",
|
|
23
24
|
"coverage": "c8 --reporter=text node --test",
|
|
24
25
|
"version": "npm run manifest && git add manifest.json",
|
|
25
|
-
"prepublishOnly": "npm run manifest && npm run validate && npm run drift:check"
|
|
26
|
+
"prepublishOnly": "npm run manifest && npm run validate && npm run drift:check && npm run originality:check"
|
|
26
27
|
},
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@iarna/toml": "^2.2.5",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"source": "https://github.com/affaan-m/everything-claude-code",
|
|
4
|
+
"revision": "90430ab3a716e12a9c6770802efa352098735f24",
|
|
5
|
+
"generatedAt": "2026-09-02",
|
|
6
|
+
"included": [
|
|
7
|
+
"agents/**",
|
|
8
|
+
"commands/**"
|
|
9
|
+
],
|
|
10
|
+
"ngramSize": 8,
|
|
11
|
+
"normalization": "NFKC lowercase; Unicode letters/numbers plus _+.#/-; whitespace joined",
|
|
12
|
+
"fingerprintFormat": "first 64 bits of SHA-256, sorted big-endian; collisions fail closed",
|
|
13
|
+
"fingerprintCount": 93337,
|
|
14
|
+
"dataSha256": "2025038e308e097c1d478c6eeeabf34dd35e6affa7abb20c3b31cf0fdde63463",
|
|
15
|
+
"exemptions": []
|
|
16
|
+
}
|
|
Binary file
|
|
@@ -4,6 +4,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import Ajv from 'ajv';
|
|
5
5
|
import { parseFrontmatter } from './lib/frontmatter.js';
|
|
6
6
|
import { fenceBalance } from './lib/skill-lint.js';
|
|
7
|
+
import { allAgentNames, agentByName } from '../targets/agents.js';
|
|
8
|
+
import { fixedCommands, resolveCommands } from '../targets/commands.js';
|
|
7
9
|
|
|
8
10
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
11
|
const SKILLS = join(ROOT, 'skills');
|
|
@@ -32,7 +34,45 @@ export function buildManifest() {
|
|
|
32
34
|
profiles: fm.profiles || [],
|
|
33
35
|
};
|
|
34
36
|
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
35
|
-
|
|
37
|
+
const agents = allAgentNames().map((id) => {
|
|
38
|
+
const agent = agentByName(id);
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
description: agent.desc,
|
|
42
|
+
role: agent.role || (id === 'developer' ? 'developer' : 'refuter'),
|
|
43
|
+
tier: agent.tier || 'balanced',
|
|
44
|
+
tools: agent.tools || null,
|
|
45
|
+
skills: agent.skills || [],
|
|
46
|
+
};
|
|
47
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
48
|
+
// Cursor exposes every command kind, so resolving the complete catalog through
|
|
49
|
+
// that target gives the target-neutral public inventory: fixed entries plus all
|
|
50
|
+
// stack aliases, without introducing a second alias table.
|
|
51
|
+
const commands = resolveCommands({
|
|
52
|
+
target: 'cursor',
|
|
53
|
+
skills: ids,
|
|
54
|
+
agents: allAgentNames(),
|
|
55
|
+
memoryMode: 'full',
|
|
56
|
+
}).map((command) => ({
|
|
57
|
+
id: command.name,
|
|
58
|
+
description: command.description,
|
|
59
|
+
kind: command.kind,
|
|
60
|
+
backing: command.backing,
|
|
61
|
+
}));
|
|
62
|
+
const sourceReceipts = JSON.parse(readFileSync(join(ROOT, 'targets', 'agent-sources.json'), 'utf8'));
|
|
63
|
+
return {
|
|
64
|
+
version,
|
|
65
|
+
counts: {
|
|
66
|
+
skills: skills.length,
|
|
67
|
+
agents: agents.length,
|
|
68
|
+
commands: commands.length,
|
|
69
|
+
fixedCommands: fixedCommands().length,
|
|
70
|
+
},
|
|
71
|
+
skills,
|
|
72
|
+
agents,
|
|
73
|
+
commands,
|
|
74
|
+
sourceReceipts,
|
|
75
|
+
};
|
|
36
76
|
}
|
|
37
77
|
|
|
38
78
|
export function validateFrontmatter() {
|
|
@@ -102,11 +142,11 @@ function main() {
|
|
|
102
142
|
let current = '';
|
|
103
143
|
try { current = readFileSync(out, 'utf8'); } catch { /* missing */ }
|
|
104
144
|
if (current !== json) { console.error('manifest.json is stale — run `npm run manifest`'); process.exit(1); }
|
|
105
|
-
console.log(`manifest OK (${manifest.counts.skills} skills)`);
|
|
145
|
+
console.log(`manifest OK (${manifest.counts.skills} skills, ${manifest.counts.agents} agents, ${manifest.counts.commands} commands)`);
|
|
106
146
|
return;
|
|
107
147
|
}
|
|
108
148
|
writeFileSync(out, json);
|
|
109
|
-
console.log(`wrote manifest.json (${manifest.counts.skills} skills)`);
|
|
149
|
+
console.log(`wrote manifest.json (${manifest.counts.skills} skills, ${manifest.counts.agents} agents, ${manifest.counts.commands} commands)`);
|
|
110
150
|
}
|
|
111
151
|
|
|
112
152
|
if (import.meta.url === `file://${process.argv[1]}`) main();
|
package/scripts/doctor.js
CHANGED
|
@@ -10,7 +10,11 @@ import { SDD_GATE_TEXT } from '../targets/hook-once.mjs';
|
|
|
10
10
|
import { isEnabled, checkSello, readSello, countFindings, readEffectiveConfig, validateRiskConfig } from '../targets/sello.mjs';
|
|
11
11
|
import { designIdentity } from './lib/design-identity.js';
|
|
12
12
|
import { startingPointSummary } from './lib/starting-point.js';
|
|
13
|
-
import { countGaps } from './lib/capabilities.js';
|
|
13
|
+
import { countGaps, listSkills, listAgents } from './lib/capabilities.js';
|
|
14
|
+
import { resolveCommands, commandPath, targetHasCommands } from '../targets/commands.js';
|
|
15
|
+
import { inspectMemoryWiring } from '../targets/memory.js';
|
|
16
|
+
import { metricsSummary } from '../targets/session-memory-core.mjs';
|
|
17
|
+
import { agentPath } from '../targets/agents.js';
|
|
14
18
|
|
|
15
19
|
// The sello's health, surfaced where the user already looks (spec: non-blocking
|
|
16
20
|
// findings live in the project and are SUMMARIZED here, never nagged about).
|
|
@@ -81,6 +85,42 @@ export function doctor({ target, home, cwd }) {
|
|
|
81
85
|
const state = readState(paths.stateFile);
|
|
82
86
|
const manifest = loadManifest();
|
|
83
87
|
const backups = listBackups({ cwd: root });
|
|
88
|
+
const memory = inspectMemoryWiring(
|
|
89
|
+
target,
|
|
90
|
+
root,
|
|
91
|
+
state.memory,
|
|
92
|
+
Boolean(Object.keys(state.skills || {}).length || state.agents?.length || state.commands?.length),
|
|
93
|
+
);
|
|
94
|
+
if (state.memory && !['unsupported', 'disabled'].includes(memory.mode)) {
|
|
95
|
+
const hasStore = existsSync(join(root, '.rsc', 'memory', 'sessions'))
|
|
96
|
+
|| existsSync(join(root, '02-DOCS', 'raw', 'worklog', '.rsc-memory', 'sessions'));
|
|
97
|
+
try {
|
|
98
|
+
memory.metrics = hasStore
|
|
99
|
+
? metricsSummary({ cwd: root })
|
|
100
|
+
: { sessions: [], total: { cost: null, toolCalls: null }, knownTotal: { cost: 0, toolCalls: 0 }, unknown: { cost: 0, toolCalls: 0 } };
|
|
101
|
+
} catch { memory.metrics = { sessions: [], total: { cost: null, toolCalls: null }, knownTotal: { cost: 0, toolCalls: 0 }, unknown: { cost: 0, toolCalls: 0 } }; }
|
|
102
|
+
}
|
|
103
|
+
const actualSkills = listSkills({ target, home, cwd: root }).map((entry) => entry.id);
|
|
104
|
+
const actualAgents = listAgents({ target, home, cwd: root }).agents.map((entry) => entry.id);
|
|
105
|
+
const desiredCommands = new Set(resolveCommands({
|
|
106
|
+
target,
|
|
107
|
+
skills: actualSkills,
|
|
108
|
+
agents: actualAgents,
|
|
109
|
+
memoryMode: memory.status === 'ready' ? memory.mode : 'degraded',
|
|
110
|
+
}).map((command) => command.name));
|
|
111
|
+
const missingCommands = [];
|
|
112
|
+
const commandOrphans = [];
|
|
113
|
+
if (targetHasCommands(target)) {
|
|
114
|
+
for (const id of state.commands || []) {
|
|
115
|
+
const path = commandPath(target, root, id);
|
|
116
|
+
if (!path || !existsSync(path)) missingCommands.push({ id, path, action: 'Run `npx @ericrisco/rsc sync` to restore this managed command.' });
|
|
117
|
+
else if (!desiredCommands.has(id)) commandOrphans.push({ id, path, action: 'Restore its backing skill/agent with `rsc add`, or run `rsc sync` to reconcile it.' });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const missingAgents = (state.agents || []).filter((id) => {
|
|
121
|
+
const path = agentPath(target, root, id);
|
|
122
|
+
return path && !existsSync(path);
|
|
123
|
+
}).map((id) => ({ id, action: 'Run `npx @ericrisco/rsc sync` to restore this managed agent.' }));
|
|
84
124
|
const report = {
|
|
85
125
|
target,
|
|
86
126
|
installed: Object.keys(state.skills),
|
|
@@ -108,6 +148,12 @@ export function doctor({ target, home, cwd }) {
|
|
|
108
148
|
gitmojiGuard: existsSync(join(root, '.rsc', '.no-gitmoji')) ? 'opted-out' : 'armed',
|
|
109
149
|
// Counted, never interpreted — by spec, the gap log's reader is the user.
|
|
110
150
|
automationGaps: countGaps(root),
|
|
151
|
+
memory,
|
|
152
|
+
missingAgents,
|
|
153
|
+
agentCollisions: state.agentCollisions || [],
|
|
154
|
+
missingCommands,
|
|
155
|
+
commandOrphans,
|
|
156
|
+
commandCollisions: state.commandCollisions || [],
|
|
111
157
|
};
|
|
112
158
|
for (const [id, e] of Object.entries(state.skills)) {
|
|
113
159
|
for (const f of e.files) if (!existsSync(f)) report.missing.push(`${id}:${f}`);
|
package/scripts/install-apply.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { rmSync, existsSync, cpSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, readdirSync } from 'node:fs';
|
|
3
|
-
import { join, dirname,
|
|
3
|
+
import { join, dirname, relative, sep } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { planInstall } from './install-plan.js';
|
|
6
6
|
import { targetPaths, writeSkill, wireHook, unwireHook, baseDir, TARGET_IDS } from '../targets/index.js';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
targetHasAgents, reconcileAgents, agentPath, agentNames,
|
|
9
|
+
resolveAgentNames, agentByName, readDeveloperTier,
|
|
10
|
+
} from '../targets/agents.js';
|
|
8
11
|
import { readState, writeState } from './lib/state.js';
|
|
9
12
|
import { readManifest, writeManifest } from './lib/manifest-file.js';
|
|
10
13
|
import { createBackup } from './lib/backups.js';
|
|
14
|
+
import {
|
|
15
|
+
targetHasCommands, resolveCommands, reconcileCommands, commandPath,
|
|
16
|
+
} from '../targets/commands.js';
|
|
17
|
+
import {
|
|
18
|
+
wireMemory, unwireMemory, memoryManagedPaths, memoryModeFor, memoryEnabledForProject, memoryArtifactsPresent,
|
|
19
|
+
} from '../targets/memory.js';
|
|
11
20
|
|
|
12
21
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
22
|
const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
|
|
@@ -68,11 +77,29 @@ function generatedHookFiles({ target, cwd }) {
|
|
|
68
77
|
];
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
export function managedPathsForInstall({ skillIds, target, home, cwd }) {
|
|
80
|
+
export function managedPathsForInstall({ skillIds, agentIds = [], target, home, cwd }) {
|
|
72
81
|
const paths = targetPaths(target, home, cwd);
|
|
73
82
|
const plan = planInstall({ skillIds, target, home, cwd });
|
|
74
83
|
const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
|
|
75
|
-
|
|
84
|
+
out.push(...memoryManagedPaths(target, cwd));
|
|
85
|
+
if (targetHasAgents(target)) {
|
|
86
|
+
const state = readState(paths.stateFile);
|
|
87
|
+
const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])];
|
|
88
|
+
const desired = resolveAgentNames([...Object.keys(state.skills || {}), ...skillIds], explicit);
|
|
89
|
+
out.push(...[...new Set([...(state.agents || []), ...desired])].map((n) => agentPath(target, cwd, n)), join(cwd, '.rsc', 'developer.json'));
|
|
90
|
+
}
|
|
91
|
+
if (targetHasCommands(target)) {
|
|
92
|
+
const state = readState(paths.stateFile);
|
|
93
|
+
const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])];
|
|
94
|
+
const desiredAgents = resolveAgentNames([...Object.keys(state.skills || {}), ...skillIds], explicit);
|
|
95
|
+
const desiredCommands = resolveCommands({
|
|
96
|
+
target,
|
|
97
|
+
skills: [...new Set([...Object.keys(state.skills || {}), ...skillIds])],
|
|
98
|
+
agents: desiredAgents,
|
|
99
|
+
memoryMode: memoryEnabledForProject(cwd) ? memoryModeFor(target) : 'disabled',
|
|
100
|
+
});
|
|
101
|
+
out.push(...[...new Set([...(state.commands || []), ...desiredCommands.map((command) => command.name)])].map((name) => commandPath(target, cwd, name)));
|
|
102
|
+
}
|
|
76
103
|
for (const step of plan) {
|
|
77
104
|
if (step.kind === 'skill') {
|
|
78
105
|
out.push(step.to, baseDir(step.id, cwd));
|
|
@@ -102,8 +129,8 @@ function localDecisions(cwd) {
|
|
|
102
129
|
// into codex on a machine that already had claude is adding, not replacing. Union, sorted
|
|
103
130
|
// where order carries no meaning, so the file stays diffable and a merge conflict stays
|
|
104
131
|
// readable.
|
|
105
|
-
export function recordInManifest({ cwd, target, skillIds, catalogVersion = CLI_VERSION, dropTarget }) {
|
|
106
|
-
const prev = readManifest(cwd) || { targets: [], skills: [], ownSkills: [], optOuts: [] };
|
|
132
|
+
export function recordInManifest({ cwd, target, skillIds, agentIds = [], catalogVersion = CLI_VERSION, dropTarget }) {
|
|
133
|
+
const prev = readManifest(cwd) || { targets: [], skills: [], agents: [], ownSkills: [], optOuts: [] };
|
|
107
134
|
const { optOuts, tier } = localDecisions(cwd);
|
|
108
135
|
const union = (a, b) => [...new Set([...(a || []), ...(b || [])])].sort();
|
|
109
136
|
return writeManifest(cwd, {
|
|
@@ -113,18 +140,20 @@ export function recordInManifest({ cwd, target, skillIds, catalogVersion = CLI_V
|
|
|
113
140
|
// have every clone rebuild a harness that was deliberately abandoned.
|
|
114
141
|
targets: union(prev.targets, [target]).filter((t) => t !== dropTarget),
|
|
115
142
|
skills: union(prev.skills, skillIds),
|
|
143
|
+
agents: union(prev.agents, agentIds),
|
|
116
144
|
ownSkills: prev.ownSkills || [],
|
|
117
145
|
catalogVersion,
|
|
118
146
|
tier: tier ?? prev.tier ?? null,
|
|
119
147
|
optOuts: optOuts.length ? optOuts : (prev.optOuts || []),
|
|
148
|
+
memory: prev.memory,
|
|
120
149
|
});
|
|
121
150
|
}
|
|
122
151
|
|
|
123
|
-
export async function applyInstall({ skillIds, target, home, cwd = process.cwd(), operation = 'install', dryRun = false }) {
|
|
152
|
+
export async function applyInstall({ skillIds = [], agentIds = [], target, home, cwd = process.cwd(), operation = 'install', dryRun = false }) {
|
|
124
153
|
const paths = targetPaths(target, home, cwd);
|
|
125
154
|
const plan = planInstall({ skillIds, target, home, cwd });
|
|
126
|
-
const managedPaths = managedPathsForInstall({ skillIds, target, home, cwd });
|
|
127
|
-
if (dryRun) return { dryRun: true, skills: skillIds, paths: managedPaths };
|
|
155
|
+
const managedPaths = managedPathsForInstall({ skillIds, agentIds, target, home, cwd });
|
|
156
|
+
if (dryRun) return { dryRun: true, skills: skillIds, agents: agentIds, paths: managedPaths };
|
|
128
157
|
const state = readState(paths.stateFile);
|
|
129
158
|
const backup = createBackup({ cwd, operation, target, paths: managedPaths, cliVersion: CLI_VERSION });
|
|
130
159
|
// Decide base refresh per skill (see baseVersionsFile): a base is re-materialized when
|
|
@@ -150,15 +179,34 @@ export async function applyInstall({ skillIds, target, home, cwd = process.cwd()
|
|
|
150
179
|
//
|
|
151
180
|
// The recorded names come from what was WRITTEN, not from a hardcoded list: a state entry
|
|
152
181
|
// naming an agent whose file never landed answers "you have it" for something absent.
|
|
182
|
+
const explicit = [...new Set([...(state.explicitAgents || readManifest(cwd)?.agents || []), ...agentIds])].sort();
|
|
183
|
+
const desiredAgents = resolveAgentNames(Object.keys(state.skills || {}), explicit);
|
|
184
|
+
const previousAgents = state.agents || [];
|
|
153
185
|
if (targetHasAgents(target)) {
|
|
154
|
-
const
|
|
155
|
-
state.agents =
|
|
186
|
+
const agentResult = reconcileAgents(target, cwd, readDeveloperTier(cwd), previousAgents, desiredAgents);
|
|
187
|
+
state.agents = agentResult.names;
|
|
188
|
+
state.agentCollisions = agentResult.collisions;
|
|
189
|
+
} else {
|
|
190
|
+
state.agents = [];
|
|
191
|
+
state.agentCollisions = [];
|
|
156
192
|
}
|
|
193
|
+
state.explicitAgents = explicit;
|
|
194
|
+
const memoryResult = wireMemory(target, cwd);
|
|
195
|
+
state.memory = { mode: memoryResult.mode, reason: memoryResult.reason, paths: memoryResult.paths };
|
|
196
|
+
const desiredCommands = resolveCommands({
|
|
197
|
+
target,
|
|
198
|
+
skills: Object.keys(state.skills || {}),
|
|
199
|
+
agents: state.agents,
|
|
200
|
+
memoryMode: state.memory?.mode || 'unsupported',
|
|
201
|
+
});
|
|
202
|
+
const commandResult = reconcileCommands(target, cwd, state.commands || [], desiredCommands);
|
|
203
|
+
state.commands = commandResult.names;
|
|
204
|
+
state.commandCollisions = commandResult.collisions;
|
|
157
205
|
state.version = CLI_VERSION;
|
|
158
206
|
writeState(paths.stateFile, state);
|
|
159
207
|
mkdirSync(dirname(versionFile(cwd)), { recursive: true });
|
|
160
208
|
writeFileSync(versionFile(cwd), CLI_VERSION + '\n');
|
|
161
|
-
recordInManifest({ cwd, target, skillIds });
|
|
209
|
+
recordInManifest({ cwd, target, skillIds, agentIds });
|
|
162
210
|
ignoreLocalState(cwd, target);
|
|
163
211
|
return { ...state, backup };
|
|
164
212
|
}
|
|
@@ -196,7 +244,14 @@ export function ignoreLocalState(cwd = process.cwd(), target) {
|
|
|
196
244
|
// repo. The declaration that does travel is .rsc.json, in the root.
|
|
197
245
|
wanted.push(rel(paths.stateFile));
|
|
198
246
|
// Subagents are catalog content, re-materialised by every install and sync.
|
|
199
|
-
for (const
|
|
247
|
+
for (const name of readState(paths.stateFile).agents || []) {
|
|
248
|
+
const file = agentPath(target, cwd, name);
|
|
249
|
+
if (file) wanted.push(rel(file));
|
|
250
|
+
}
|
|
251
|
+
for (const name of readState(paths.stateFile).commands || []) {
|
|
252
|
+
const file = commandPath(target, cwd, name);
|
|
253
|
+
if (file) wanted.push(rel(file));
|
|
254
|
+
}
|
|
200
255
|
}
|
|
201
256
|
// `.rsc`, `/.rsc` and `.rsc/` are the same rule to git. Normalising both ends is what
|
|
202
257
|
// keeps this idempotent against a .gitignore a human wrote in their own spelling.
|
|
@@ -224,7 +279,26 @@ export function listInstalled({ target, home, cwd = process.cwd() }) {
|
|
|
224
279
|
return Object.keys(readState(paths.stateFile).skills);
|
|
225
280
|
}
|
|
226
281
|
|
|
227
|
-
export
|
|
282
|
+
export function listInstalledAgents({ target, home, cwd = process.cwd() }) {
|
|
283
|
+
const state = readState(targetPaths(target, home, cwd).stateFile);
|
|
284
|
+
const explicit = new Set(state.explicitAgents || []);
|
|
285
|
+
return (state.agents || []).map((id) => {
|
|
286
|
+
const agent = agentByName(id);
|
|
287
|
+
return {
|
|
288
|
+
id,
|
|
289
|
+
source: agentNames().includes(id) ? 'base' : explicit.has(id) ? 'explicit' : 'skill',
|
|
290
|
+
skills: agent?.skills || [],
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function listInstalledCommands({ target, home, cwd = process.cwd() }) {
|
|
296
|
+
const state = readState(targetPaths(target, home, cwd).stateFile);
|
|
297
|
+
return (state.commands || []).map((id) => ({ id, path: commandPath(target, cwd, id) }))
|
|
298
|
+
.filter((entry) => entry.path && existsSync(entry.path));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function uninstall({ skillIds = [], agentIds = [], target, home, cwd = process.cwd(), dryRun }) {
|
|
228
302
|
const paths = targetPaths(target, home, cwd);
|
|
229
303
|
const state = readState(paths.stateFile);
|
|
230
304
|
const removed = [];
|
|
@@ -237,9 +311,19 @@ export async function uninstall({ skillIds, target, home, cwd = process.cwd(), d
|
|
|
237
311
|
removed.push(f);
|
|
238
312
|
}
|
|
239
313
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
314
|
+
const previousExplicit = state.explicitAgents || readManifest(cwd)?.agents || [];
|
|
315
|
+
const nextExplicit = previousExplicit.filter((id) => !agentIds.includes(id));
|
|
316
|
+
const remainingSkillIds = Object.keys(state.skills || {}).filter((id) => !skillIds.includes(id));
|
|
317
|
+
const desiredAgents = resolveAgentNames(remainingSkillIds, nextExplicit);
|
|
318
|
+
const staleAgents = (state.agents || []).filter((id) => !desiredAgents.includes(id));
|
|
319
|
+
for (const id of staleAgents) {
|
|
320
|
+
const file = agentPath(target, cwd, id);
|
|
321
|
+
if (file) { managedPaths.push(file); removed.push(file); }
|
|
322
|
+
}
|
|
323
|
+
const explicitChanged = nextExplicit.length !== previousExplicit.length;
|
|
324
|
+
if (!removed.length && !explicitChanged) return removed;
|
|
325
|
+
if (dryRun) return [...new Set(removed)];
|
|
326
|
+
createBackup({ cwd, operation: 'uninstall', target, paths: [...new Set(managedPaths)], cliVersion: CLI_VERSION });
|
|
243
327
|
for (const id of skillIds) {
|
|
244
328
|
const entry = state.skills[id];
|
|
245
329
|
if (!entry) continue;
|
|
@@ -248,8 +332,28 @@ export async function uninstall({ skillIds, target, home, cwd = process.cwd(), d
|
|
|
248
332
|
}
|
|
249
333
|
delete state.skills[id];
|
|
250
334
|
}
|
|
335
|
+
const agentResult = reconcileAgents(target, cwd, readDeveloperTier(cwd), state.agents || [], desiredAgents);
|
|
336
|
+
state.agents = agentResult.names;
|
|
337
|
+
state.agentCollisions = agentResult.collisions;
|
|
338
|
+
state.explicitAgents = nextExplicit;
|
|
339
|
+
const desiredCommands = resolveCommands({
|
|
340
|
+
target,
|
|
341
|
+
skills: remainingSkillIds,
|
|
342
|
+
agents: state.agents,
|
|
343
|
+
memoryMode: state.memory?.mode || 'unsupported',
|
|
344
|
+
});
|
|
345
|
+
const commandResult = reconcileCommands(target, cwd, state.commands || [], desiredCommands);
|
|
346
|
+
removed.push(...commandResult.removed);
|
|
347
|
+
state.commands = commandResult.names;
|
|
348
|
+
state.commandCollisions = commandResult.collisions;
|
|
251
349
|
writeState(paths.stateFile, state);
|
|
252
|
-
|
|
350
|
+
const manifest = readManifest(cwd);
|
|
351
|
+
if (manifest) writeManifest(cwd, {
|
|
352
|
+
...manifest,
|
|
353
|
+
skills: manifest.skills.filter((id) => !skillIds.includes(id)),
|
|
354
|
+
agents: manifest.agents.filter((id) => !agentIds.includes(id)),
|
|
355
|
+
});
|
|
356
|
+
return [...new Set(removed)];
|
|
253
357
|
}
|
|
254
358
|
|
|
255
359
|
export async function syncInstalled({ target, home, cwd = process.cwd(), dryRun = false }) {
|
|
@@ -260,17 +364,20 @@ export async function syncInstalled({ target, home, cwd = process.cwd(), dryRun
|
|
|
260
364
|
// declares a harness and has none. The committed manifest is exactly the declaration
|
|
261
365
|
// to rebuild from, and it is sitting right there.
|
|
262
366
|
const ids = Object.keys(state.skills || {});
|
|
263
|
-
const
|
|
264
|
-
|
|
367
|
+
const manifest = readManifest(cwd);
|
|
368
|
+
const declared = ids.length ? ids : (manifest?.skills || []);
|
|
369
|
+
const declaredAgents = state.explicitAgents?.length ? state.explicitAgents : (manifest?.agents || []);
|
|
370
|
+
if (!declared.length && !declaredAgents.length) return dryRun ? { dryRun: true, synced: [], syncedAgents: [], paths: [] } : { synced: [], syncedAgents: [], backup: null };
|
|
265
371
|
if (dryRun) {
|
|
266
372
|
return {
|
|
267
373
|
dryRun: true,
|
|
268
374
|
synced: declared,
|
|
269
|
-
|
|
375
|
+
syncedAgents: declaredAgents,
|
|
376
|
+
paths: managedPathsForInstall({ skillIds: declared, agentIds: declaredAgents, target, home, cwd }),
|
|
270
377
|
};
|
|
271
378
|
}
|
|
272
|
-
const nextState = await applyInstall({ skillIds: declared, target, home, cwd, operation: 'sync' });
|
|
273
|
-
return { synced: declared, backup: nextState.backup };
|
|
379
|
+
const nextState = await applyInstall({ skillIds: declared, agentIds: declaredAgents, target, home, cwd, operation: 'sync' });
|
|
380
|
+
return { synced: declared, syncedAgents: declaredAgents, backup: nextState.backup };
|
|
274
381
|
}
|
|
275
382
|
|
|
276
383
|
// Remove EVERYTHING rsc put in this project: installed skills across all targets,
|
|
@@ -293,20 +400,30 @@ export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRu
|
|
|
293
400
|
for (const id of Object.keys(state.skills || {})) {
|
|
294
401
|
for (const f of state.skills[id].files || []) drop(f, true);
|
|
295
402
|
}
|
|
403
|
+
for (const name of state.commands || []) {
|
|
404
|
+
const file = commandPath(target, cwd, name);
|
|
405
|
+
if (file) drop(file);
|
|
406
|
+
}
|
|
407
|
+
for (const name of state.agents || []) {
|
|
408
|
+
const file = agentPath(target, cwd, name);
|
|
409
|
+
if (file) drop(file);
|
|
410
|
+
}
|
|
296
411
|
drop(paths.stateFile);
|
|
297
412
|
}
|
|
298
|
-
//
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
413
|
+
// Unwiring mutates shared config files, so only run it for real (dry runs report
|
|
414
|
+
// those files without touching them).
|
|
415
|
+
if (!dryRun) {
|
|
416
|
+
removed.push(...unwireHook(target, paths));
|
|
417
|
+
removed.push(...unwireMemory(target, cwd));
|
|
418
|
+
} else {
|
|
419
|
+
removed.push(...memoryArtifactsPresent(target, cwd));
|
|
305
420
|
}
|
|
421
|
+
// A lost state file also loses proof of ownership. Leave same-named user files
|
|
422
|
+
// behind rather than guessing from a catalog id and deleting their work.
|
|
306
423
|
}
|
|
307
424
|
drop(join(cwd, '.rsc'), true);
|
|
308
425
|
if (withDocs) drop(join(cwd, '02-DOCS'), true);
|
|
309
|
-
return removed;
|
|
426
|
+
return [...new Set(removed)];
|
|
310
427
|
}
|
|
311
428
|
|
|
312
429
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
@@ -25,6 +25,9 @@ import { homedir } from 'node:os';
|
|
|
25
25
|
import { loadManifest } from './manifest.js';
|
|
26
26
|
import { targetPaths } from '../../targets/index.js';
|
|
27
27
|
import { developerAgentPath, targetHasAgents } from '../../targets/agents.js';
|
|
28
|
+
import { targetHasCommands, commandPath } from '../../targets/commands.js';
|
|
29
|
+
import { inspectMemoryWiring } from '../../targets/memory.js';
|
|
30
|
+
import { readState } from './state.js';
|
|
28
31
|
|
|
29
32
|
// AGENT_TARGETS is not exported, so derive dir + extension from the one path helper
|
|
30
33
|
// that owns it — duplicating that table is how two copies drift apart. The extension
|
|
@@ -101,6 +104,14 @@ export function listSkills({ target, home, cwd = process.cwd() } = {}) {
|
|
|
101
104
|
return out.sort((a, b) => a.id.localeCompare(b.id) || a.scope.localeCompare(b.scope));
|
|
102
105
|
}
|
|
103
106
|
|
|
107
|
+
export function listCommands({ target, cwd = process.cwd() } = {}) {
|
|
108
|
+
if (!targetHasCommands(target)) return { supported: false, commands: [] };
|
|
109
|
+
const state = readState(targetPaths(target, undefined, cwd).stateFile);
|
|
110
|
+
const commands = (state.commands || []).map((id) => ({ id, path: commandPath(target, cwd, id) }))
|
|
111
|
+
.filter((entry) => entry.path && existsSync(entry.path));
|
|
112
|
+
return { supported: true, commands };
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
// Keep the discriminator: a description's value is its `NOT x (that is y)` boundary,
|
|
105
116
|
// so never cut it off. Trim the middle instead when the whole thing is long.
|
|
106
117
|
export function shortDesc(d, limit = 220) {
|
|
@@ -124,6 +135,8 @@ export function capabilities({ target, home, cwd = process.cwd(), full = false }
|
|
|
124
135
|
const skills = listSkills({ target, home, cwd });
|
|
125
136
|
const installedIds = new Set(skills.map((s) => s.id));
|
|
126
137
|
const { supported, agents } = listAgents({ target, home, cwd });
|
|
138
|
+
const commandReport = listCommands({ target, cwd });
|
|
139
|
+
const state = readState(targetPaths(target, home, cwd).stateFile);
|
|
127
140
|
return {
|
|
128
141
|
target,
|
|
129
142
|
agentsSupported: supported,
|
|
@@ -133,6 +146,14 @@ export function capabilities({ target, home, cwd = process.cwd(), full = false }
|
|
|
133
146
|
.map((s) => (full ? { id: s.id, description: shortDesc(s.description) } : { id: s.id }))
|
|
134
147
|
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
135
148
|
agents,
|
|
149
|
+
commandsSupported: commandReport.supported,
|
|
150
|
+
commands: commandReport.commands,
|
|
151
|
+
memory: inspectMemoryWiring(
|
|
152
|
+
target,
|
|
153
|
+
cwd,
|
|
154
|
+
state.memory,
|
|
155
|
+
Boolean(Object.keys(state.skills || {}).length || state.agents?.length || state.commands?.length),
|
|
156
|
+
),
|
|
136
157
|
};
|
|
137
158
|
}
|
|
138
159
|
|
package/scripts/lib/domains.js
CHANGED
|
@@ -17,14 +17,14 @@ export const DOMAINS = [
|
|
|
17
17
|
{ title: 'AI — build it in', ids: ['building-agents', 'rag', 'embeddings-search', 'prompt-engineering', 'llm-pipeline', 'agent-eval', 'chatbot', 'ai-media', 'replicate-images', 'structured-extraction', 'agent-safety', 'cost-tracking'] },
|
|
18
18
|
{ title: 'AI — run it on', ids: ['replicate', 'runpod', 'modal', 'huggingface', 'ollama', 'together-fireworks', 'fal'] },
|
|
19
19
|
{ title: 'AI — train it', ids: ['machine-learning', 'deep-learning', 'nlp', 'finetuning', 'training-data', 'unsloth', 'open-weights', 'vllm'] },
|
|
20
|
-
{ title: 'Languages', ids: ['typescript', 'python', 'java', 'csharp-dotnet', 'php', 'ruby', 'cpp', 'elixir', 'bash-scripting', 'sql', 'go'
|
|
21
|
-
{ title: 'Frameworks & app stacks', ids: ['fastapi', 'nextjs', 'react', 'react-native', 'vue-nuxt', 'angular', 'svelte', 'astro', 'solid-js', 'htmx', 'nodejs', 'nestjs', 'django', 'laravel', 'rails', 'spring-boot', 'phoenix', 'flutter', 'swift-ios', 'kotlin-android', 'compose-multiplatform', 'expo', 'tauri', 'electron', 'rust', 'wordpress', 'shopify', 'no-code-app', 'chrome-extension', 'api-design'
|
|
20
|
+
{ title: 'Languages', ids: ['typescript', 'python', 'java', 'csharp-dotnet', 'php', 'ruby', 'cpp', 'elixir', 'bash-scripting', 'sql', 'go'] },
|
|
21
|
+
{ title: 'Frameworks & app stacks', ids: ['fastapi', 'nextjs', 'react', 'react-native', 'vue-nuxt', 'angular', 'svelte', 'astro', 'solid-js', 'htmx', 'nodejs', 'nestjs', 'django', 'laravel', 'rails', 'spring-boot', 'phoenix', 'flutter', 'swift-ios', 'kotlin-android', 'compose-multiplatform', 'expo', 'tauri', 'electron', 'rust', 'wordpress', 'shopify', 'no-code-app', 'chrome-extension', 'api-design'] },
|
|
22
22
|
{ title: 'Game development', ids: ['godot', 'unity', 'unreal', 'game-design', 'game-storytelling', 'level-design', 'gamedev-shaders', 'gamedev-multiplayer', 'gamedev-physics', 'gamedev-pathing', 'gamedev-shipping'] },
|
|
23
23
|
{ title: 'Databases & data layer', ids: ['postgresdb', 'mysql', 'mongodb', 'redis', 'supabase', 'neon', 'planetscale', 'sqlite-turso', 'prisma-orm', 'drizzle-orm', 'firebase', 'dynamodb', 'vector-db', 'clickhouse-analytics', 'duckdb', 'db-migrations', 'backups'] },
|
|
24
24
|
{ title: 'Ship & operate — platforms', ids: ['vercel', 'netlify', 'cloudflare', 'railway', 'render', 'fly-io', 'coolify', 'hetzner', 'digitalocean', 'aws-essentials', 'gcp-essentials'] },
|
|
25
25
|
{ title: 'Ship & operate — devops', ids: ['docker', 'github-actions', 'git-workflow', 'domains-dns', 'monitoring', 'email-deliverability', 'scaling', 'deployment', 'deprecation'] },
|
|
26
26
|
{ title: 'Ship & operate — quality & security', ids: ['code-review', 'security-scan', 'secure-coding', 'testing-py', 'testing-web', 'testing-go', 'e2e-testing', 'accessibility', 'performance', 'error-handling', 'observability'] },
|
|
27
|
-
{ title: 'Motion & interface craft', ids: ['
|
|
27
|
+
{ title: 'Motion & interface craft', ids: ['motion-craft', 'ui-engineering', 'variant-explorer'] },
|
|
28
28
|
{ title: 'Design & content craft', ids: ['design-loop', 'design', 'design-dna', 'presentations', 'course-storytelling', 'course-builder', 'technical-writing', 'translation-l10n'] },
|
|
29
29
|
{ title: 'Knowledge & meta', ids: ['knowledge-ops', 'codebase-onboarding', 'research-ops', 'decision-records', 'continuous-learning', 'skill-scout', 'context-budget', 'roast-me', 'show-me', 'eli5', 'fable-operator'] },
|
|
30
30
|
];
|
|
@@ -15,7 +15,7 @@ import { join } from 'node:path';
|
|
|
15
15
|
export const MANIFEST = '.rsc.json';
|
|
16
16
|
export const manifestPath = (cwd = process.cwd()) => join(cwd, MANIFEST);
|
|
17
17
|
|
|
18
|
-
const KEYS = ['version', 'targets', 'skills', 'ownSkills', 'catalogVersion', 'tier', 'optOuts'];
|
|
18
|
+
const KEYS = ['version', 'targets', 'skills', 'agents', 'ownSkills', 'catalogVersion', 'tier', 'optOuts', 'memory'];
|
|
19
19
|
|
|
20
20
|
export function readManifest(cwd = process.cwd()) {
|
|
21
21
|
const file = manifestPath(cwd);
|
|
@@ -32,10 +32,12 @@ export function readManifest(cwd = process.cwd()) {
|
|
|
32
32
|
version: raw.version ?? 1,
|
|
33
33
|
targets: raw.targets || [],
|
|
34
34
|
skills: raw.skills || [],
|
|
35
|
+
agents: raw.agents || [],
|
|
35
36
|
ownSkills: raw.ownSkills || [],
|
|
36
37
|
catalogVersion: raw.catalogVersion ?? null,
|
|
37
38
|
tier: raw.tier ?? null,
|
|
38
39
|
optOuts: raw.optOuts || [],
|
|
40
|
+
memory: raw.memory ?? undefined,
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -45,6 +47,7 @@ export function writeManifest(cwd, manifest) {
|
|
|
45
47
|
const out = {};
|
|
46
48
|
for (const k of KEYS) if (manifest[k] !== undefined) out[k] = manifest[k];
|
|
47
49
|
out.version ??= 1;
|
|
50
|
+
out.agents ??= [];
|
|
48
51
|
const ordered = {};
|
|
49
52
|
for (const k of KEYS) if (out[k] !== undefined) ordered[k] = out[k];
|
|
50
53
|
const file = manifestPath(cwd);
|