@ericrisco/rsc 0.1.29 → 0.1.30

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.29",
2
+ "version": "0.1.30",
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.29",
3
+ "version": "0.1.30",
4
4
  "description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,6 +4,7 @@ import { join, dirname } 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 { targetHasAgents, writeDeveloperAgent, removeDeveloperAgent, developerAgentPath } from '../targets/agents.js';
7
8
  import { readState, writeState } from './lib/state.js';
8
9
  import { createBackup } from './lib/backups.js';
9
10
 
@@ -62,6 +63,7 @@ function managedPathsForInstall({ skillIds, target, home, cwd }) {
62
63
  const paths = targetPaths(target, home, cwd);
63
64
  const plan = planInstall({ skillIds, target, home, cwd });
64
65
  const out = [paths.stateFile, versionFile(cwd), baseVersionsFile(cwd)];
66
+ if (targetHasAgents(target)) out.push(developerAgentPath(target, cwd), join(cwd, '.rsc', 'developer.json'));
65
67
  for (const step of plan) {
66
68
  if (step.kind === 'skill') {
67
69
  out.push(step.to, baseDir(step.id, cwd));
@@ -94,6 +96,11 @@ export async function applyInstall({ skillIds, target, home, cwd = process.cwd()
94
96
  }
95
97
  }
96
98
  writeBaseVersions(cwd, baseVersions);
99
+ // Install the `developer` subagent for targets that support file-based agents (Claude
100
+ // Code, Cursor, OpenCode, Gemini, Copilot, Junie, Kiro, Codex). It runs at the balanced
101
+ // tier by default (never light/Haiku); the tier lives in .rsc/developer.json, set by
102
+ // `init` at onboarding and honored on every (re)install/sync.
103
+ if (targetHasAgents(target)) state.agents = writeDeveloperAgent(target, cwd).length ? ['developer'] : [];
97
104
  state.version = CLI_VERSION;
98
105
  writeState(paths.stateFile, state);
99
106
  mkdirSync(dirname(versionFile(cwd)), { recursive: true });
@@ -174,6 +181,9 @@ export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRu
174
181
  }
175
182
  // unwireHook mutates files, so only run it for real (dry runs skip it).
176
183
  if (!dryRun) removed.push(...unwireHook(target, paths));
184
+ // Remove the installed developer subagent (agent-capable targets only).
185
+ const agentFile = developerAgentPath(target, cwd);
186
+ if (agentFile) drop(agentFile);
177
187
  }
178
188
  drop(join(cwd, '.rsc'), true);
179
189
  if (withDocs) drop(join(cwd, '02-DOCS'), true);
@@ -158,6 +158,12 @@ test output. You merge the branches, then run the *combined* test suite before t
158
158
  green-in-isolation is not green-together. Hand the orchestration to `parallel`; keep the TDD
159
159
  discipline inside each branch.
160
160
 
161
+ **Dispatch implementation work to the `developer` subagent.** rsc installs a `developer` agent
162
+ pinned to the balanced tier (Sonnet by default, chosen at onboarding, never `light`). When you
163
+ delegate a task or fan out via `parallel`, dispatch it to that agent (e.g. Claude Code
164
+ `subagent_type: developer`) so the bulk of TDD execution runs on the cheaper-but-capable model
165
+ while you stay on the session model to orchestrate. Full rationale: `../sdd/references/model-routing.md`.
166
+
161
167
  ## Model tier — `balanced` (opt-in routing)
162
168
 
163
169
  This phase's default model tier is **`balanced`** — it is the bulk of TDD execution: cost-sensitive, with quality balanced handles well. Routing is **off** unless `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`. When on: resolve this phase's tier (`models.overrides` wins over `models.phases`), map it to a model via `models.tiers`, and apply per `../sdd/references/model-routing.md` — announce the switch per the accompaniment dial when it differs from the session model, and dispatch any `Task`/`parallel` subagents on that model (this is where routing pays off most — fan-out runs on `balanced` while a hard sub-problem can be escalated to `heavy`). Routing off or no profile → honor the session model silently. Never fake a switch a tool can't make; skip routing on a one-line change.
@@ -55,6 +55,14 @@ Before discovery, before any recommendation, write the profile to `02-DOCS` and
55
55
 
56
56
  If `02-DOCS/` does not yet exist (greenfield), create `02-DOCS/wiki/harness/` now — just enough to hold these two files. That, plus the `CLAUDE.md` Knowledge-map link, is everything `init` writes; ALL other `01-TOOLS/` + `02-DOCS/` scaffolding is the `harness` skill's job.
57
57
 
58
+ ### Step 4 — Propose the developer model (the implementation subagent)
59
+
60
+ rsc installs a **`developer`** subagent (the fan-out / implementation worker) for every assistant that supports file-based agents (Claude Code, Cursor, OpenCode, Gemini, Copilot, Junie, Kiro, Codex). It runs at the **balanced** tier by default — **Sonnet** on Anthropic tools (the provider's mid model elsewhere) — and **never** the cheapest `light` model, which is too weak to build with. Offer the choice once, calibrated to the dial:
61
+
62
+ - *"La implementación la hará un sub-agente `developer`. ¿Qué modelo? **balanced / Sonnet** (rápido y económico — recomendado) o **heavy / Opus** (máxima calidad, más caro)."* Never offer `light`/Haiku.
63
+
64
+ Record it to `.rsc/developer.json` (`{ "tier": "balanced" }` or `"heavy"`) and re-run the install/sync so the agent files adopt it. If you skip the question (e.g. L0), default to `balanced`. The default is also written at install time, so the developer agent is always set even when onboarding never asks — "propose at onboarding, and also when it isn't set."
65
+
58
66
  ### Opt-out marker — `.rsc/.no-harness`
59
67
 
60
68
  A freshly-installed session auto-starts `init` while `02-DOCS/wiki/harness/user-profile.md` is absent (the `suggest` Onboarding gate + claude's SessionStart hook). If the user does not want a harness in this repo (e.g. they installed only code skills), write an empty `.rsc/.no-harness` — this permanently silences the auto-start here even before a profile exists. Completing first contact (which writes `user-profile.md`) also silences it. The marker is project-local; commit it so the "no harness here" decision is shared by the team.
@@ -75,6 +75,8 @@ Each subagent still owns its own discipline inside its scope — TDD via `implem
75
75
 
76
76
  **Per-unit model tier (when routing is enabled).** `parallel` has *no fixed tier* — this is the most concrete place per-phase model routing pays off. When `models.enabled: true` in `02-DOCS/wiki/sdd/config.yaml`, give each unit the tier of the *kind of work it does*, not one tier for the whole fan-out: an implement-type unit → `balanced`, a scan/research/boilerplate unit → `light`, a unit doing genuine design or root-cause reasoning → `heavy`. Resolve the tier to a concrete model via `models.tiers` and **dispatch that subagent on that model** (e.g. Claude Code's `model` field on the Task/subagent) — real routing, independent of the session model. If routing is off or no profile exists, dispatch on the session model and say nothing. Full protocol: `../sdd/references/model-routing.md`.
77
77
 
78
+ **The `developer` agent is the default worker.** rsc installs a `developer` subagent pinned to the **balanced** tier (Sonnet by default; the user's onboarding choice in `.rsc/developer.json`, never `light`). For implement-type units, **dispatch to the `developer` agent** (e.g. Claude Code `subagent_type: developer`) — that's the deliberate cost cap the user asked for. Only escalate a genuinely heavy unit (real design / root-cause) to a heavy model, and only when routing is enabled; otherwise the `developer` agent's balanced model is the floor and the ceiling.
79
+
78
80
  ### Skill resolution feedback
79
81
 
80
82
  Every subagent result must report:
@@ -155,6 +155,24 @@ To target another provider, change `models.provider` and set `models.tiers` to t
155
155
  These are starting points — set them to whatever your account actually has. The tiers are the
156
156
  contract; the concrete names are yours to edit.
157
157
 
158
+ ## The `developer` subagent (the installed implementation worker)
159
+
160
+ rsc installs a **`developer`** subagent into every assistant that supports file-based agents
161
+ (Claude Code `.claude/agents/`, Cursor, OpenCode, Gemini, Copilot, Junie, Kiro, Codex) when the
162
+ harness is installed. It is the concrete embodiment of "run the fan-out on a smaller model":
163
+
164
+ - It is pinned to the **balanced** tier — **Sonnet** for Anthropic-backed tools, the provider's
165
+ mid model elsewhere (Gemini Flash, GPT-mini). It is **never** `light`/Haiku — that tier is too
166
+ weak to build with. The floor for implementation is balanced.
167
+ - The tier is chosen at **onboarding** (`init`, Step 4): balanced (default) or heavy. It is stored
168
+ in `.rsc/developer.json` and the installer writes the per-target agent file with the resolved
169
+ model on every (re)install/sync. If onboarding never asks, the install-time default is balanced.
170
+ - `implement` and `parallel` **dispatch implementation/fan-out work to this agent**, so the
171
+ session model orchestrates while the cheaper-but-capable model does the bulk of the typing.
172
+ - On assistants without file-based agents (Amp, Zed, Windsurf, Cline, Roo, Continue, Aider, Jules,
173
+ Antigravity), there is no agent file — the developer tier is advisory there, like the rest of
174
+ routing. The per-target model id is an editable default (see the provider table above).
175
+
158
176
  ## Result-envelope `model` field
159
177
 
160
178
  Every phase's result envelope carries the model it ran on, so the chain is auditable:
@@ -0,0 +1,98 @@
1
+ // rsc developer agent — installed with the harness for every target that supports
2
+ // file-based subagents with a per-agent model. The agent runs at the `balanced` tier
3
+ // (never `light`/Haiku): Sonnet for Anthropic-backed tools, the provider's mid model
4
+ // elsewhere. The chosen tier (balanced default, or heavy) lives in `.rsc/developer.json`,
5
+ // written by `init` at onboarding and read here so re-syncs honor it.
6
+ import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+
9
+ // Concrete model per provider per tier. June 2026 defaults — EDIT to your account's
10
+ // models; the TIER is the contract, the id is yours to change. `light` is deliberately
11
+ // absent: the developer floor is `balanced`.
12
+ const TIER_MODEL = {
13
+ anthropic: { balanced: 'claude-sonnet-4-6', heavy: 'claude-opus-4-8' },
14
+ google: { balanced: 'gemini-2.5-flash', heavy: 'gemini-2.5-pro' },
15
+ openai: { balanced: 'gpt-5.1-mini', heavy: 'gpt-5.1' },
16
+ };
17
+
18
+ // Per-target agent capability: where the file goes, its format, and how the model value
19
+ // is written for that tool. Targets absent here have no installable file-based agents
20
+ // (Amp/Zed/Windsurf/Cline/Roo/Continue/Aider/Jules/Antigravity) — there the developer
21
+ // model is advisory (model-routing), not a file.
22
+ const AGENT_TARGETS = {
23
+ claude: { dir: '.claude/agents', ext: '.md', format: 'md', model: (t) => (t === 'heavy' ? 'opus' : 'sonnet') },
24
+ junie: { dir: '.junie/agents', ext: '.md', format: 'md', model: (t) => (t === 'heavy' ? 'opus' : 'sonnet') },
25
+ cursor: { dir: '.cursor/agents', ext: '.md', format: 'md', model: (t) => TIER_MODEL.anthropic[t] },
26
+ opencode: { dir: '.opencode/agents', ext: '.md', format: 'md', mode: 'subagent', model: (t) => `anthropic/${TIER_MODEL.anthropic[t]}` },
27
+ gemini: { dir: '.gemini/agents', ext: '.md', format: 'md', model: (t) => TIER_MODEL.google[t] },
28
+ copilot: { dir: '.github/agents', ext: '.agent.md', format: 'md', model: (t) => TIER_MODEL.anthropic[t] },
29
+ kiro: { dir: '.kiro/agents', ext: '.json', format: 'json', model: (t) => (t === 'heavy' ? 'claude-opus-4' : 'claude-sonnet-4') },
30
+ codex: { dir: '.codex/agents', ext: '.toml', format: 'toml', model: (t) => TIER_MODEL.openai[t] },
31
+ };
32
+
33
+ export const AGENT_TARGET_IDS = Object.keys(AGENT_TARGETS);
34
+ export function targetHasAgents(target) { return Boolean(AGENT_TARGETS[target]); }
35
+
36
+ const NAME = 'developer';
37
+ const DESC = 'Implementation worker: turns an approved spec+plan into working, tested code under strict TDD (red->green->refactor), one task at a time. The rsc SDD fan-out/implementation hand.';
38
+ const BODY = `You are the **developer** subagent for this project — the hands of the rsc SDD chain. You execute a planned, approved task into working, tested code. You do NOT design features.
39
+
40
+ - Work **test-first**: smallest failing test (RED), least code to pass it (GREEN), then refactor on green. A test that never failed proves nothing.
41
+ - One task at a time; keep the diff to that task's scope — no "while I'm here".
42
+ - Follow the project's spec, plan and constitution under \`02-DOCS/wiki/sdd/\`, and borrow test mechanics from the stack skill (fastapi/go/nextjs/flutter/...).
43
+ - If there is no approved spec + plan for non-trivial feature work, STOP and route to \`specify\` — do not write feature code.
44
+ - Log non-obvious decisions to \`02-DOCS/wiki/sdd/decisions.md\`. Report your diff + test output at the end.
45
+
46
+ Full discipline lives in the \`implement\` skill.`;
47
+
48
+ // `.rsc/developer.json` — the chosen tier (balanced default; never light). `init` writes
49
+ // it on the onboarding answer; the installer reads it so every (re)install/sync matches.
50
+ const tierFile = (cwd) => join(cwd, '.rsc', 'developer.json');
51
+ export function readDeveloperTier(cwd) {
52
+ try {
53
+ return JSON.parse(readFileSync(tierFile(cwd), 'utf8')).tier === 'heavy' ? 'heavy' : 'balanced';
54
+ } catch { return 'balanced'; }
55
+ }
56
+ export function writeDeveloperTier(cwd, tier) {
57
+ const t = tier === 'heavy' ? 'heavy' : 'balanced';
58
+ mkdirSync(dirname(tierFile(cwd)), { recursive: true });
59
+ writeFileSync(tierFile(cwd), `${JSON.stringify({ tier: t }, null, 2)}\n`);
60
+ return t;
61
+ }
62
+
63
+ function renderMd(spec, model) {
64
+ const fm = ['---', `name: ${NAME}`, `description: "${DESC}"`, `model: ${model}`];
65
+ if (spec.mode) fm.push(`mode: ${spec.mode}`);
66
+ fm.push('---', '');
67
+ return `${fm.join('\n')}${BODY}\n`;
68
+ }
69
+ const renderJson = (model) => `${JSON.stringify({ name: NAME, description: DESC, model, prompt: BODY }, null, 2)}\n`;
70
+ function renderToml(model) {
71
+ const esc = (s) => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
72
+ // body as a TOML multiline LITERAL string ('''…''') — no escape processing.
73
+ return `name = "${NAME}"\ndescription = "${esc(DESC)}"\nmodel = "${model}"\ndeveloper_instructions = '''\n${BODY}\n'''\n`;
74
+ }
75
+
76
+ export function developerAgentPath(target, cwd) {
77
+ const spec = AGENT_TARGETS[target];
78
+ return spec ? join(cwd, ...spec.dir.split('/'), `${NAME}${spec.ext}`) : null;
79
+ }
80
+
81
+ export function writeDeveloperAgent(target, cwd, tier = readDeveloperTier(cwd)) {
82
+ const spec = AGENT_TARGETS[target];
83
+ if (!spec) return [];
84
+ const model = spec.model(tier);
85
+ const content = spec.format === 'json' ? renderJson(model)
86
+ : spec.format === 'toml' ? renderToml(model)
87
+ : renderMd(spec, model);
88
+ const path = developerAgentPath(target, cwd);
89
+ mkdirSync(dirname(path), { recursive: true });
90
+ writeFileSync(path, content);
91
+ return [path];
92
+ }
93
+
94
+ export function removeDeveloperAgent(target, cwd) {
95
+ const path = developerAgentPath(target, cwd);
96
+ if (path && existsSync(path)) { rmSync(path, { force: true }); return [path]; }
97
+ return [];
98
+ }