@rig-c/core 3.0.0 → 3.1.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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/rig-c/core/main/assets/banner.svg" alt="rigc — AI-authored 2D rigging and animation, verified before it is written" width="100%" />
2
+ <img src="https://raw.githubusercontent.com/rig-c/core/main/assets/banner.svg" alt="@rig-c/core — AI-authored 2D rigging and animation, verified before it is written" width="100%" />
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -8,6 +8,8 @@
8
8
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-38BDF8.svg?style=flat-square" alt="license" /></a>
9
9
  </p>
10
10
 
11
+ # @rig-c/core
12
+
11
13
  **AI-authored 2D rigging and animation, verified before it is written.** rigc is
12
14
  a rig compiler: a rig spec and a motion spec in, skeleton data out — in the Spine 4.3
13
15
  format, with rigc's own compiled model beside it —
package/docs/AUTHORING.md CHANGED
@@ -139,7 +139,7 @@ commands take it and what its default is.
139
139
  | `check --candidate <dir> --frames <dir> [--out <dir>]` | the candidate against reference pictures — the only instrument here that can see a *wrong animation*. `--out` also writes, for every frame the table lists, the picture its figures came from: reference, candidate, difference and overlay at native size <a id="cmd-check"></a> |
140
140
  | `bench <rung> --candidate <dir>` | one rung of the benchmark ladder <a id="cmd-bench"></a> |
141
141
  | `guide <RULE>` / `guide §<n>[.<m>[.<k>]] [--all]` | the guide's row for the rule a failure line names — every `FAIL` line, `vote --record` refusal, `ingest` finding and coded compile error ends with ` (see rigc guide <RULE>)`; only legacy uncoded library errors point at `§5.1` — or a section's heading, own prose and subsection index, read from the installed `docs/AUTHORING.md` when it runs. `--all` includes the subsection bodies; a leaf prints its full text either way. Sizes describe full ledger spans. `--all` with a rule is refused, exit 2; an unknown name is refused with the nearest real ones, exit 1 <a id="cmd-guide"></a> |
142
- | `skills install [--dir …] [--copy]` | links every agent skill the package ships into `.agents/skills` (or `--dir`) as relative symlinks, or copies them with `--copy` — where Codex, Gemini CLI and Antigravity look. A second run has nothing to do; an entry already there that is not this command's is refused by name and nothing is written <a id="cmd-skills-install"></a> |
142
+ | `skills install [--dir …] [--copy]` | links every agent skill this package and any installed `@rig-c/*` or rig-c alias package ship (`PACKAGE_ROOT/skills` and every `node_modules/@rig-c/*/skills/<name>/SKILL.md` and alias reachable from the project's `node_modules`) into `.agents/skills` (or `--dir`) as relative symlinks, or copies them with `--copy` — where Codex, Gemini CLI and Antigravity look — reporting which package each skill came from (`[<package>]`). In a consumer without `@rig-c/core` installed, reach the installer with `bunx @rig-c/core skills install`. A second run has nothing to do; an entry already there that is not this command's, or a duplicate skill name across two distinct packages (an alias or symlink resolving to the same `realpathSync` folder is deduplicated), is refused by name and nothing is written <a id="cmd-skills-install"></a> |
143
143
  | `review <dir> […]` | the human review of a build, handed to the `rig-review` bin of `@rig-c/review` installed beside the package; rigc does not depend on it and never installs it. `rigc review <dir> [--reference <video>] [--animation <name>] [--compare <dir>]` is `rig-review open <dir> …` with those flags 1:1 and prints the loopback URL to give a person; a first argument that is one of review's own words (`open`, `check`, `feedback`, `control`, `strip`, `compare`) or starts with `-` (`--help`, `--`) is handed over untouched. stdout is review's byte for byte, stderr review's plus the launcher's own refusals, the exit code review's. Refused by name, exit 2, before the bin starts: `REVIEW_OUTPUT_MISSING <path>` (the target is not a directory); `REVIEW_NOT_INSTALLED` with two lines, `bun add -d @rig-c/review` and `bunx @rig-c/review open <dir> …` carrying the flags given — the same on a terminal and off, with nothing installed or written; `REVIEW_BUN_TOO_OLD: Bun <found>, @rig-c/review needs <required>`, the floor read off the installed package's `engines.bun`; and an empty argument (`""`) under a Bun that drops one (Bun 1.2.0, measured by a probe), `rigc review: argument N is empty, and Bun <version>, the Bun running this, drops an empty argument when it starts a process — …`. docs/AUTHORING.md lists every line <a id="cmd-review"></a> |
144
144
 
145
145
  `diff`, `bonedist`, `check` and `bench` measure against something you were given; the
@@ -405,7 +405,7 @@ What the flags mean:
405
405
  | `--min-visible` | `chainfit` only: below this share of a part surviving the parts drawn over it, the placement is refused `occluded` instead of reported flat (default `0.25`) — §12.4 |
406
406
  | `--passes` | `chainfit` only: how many times the occluder masks are rebuilt from the answers and the fit rerun (default `2`) — §12.4 |
407
407
  | `--anchor-residual` | `chainfit` only: the residual a `pose` placement must be within to anchor a chain (default `0.16`) — §12.2 |
408
- | `--dir` | `skills install` only: the directory to install the agent skills into, resolved against your working directory (default `.agents/skills`, which Codex, Gemini CLI and Antigravity read). The skills are the `skills/` directory of the package the command runs from, never the working directory's |
408
+ | `--dir` | `skills install` only: the directory to install the agent skills into, resolved against your working directory (default `.agents/skills`, which Codex, Gemini CLI and Antigravity read). The skills are scanned from the `skills/` directory of the package the command runs from and every `@rig-c/*` and rig-c alias package in the project's `node_modules`, never a loose `skills/` in the working directory |
409
409
  | `--copy` | `skills install` only: copy each skill folder instead of writing a relative symlink to it. A copy is not reached by an upgrade of the package; one that is no longer the package's bytes is refused on the next run, naming the first file that differs |
410
410
 
411
411
  `render` also takes `--fps <n>` (the rate it samples at, default 12 — the same
@@ -438,10 +438,28 @@ each under `sets[].dir` — read the folder from there, not from the animation's
438
438
  the subsections' bodies. A leaf prints its full text either way — §5 says when to reach for it.
439
439
 
440
440
  `skills install` is the other command that is not about a rig: it puts the agent
441
- skills the package ships where an agent host looks for them. Run it through the
441
+ skills this package and every installed `@rig-c/*` or rig-c alias package ship where
442
+ an agent host looks for them, scanning `PACKAGE_ROOT/skills` and every
443
+ `node_modules/@rig-c/*/skills/<name>/SKILL.md` (and any unscoped alias package
444
+ shipping `skills/<name>/SKILL.md` that resolves by `realpathSync` into `@rig-c/*` or
445
+ declares a `github.com/rig-c/` `repository` in `package.json` — never by directory
446
+ name alone) reachable from the project's `node_modules`, and
447
+ reporting `[<package>]` on each line. Run it through the
442
448
  project's own install — `bun rigc skills install` — so the links point into that
443
- project's `node_modules/rig-c/skills/`. A second run has nothing to do and
444
- exits 0. An entry already at `<dir>/<name>` that is not a link to the same folder
449
+ project's `node_modules/rig-c/skills/` (or `node_modules/@rig-c/core/skills/`) and
450
+ its sibling packages; in a
451
+ consumer without `@rig-c/core` installed (for example, a project with only
452
+ `@rig-c/play` or a host adapter), run `bunx @rig-c/core skills install --copy`
453
+ so every skill is written as a plain folder instead of linking `@rig-c/core`'s
454
+ skills into a temporary `.bunx` cache that can be emptied. A second run has nothing to do and
455
+ exits 0. An alias or symlink that resolves (`realpathSync`) to a skill folder
456
+ already discovered is deduplicated; two copies of the same product (`rig-c` ↔
457
+ `@rig-c/core`, `rig-<name>` ↔ `@rig-c/<name>`, or a project copy beside the running
458
+ package) choose the copy inside the project's `node_modules` first and the scoped
459
+ package (`@rig-c/*`) over the unscoped alias (`rig-*`), printing which copy was
460
+ chosen; two different products shipping the same skill
461
+ name refuse the call, exit 1, naming both sources (`<n> duplicate skill name(s)
462
+ across packages, and nothing was written:`). An entry already at `<dir>/<name>` that is not a link to the same folder
445
463
  (or, under `--copy`, not the same bytes) is refused, exit 1, and **nothing is
446
464
  written** — every such entry is named with what is there and what was required:
447
465
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rig-c/core",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@ index within that same limit; only legacy uncoded library errors point at `§5.1
28
28
  ## Non-negotiables
29
29
 
30
30
  - **Validation is never bypassed.** `build` writes nothing on a red gate; there is
31
- no `--no-validate`, and none may be added — AUTHORING §0 says so in as many
31
+ no flag to skip it, and none may be added — AUTHORING §0 says so in as many
32
32
  words. Correctness is the whole of the reason: the assertions are what make the
33
33
  output trustworthy, and they are held to the official parser's verdicts in the
34
34
  repository's CI — all but the parse itself, which runs only where the runtime is
@@ -46,29 +46,38 @@ index within that same limit; only legacy uncoded library errors point at `§5.1
46
46
  ## Install
47
47
 
48
48
  ```shell
49
- bunx @rig-c/core --help # run it without installing
50
- bun add -d @rig-c/core # or pin it in the project; the command is `rigc`
51
- bun rigc skills install # then link these skills into .agents/skills
49
+ bunx @rig-c/core --help # run it without installing
50
+ bun add -d @rig-c/core # or pin it in the project; the command is `rigc`
51
+ bun rigc skills install # link this package's and every installed @rig-c/* package's skills into .agents/skills
52
+ bunx @rig-c/core skills install --copy # in a consumer without @rig-c/core installed (for example, only @rig-c/play)
52
53
  ```
53
54
 
54
55
  Codex, Gemini CLI and Antigravity read skills from `.agents/skills/` in the
55
- workspace and none of them reads `node_modules`; `rigc skills install` puts every
56
- skill the package ships there, and `rigc skills --help` says what it refuses.
57
-
58
- The package links no Spine runtime. `validate`, `preview` and `vote` below run
59
- through `@esotericsoftware/spine-core`: install it beside the package
60
- (`bun add -d @esotericsoftware/spine-core`) and the same `rigc` runs them, and
61
- `build` runs the round trip as well. Without it they are refused by name, and
62
- `build` writes the same files gated without the parse — `rigc --version` names the
63
- entry that ran. In the parse's place that `build` runs two rules of its own over
64
- rigc's model document, `A00_MODEL_READ` and `A00_MODEL_REGIONS_ON_PAGES`; they are
65
- not registry assertions, so its summary counts two more, and AUTHORING §5.2 has a
66
- row for each.
56
+ workspace and none of them reads `node_modules`; `rigc skills install` scans this
57
+ package's `skills/` and every `node_modules/@rig-c/*/skills/<name>/SKILL.md` (and
58
+ rig-c alias package) in the project, and `rigc skills --help` says what it refuses.
59
+ In a consumer without `@rig-c/core` installed, run `bunx @rig-c/core skills install --copy`:
60
+ it reaches the project's `node_modules` from the working directory and writes plain
61
+ folders instead of linking `@rig-c/core`'s own skills into a temporary `.bunx` cache
62
+ that can be emptied.
63
+
64
+ The package links no Spine runtime. `build` writes `skeleton.json`,
65
+ `skeleton.atlas` and `skeleton.model.json`. `preview`, `vote`, `bench` and `bonedist`
66
+ run through `@esotericsoftware/spine-core`: install it beside
67
+ the package (`bun add -d @esotericsoftware/spine-core`) and the same `rigc` runs
68
+ them, and `build` runs the round trip as well. Without it those commands are refused
69
+ by name, while `build`, `validate`, `explain`, `diff`, `render`, `check` and `inspect`
70
+ (`--poser core|spine`) run on rigc's own core over `skeleton.model.json` —
71
+ `rigc --version` names the entry that ran (AUTHORING §0). In the parse's place that
72
+ `build` runs two rules of its own over rigc's model document, `A00_MODEL_READ` and
73
+ `A00_MODEL_REGIONS_ON_PAGES`; they are not registry assertions, so its summary
74
+ counts two more, and AUTHORING §5.2 has a row for each.
67
75
 
68
76
  ## The loop
69
77
 
70
78
  1. `rigc build --rig <spec> --motion <spec> --images <dir> --out <dir>` compiles,
71
- gates, and writes only on green.
79
+ gates (under `--profile spine`, the default, or `--profile rig-play`), and writes
80
+ `skeleton.json`, `skeleton.atlas` and `skeleton.model.json` only on green.
72
81
  2. Read the report. Every red line names the file to change; fix the spec and
73
82
  build again.
74
83
  3. `rigc explain --rig <spec> --motion <spec> --out <dir>` when the red line is
@@ -85,11 +94,17 @@ row for each.
85
94
  cover a feature the art shows — and answer each with `render --hide <slot>` (or
86
95
  `--slot <slot,…>`), which draws the frame again without that part on the same
87
96
  grid, so the two frames say which part a pixel is. AUTHORING §0 holds the three.
88
- When the question is geometry — is this bone where the art is, which way does it
89
- squash, where in rig units is this point — `rigc inspect --candidate <out> --show
90
- art,bones,names --dim` draws the bones over the art and writes a legend beside the
91
- picture that puts every mark back in world units; AUTHORING §13 maps each
92
- question to its view.
97
+ For motion and dial readings, `render --pass-through <cloth>:<leg>` names the
98
+ frames where a cloth drawn over a leg passes through it (AUTHORING §8.5),
99
+ `render --angle-range <bone>:<relative-to>` prints how far a bone turns against
100
+ another (AUTHORING §8.7), `render --seam <slot>:<slot>` reads joint gaps,
101
+ visible ghosts and background exposure (AUTHORING §8.8), and
102
+ `render --parallax <dial>` reads a slider dial across its value grid
103
+ (AUTHORING §8.12). When the question is geometry — is this bone where the art is,
104
+ which way does it squash, where in rig units is this point —
105
+ `rigc inspect --candidate <out> --show art,bones,names --dim` draws the bones
106
+ over the art and writes a legend beside the picture that puts every mark back in
107
+ world units; AUTHORING §13 maps each question to its view.
93
108
  5. `rigc check --candidate <out> --frames <dir>` when you have reference pictures
94
109
  (`--out <dir>` writes the picture each of its numbers came from — open the worst).
95
110
  Read its output from the top: the framing block says whether the figures under
@@ -100,17 +115,21 @@ row for each.
100
115
  `rigc vote --candidate <a> --candidate <b>` when several candidates are green and
101
116
  only a person can choose between them.
102
117
  6. `rigc validate <out>` re-gates artifacts already on disk, and
103
- `rigc <command> --help` is each command's own flag table. `rigc repack <out>
104
- --out <dir>` packs a build again under `build --pack`'s packing flags when its
105
- parts were not kept, and writes only after every region and the skeleton are
106
- shown unchanged (`--accept-skeleton-differences` writes a rebuild whose skeleton
107
- differs, every difference printed; `--stage-box <slot>` reads a build's stage box)
108
- — AUTHORING §0.4. A mesh refused because its triangles are several pieces that
109
- share no vertex: `rigc split --rig … --motion … --slot <slot> --out <dir>` writes
110
- both specs back with one mesh per piece and every deform key renumbered — build
111
- that — AUTHORING §3.4.11.
112
- 7. Every finished unit ends with `rigc preview --candidate <out>`, and the report
113
- names the `.html` it wrote. The hand-off to a person is part of the work.
118
+ `rigc <command> --help` is each command's own flag table.
119
+ `rigc repack <out> --out <dir>` packs a build again under `build --pack`'s
120
+ packing flags when its parts were not kept, and writes only after every region and
121
+ the skeleton are shown unchanged (`--keep-pages` refreshes `skeleton.model.json`
122
+ over the existing atlas pages; `--accept-skeleton-differences` writes a rebuild
123
+ whose skeleton differs, every difference printed; `--stage-box <slot>` reads a
124
+ build's stage box) — AUTHORING §0.4. A mesh refused because its triangles are
125
+ several pieces that share no vertex: `rigc split --rig … --motion … --slot <slot> --out <dir>`
126
+ writes both specs back with one mesh per piece and every deform key renumbered —
127
+ build that — AUTHORING §3.4.11.
128
+ 7. Every finished unit ends with `rigc preview --candidate <out>` (or
129
+ `rigc review <out>` when `@rig-c/review` is installed beside the project and a
130
+ person reviews the build via its loopback URL, AUTHORING §0), and the report
131
+ names the `.html` or URL it produced. The hand-off to a person is part of the
132
+ work.
114
133
 
115
134
  The loop in full, with `pose` before it and `chainfit` after the first build:
116
135
  AUTHORING §0.
@@ -141,10 +160,22 @@ spec files field by field, the emission rules, the loop, and the failure map. Th
141
160
  | --- | --- | --- |
142
161
  | a **skeleton** — how many bones, where each pivot sits, what hangs off what | [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) | `rigc-rigging` |
143
162
  | a **movement** — an idle, a loop, from this pose to that one | [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) | `rigc-motion` |
144
- | a **face** — a blink, a gaze, a head turn a few degrees off axis | [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) | `rigc-face` |
163
+ | a **face** — a blink, a gaze, a head turn or a value-driven face dial | [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) | `rigc-face` |
145
164
  | a **skeleton.json somebody else authored** — read it, repair it, extend it | [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) | `rigc-ingest` |
146
165
  | you are the **person operating** the agent rather than the agent | [PROMPTING.md](https://github.com/rig-c/core/blob/main/docs/PROMPTING.md) | — |
147
166
 
167
+ Other `@rig-c/*` packages ship their own agent skills (`node_modules/@rig-c/<pkg>/skills/<skill>/SKILL.md`), which `rigc skills install` links beside these; load that skill for its tool's flags and rules:
168
+
169
+ | The request is… | Tool | Skill |
170
+ | --- | --- | --- |
171
+ | Turn one anime character painting and its See-through layer decomposition into a verified 2D character rig — measured parts, weighted meshes, bone chains and a looping idle | `@rig-c/parts` (`rig-parts`) | `rig-parts` |
172
+ | Clean and retarget a body-18 (or COCO-17) pose sequence onto a rig built by `rigc` or `@rig-c/parts` — bone rotate keys, hip translate keys, leg foreshortening, alternate-drawing switches and skirt-physics fitting | `@rig-c/pose` (`rig-pose`) | `rig-pose` |
173
+ | Hand a rig build or `rig-parts`/`rig-pose` output to a person for local inspection in a browser workspace (`rig-review open`) and retrieve their structured feedback (`rig-review feedback`), validate the delivery package (`rig-review check`), or export frame strips (`rig-review strip`) | `@rig-c/review` (`rig-review`) | `rig-review` |
174
+ | Play a rig built by `rigc` (`skeleton.model.json`) on a web page as plain DOM or a standalone `<canvas>`, with multi-track playback, attachment sets, wind and gravity physics, bone adjustments and live-frame borrowing | `@rig-c/play` (`rig-play`) | `rig-play` |
175
+ | Play a rig built by `rigc` (`skeleton.model.json`) inside a PixiJS 8 scene graph (`PixiRig` and `PixiRenderer`), posed through `@rig-c/play` with no Spine runtime | `@rig-c/pixi` | `rig-pixi` |
176
+ | Play a rig built by `rigc` (`skeleton.model.json`) inside a Phaser 4.2+ WebGL scene (`PhaserRig` and `PhaserRenderer`), posed through `@rig-c/play` with shared GPU page textures and no Spine runtime | `@rig-c/phaser` | `rig-phaser` |
177
+ | Play a rig built by `rigc` (`skeleton.model.json`) inside a native Three.js WebGL2 scene (`ThreeRig` and `ThreeRenderer`), posed through `@rig-c/play` under an orthographic or perspective camera with no Spine runtime | `@rig-c/three` | `rig-three` |
178
+
148
179
  Every guide linked here is in the installed package at `node_modules/@rig-c/core/docs/`,
149
180
  which is the copy that matches the rigc you run; the links go to the repository's
150
181
  `main`. Inside the Claude Code plugin the same files are at `${CLAUDE_PLUGIN_ROOT}/docs/`.
@@ -1,16 +1,17 @@
1
1
  ---
2
2
  name: rigc-face
3
- description: Author a face on plain Spine data with rigc — a blink, a gaze shift, a breathing portrait and a head turn a few degrees off axis, built from deform timelines and per-part parallax. Use when the request is a talking or living portrait, a standing character, an expression or a head turn, such as "rig this face", "make the portrait blink and look around" or "turn the head". Not for Live2D file conversion, cutting a face illustration into parts, or VTuber-style real-time face tracking.
3
+ description: Author a face on plain Spine data with rigc — a blink, a gaze shift, a breathing portrait, a head turn (from a flat sheet a few degrees off axis or a per-vertex depth map across its turn ceiling) and slider-driven face dials, built from closed-form deform transforms, derived group tracks, per-part parallax and socket alpha clipping. Use when the request is a talking or living portrait, a standing character, an expression, a head turn or a look dial, such as "rig this face", "make the portrait blink and look around" or "turn the head". Not for Live2D file conversion, cutting a face illustration into parts, or VTuber-style real-time face tracking.
4
4
  license: MIT
5
5
  compatibility: Requires Bun 1.4 or later and the npm package @rig-c/core (rig-c is the convenience name).
6
6
  ---
7
7
 
8
- # Face — a turn, a gaze and a blink
8
+ # Face — a turn, a gaze, a blink and a dial
9
9
 
10
10
  Load this when the request is a **head rather than a body**: a drawn face that
11
- breathes, blinks, moves its eyes and turns a few degrees off axis. Every rule below
12
- is owned by [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md); this file says when to open it and what
13
- it will not do for you.
11
+ breathes, blinks, moves its eyes, turns off axis (on a flat sheet or a per-vertex
12
+ `depth` map) or exposes its turn as a slider-driven dial. Every rule below is owned
13
+ by [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md); this file says when to open it and what it will
14
+ not do for you.
14
15
 
15
16
  📖 **A failure line ends with the command that prints its row of the guide** —
16
17
  ` (see rigc guide <RULE>)`. Run that command first, before reading anything else:
@@ -27,9 +28,22 @@ index within that same limit; only legacy uncoded library errors point at `§5.1
27
28
 
28
29
  - **Validation is never bypassed.** `build` writes nothing on a red gate, and there
29
30
  is no flag that changes that — AUTHORING §0.
30
- - **The compiler never invents a value.** A `deform` key states every vertex it
31
- moves; the geometry a turn implies is evaluated by you and written into the spec,
32
- never inferred by the compiler — AUTHORING §4 and FACE §1.
31
+ - **The compiler never invents a value.** A `deform` key states either explicit
32
+ vertex deltas or `transform` (`yaw`/`pitch` with `radius` and `degrees`, or over
33
+ the mesh's per-vertex `depth` sheet, AUTHORING §3.4.5 and FACE §2.1, §2.2), and a
34
+ bone group track states `derive` (`depth` per member, `carried` shared shift); the
35
+ compiler evaluates the closed form from the parameters you declare and never
36
+ guesses a radius, angle or depth — AUTHORING §4, §4.5.1, §4.11.1 and FACE §1,
37
+ §1.1, §3.1.
38
+ - **A face angle exposed as a dial is a slider constraint, not a playback time.**
39
+ Map a control bone across `[−1, +1]` onto the turn animation with a slider
40
+ (AUTHORING §3.5.2, FACE §8), bound its degrees by the turn ceiling `build` and
41
+ `explain` print for the depth sheet (FACE §2.2), set `additive: true` when yaw and
42
+ pitch dials share a target (`A40_SLIDERS_COMPOSE_ON_A_SHARED_TARGET`), keep driven
43
+ constraints after their driver (`A42_DRIVEN_CONSTRAINTS_UPDATE_AFTER_THEIR_DRIVER`),
44
+ read the dial across its value grid with `rigc render --candidate <out> --parallax <dial>`
45
+ (AUTHORING §8.12), and clip an iris or highlight to its painted socket with
46
+ `alphaClip` (AUTHORING §3.4, FACE §11).
33
47
  - **The validator's messages are the instructions.** Read each named failure as the
34
48
  pointer to the file that has to change — AUTHORING §5.
35
49
 
@@ -46,19 +60,23 @@ nothing about the fold, so author under `--profile rig-play`.
46
60
 
47
61
  What `A39` still cannot say is whether the projection was the right one —
48
62
  nothing measures whether 12° was the angle the shot wanted. FACE §9.2 is that
49
- demonstration, three builds with one of them refused, and §9.3 is the
50
- differential audit and the three limits it does not lift.
63
+ demonstration, three builds with one of them refused, and §9.3 is the differential
64
+ audit and the three limits it does not lift.
51
65
 
52
66
  ## Read, in this order
53
67
 
54
- 1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the `deform` timeline field by field
55
- and what rigc refuses in it (§4), the failure map (§5–§6), the editor's
56
- conventions (§10).
68
+ 1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — `alphaClip` and per-vertex `depth` maps
69
+ (§3.4, §3.4.5), slider constraints (§3.5.2), the `deform` timeline, `derive` and
70
+ `transform` field by field (§4, §4.5.1, §4.11, §4.11.1), the failure map
71
+ (§5–§6), `render --parallax <dial>` (§8.12), and the editor's conventions (§10).
57
72
  2. [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) — timing, easing and the offset table (§3),
58
73
  candidates and the ballot (§4–§5). A blink and a gaze are ordinary motion work.
59
- 3. [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) — the face's own geometry: the closed form every
60
- number in a turn comes from (§1), the hierarchy underneath (§3), what
61
- foreshortens (§5), and the deform audit gap (§9).
74
+ 3. [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) — the face's own geometry: the closed form and
75
+ `transform` (§1, §1.1), per-vertex depth sheets and turn ceilings (§2, §2.1,
76
+ §2.2), the shared-shift hierarchy and `derive` (§3, §3.1), the 3×5 mesh and its
77
+ silhouette cliff (§4), what foreshortens (§5), the blink and channel allocation
78
+ (§6–§7), the three cliffs and the turn as a slider-driven value (§8), the deform
79
+ audit gap (§9), and non-goals and `alphaClip` socket clipping (§11).
62
80
  4. Then [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) for the hierarchy as a general rule
63
81
  rather than this closed form, and [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) if the head
64
82
  arrived as a compiled skeleton.
@@ -36,17 +36,24 @@ index within that same limit; only legacy uncoded library errors point at `§5.1
36
36
  ## What this guide will not do
37
37
 
38
38
  Nothing grades a hierarchy. A rig with its head off its torso passes the gate, the
39
- pixels are nearly blind to structure, and the two instruments that see it at all
40
- are named in RIGGING §11 — neither as a pass bar.
39
+ pixels are nearly blind to structure, and the instruments that see it at all are
40
+ named in RIGGING §11 and §11.2 — none as a pass bar. `rigc pose` and `rigc chainfit`
41
+ read part placements and chain hinges from a key-pose picture inside the loop
42
+ (AUTHORING §11, RIGGING §9–§10); `rigc bonedist` measures per-frame bone
43
+ world-transform distance against the same rig's own `home` setup-pose animation
44
+ (RIGGING §7.3) or, at the finish line only, against another skeleton (RIGGING
45
+ §11.2, subject to the honesty rule in AUTHORING §9.1).
41
46
 
42
47
  ## Read, in this order
43
48
 
44
49
  1. [AUTHORING.md](https://github.com/rig-c/core/blob/main/docs/AUTHORING.md) — the rig spec field by field (§3),
45
- the failure map (§5–§6), reading a pose out of a picture (§8.1, §11, §12),
46
- and the editor's own conventions (§10).
50
+ the failure map (§5–§6), reading a pose out of a picture and diagnostic render
51
+ flags (§8.1, §8.5, §8.7, §8.8, §11, §12), the honesty rule (§9.1), and the
52
+ editor's own conventions (§10).
47
53
  2. [RIGGING.md](https://github.com/rig-c/core/blob/main/docs/RIGGING.md) — the structure itself: what identifies a
48
- pivot and what moving one costs (§2–§3), gauges (§4), what a chain can reach
49
- (§6), and the instruments that see structure (§11).
54
+ pivot and what moving one costs (§2–§3), gauges (§4), what a chain can reach and
55
+ `bonedist` against `home` (§6–§7, §7.3), and the instruments that see structure
56
+ (§11, §11.2).
50
57
  3. Then [MOTION.md](https://github.com/rig-c/core/blob/main/docs/MOTION.md) once the skeleton exists;
51
58
  [FACE.md](https://github.com/rig-c/core/blob/main/docs/FACE.md) if the figure is a head;
52
59
  [INGEST.md](https://github.com/rig-c/core/blob/main/docs/INGEST.md) if the skeleton was handed to you already
@@ -2211,19 +2211,163 @@ interface SkillsInstallEntry {
2211
2211
  target: string;
2212
2212
  /** `<package>/skills/<name>`. */
2213
2213
  source: string;
2214
+ /** Which package the skill came from (`package.json` `name`, or its `node_modules` directory name). */
2215
+ packageName: string;
2214
2216
  action: SkillsInstallAction;
2215
2217
  /** The link text, relative to the directory it sits in, when the entry is a link. */
2216
2218
  link: string;
2219
+ /** Other copies of the same product that this copy was chosen over, when more than one copy was present. */
2220
+ chosenOver?: Array<{ packageName: string; source: string }>;
2221
+ }
2222
+
2223
+ export interface DiscoveredSkillSource {
2224
+ packageName: string;
2225
+ skillsDir: string;
2226
+ /** Whether this source lives inside a `node_modules` tree reached walking up from `process.cwd()`. */
2227
+ inProjectNodeModules?: boolean;
2228
+ }
2229
+
2230
+ /**
2231
+ * Canonical product identity for a package name (issue #1672):
2232
+ * `rig-c`, `spine-rigc` and `@rig-c/core` are the same product (`@rig-c/core`), and each unscoped convenience
2233
+ * alias `rig-<name>` is the same product as `@rig-c/<name>`.
2234
+ */
2235
+ function canonicalProduct(packageName: string): string {
2236
+ if (packageName === 'rig-c' || packageName === 'spine-rigc' || packageName === '@rig-c/core') {
2237
+ return '@rig-c/core';
2238
+ }
2239
+ if (packageName.startsWith('rig-')) {
2240
+ return `@rig-c/${packageName.slice('rig-'.length)}`;
2241
+ }
2242
+ return packageName;
2243
+ }
2244
+
2245
+ function isDirectoryThere(path: string): boolean {
2246
+ try {
2247
+ return existsSync(path) && statSync(path).isDirectory();
2248
+ } catch {
2249
+ return false;
2250
+ }
2217
2251
  }
2218
2252
 
2219
2253
  /** Every `<name>/` under `source` that holds a `SKILL.md`, in name order, so two runs print the same lines. */
2220
2254
  function shippedSkills(source: string): string[] {
2221
- if (!existsSync(source) || !statSync(source).isDirectory()) return [];
2255
+ if (!isDirectoryThere(source)) return [];
2222
2256
  return readdirSync(source)
2223
- .filter((name) => statSync(join(source, name)).isDirectory() && existsSync(join(source, name, 'SKILL.md')))
2257
+ .filter((name) => isDirectoryThere(join(source, name)) && existsSync(join(source, name, 'SKILL.md')))
2224
2258
  .sort();
2225
2259
  }
2226
2260
 
2261
+ /** Read `<pkgDir>/package.json`'s `name` when present and non-empty, falling back to `fallback`. */
2262
+ function readPackageName(pkgDir: string, fallback: string): string {
2263
+ const manifestPath = join(pkgDir, 'package.json');
2264
+ if (!existsSync(manifestPath)) return fallback;
2265
+ try {
2266
+ const raw = JSON.parse(readFileSync(manifestPath, 'utf8')) as { name?: unknown };
2267
+ return typeof raw.name === 'string' && raw.name.trim() !== '' ? raw.name.trim() : fallback;
2268
+ } catch {
2269
+ return fallback;
2270
+ }
2271
+ }
2272
+
2273
+ /**
2274
+ * Whether an unscoped `<node_modules>/<entry>` directory is a rig-c alias package (issue #1672):
2275
+ * 1. it resolves (`realpathSync`) into a `@rig-c/*` directory, or
2276
+ * 2. its `package.json` `repository` (string or `{url}`) points at `github.com/rig-c/`.
2277
+ * A directory or package name alone never qualifies, because any third party can publish an unscoped `rig-<anything>`
2278
+ * package.
2279
+ */
2280
+ function isRigcAliasDir(pkgDir: string): boolean {
2281
+ try {
2282
+ if (realpathSync(pkgDir).replaceAll('\\', '/').includes('/@rig-c/')) return true;
2283
+ } catch {
2284
+ return false;
2285
+ }
2286
+ const manifestPath = join(pkgDir, 'package.json');
2287
+ if (!existsSync(manifestPath)) return false;
2288
+ try {
2289
+ const raw = JSON.parse(readFileSync(manifestPath, 'utf8')) as { repository?: unknown };
2290
+ const repo =
2291
+ typeof raw.repository === 'string'
2292
+ ? raw.repository.trim()
2293
+ : typeof raw.repository === 'object' && raw.repository !== null && typeof (raw.repository as { url?: unknown }).url === 'string'
2294
+ ? (raw.repository as { url: string }).url.trim()
2295
+ : '';
2296
+ return /^(?:(?:git\+)?https?:\/\/|(?:git\+)?ssh:\/\/git@|git:\/\/)?github\.com[:/]rig-c\/[A-Za-z0-9._-]+(?:\.git)?\/?$/.test(repo);
2297
+ } catch {
2298
+ return false;
2299
+ }
2300
+ }
2301
+
2302
+ /**
2303
+ * Every `node_modules` directory reachable from `cwd` (walking up `<dir>/node_modules`) and from an installed
2304
+ * `packageRoot` (every ancestor directory named `node_modules`, which covers flat `@rig-c/core`, unscoped `rig-c`, and
2305
+ * isolated `node_modules/.bun/.../node_modules/@rig-c/core` layouts), deduplicated by `realpathSync`.
2306
+ */
2307
+ function nodeModulesRoots(cwd: string, packageRoot: string): string[] {
2308
+ const out: string[] = [];
2309
+ const seen = new Set<string>();
2310
+ const add = (nm: string): void => {
2311
+ if (!isDirectoryThere(nm)) return;
2312
+ const real = realpathSync(nm);
2313
+ if (seen.has(real)) return;
2314
+ seen.add(real);
2315
+ out.push(nm);
2316
+ };
2317
+ for (let dir = resolve(cwd); ; dir = dirname(dir)) {
2318
+ add(join(dir, 'node_modules'));
2319
+ if (dirname(dir) === dir) break;
2320
+ }
2321
+ for (let dir = dirname(resolve(packageRoot)); dirname(dir) !== dir; dir = dirname(dir)) {
2322
+ if (basename(dir) === 'node_modules') add(dir);
2323
+ }
2324
+ return out;
2325
+ }
2326
+
2327
+ /**
2328
+ * Every skill-shipping package source `rigc skills install` scans (issue #1672):
2329
+ * 1. `join(packageRoot, 'skills')` — the package this command ran from, always first;
2330
+ * 2. every `node_modules/@rig-c/<pkg>/skills` and every rig-c alias package's `skills/` reachable from the project's
2331
+ * `node_modules`, in deterministic sorted order.
2332
+ */
2333
+ export function discoverSkillSources(cwd: string, packageRoot: string): DiscoveredSkillSource[] {
2334
+ const projectNmReals = new Set<string>();
2335
+ for (let dir = resolve(cwd); ; dir = dirname(dir)) {
2336
+ const nm = join(dir, 'node_modules');
2337
+ if (isDirectoryThere(nm)) projectNmReals.add(realpathSync(nm));
2338
+ if (dirname(dir) === dir) break;
2339
+ }
2340
+ const sources: DiscoveredSkillSource[] = [
2341
+ {
2342
+ packageName: readPackageName(packageRoot, basename(packageRoot)),
2343
+ skillsDir: join(packageRoot, 'skills'),
2344
+ inProjectNodeModules: false,
2345
+ },
2346
+ ];
2347
+ for (const nm of nodeModulesRoots(cwd, packageRoot)) {
2348
+ const inProjectNodeModules = projectNmReals.has(realpathSync(nm));
2349
+ const scopeDir = join(nm, '@rig-c');
2350
+ if (isDirectoryThere(scopeDir)) {
2351
+ for (const sub of readdirSync(scopeDir).sort()) {
2352
+ const pkgDir = join(scopeDir, sub);
2353
+ const skillsDir = join(pkgDir, 'skills');
2354
+ if (isDirectoryThere(pkgDir) && shippedSkills(skillsDir).length > 0) {
2355
+ sources.push({ packageName: readPackageName(pkgDir, `@rig-c/${sub}`), skillsDir, inProjectNodeModules });
2356
+ }
2357
+ }
2358
+ }
2359
+ for (const entry of readdirSync(nm).sort()) {
2360
+ if (entry.startsWith('@') || entry.startsWith('.')) continue;
2361
+ const pkgDir = join(nm, entry);
2362
+ const skillsDir = join(pkgDir, 'skills');
2363
+ if (isDirectoryThere(pkgDir) && isRigcAliasDir(pkgDir) && shippedSkills(skillsDir).length > 0) {
2364
+ sources.push({ packageName: readPackageName(pkgDir, entry), skillsDir, inProjectNodeModules });
2365
+ }
2366
+ }
2367
+ }
2368
+ return sources;
2369
+ }
2370
+
2227
2371
  /**
2228
2372
  * The real path of `path` whether or not it exists yet: the real path of its
2229
2373
  * nearest existing ancestor with the rest appended. A relative link has to be
@@ -2266,30 +2410,90 @@ function firstDifference(copy: string, original: string): string | null {
2266
2410
  }
2267
2411
 
2268
2412
  /**
2269
- * Install every shipped skill into `dir`, or refuse and write nothing.
2413
+ * Install every discovered skill into `dir`, or refuse and write nothing.
2270
2414
  *
2271
- * The plan is made in full before the first write: every entry is classified as
2272
- * absent, already this command's, or in the way, and one entry in the way
2273
- * refuses the whole call with every such entry named.
2415
+ * The plan is made in full before the first write:
2416
+ * - two sources that resolve (`realpathSync`) to the same skill folder are one skill (an alias or symlink reached
2417
+ * twice is deduplicated);
2418
+ * - two copies of the same product (`rig-c` ↔ `@rig-c/core`, `rig-<name>` ↔ `@rig-c/<name>`, or a project copy beside
2419
+ * the running package) are not a collision: the copy in the project's `node_modules` wins over the running package,
2420
+ * and between two project copies the scoped package (`@rig-c/*`) wins over the unscoped alias (`rig-*`), with the
2421
+ * chosen copy reported on its line;
2422
+ * - two different products shipping the same skill name refuse the call naming both sources and write nothing;
2423
+ * - every target is classified as absent, already this command's, or in the way, and one entry in the way refuses the
2424
+ * whole call with every such entry named.
2274
2425
  */
2275
- function installSkills(source: string, dir: string, copy: boolean): SkillsInstallEntry[] {
2276
- const names = shippedSkills(source);
2277
- if (names.length === 0) {
2426
+ function installSkills(sources: readonly DiscoveredSkillSource[], dir: string, copy: boolean): SkillsInstallEntry[] {
2427
+ const own = sources[0];
2428
+ if (own === undefined || shippedSkills(own.skillsDir).length === 0) {
2429
+ const ownSource = own?.skillsDir ?? join(PACKAGE_ROOT, 'skills');
2278
2430
  throw new SkillsInstallError(
2279
- `no skill to install: ${source} ${existsSync(source) ? 'holds no <name>/SKILL.md' : 'is not there'}, and it is ` +
2431
+ `no skill to install: ${ownSource} ${existsSync(ownSource) ? 'holds no <name>/SKILL.md' : 'is not there'}, and it is ` +
2280
2432
  'the skills/ directory of the package this command ran from; nothing was written',
2281
2433
  );
2282
2434
  }
2435
+ interface CandidateSkill {
2436
+ name: string;
2437
+ packageName: string;
2438
+ product: string;
2439
+ source: string;
2440
+ realFrom: string;
2441
+ inProjectNodeModules: boolean;
2442
+ isScoped: boolean;
2443
+ chosenOver?: Array<{ packageName: string; source: string }>;
2444
+ }
2445
+ const byName = new Map<string, CandidateSkill[]>();
2446
+ for (const src of sources) {
2447
+ const inProject = Boolean(src.inProjectNodeModules);
2448
+ const product = canonicalProduct(src.packageName);
2449
+ const isScoped = src.packageName.startsWith('@rig-c/');
2450
+ for (const name of shippedSkills(src.skillsDir)) {
2451
+ const from = join(src.skillsDir, name);
2452
+ const realFrom = realpathSync(from);
2453
+ const list = byName.get(name) ?? [];
2454
+ if (list.some((item) => item.realFrom === realFrom)) {
2455
+ const existing = list.find((item) => item.realFrom === realFrom);
2456
+ if (existing !== undefined && inProject) existing.inProjectNodeModules = true;
2457
+ continue;
2458
+ }
2459
+ list.push({ name, packageName: src.packageName, product, source: from, realFrom, inProjectNodeModules: inProject, isScoped });
2460
+ byName.set(name, list);
2461
+ }
2462
+ }
2463
+ const collisions = [...byName.entries()]
2464
+ .filter(([, list]) => new Set(list.map((item) => item.product)).size > 1)
2465
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
2466
+ if (collisions.length > 0) {
2467
+ throw new SkillsInstallError(
2468
+ `${collisions.length} duplicate skill name(s) across packages, and nothing was written:\n` +
2469
+ collisions
2470
+ .map(([name, list]) => ` ${name} is shipped by ${list.map((item) => `${item.packageName} (${item.source})`).join(' and ')}`)
2471
+ .join('\n'),
2472
+ );
2473
+ }
2283
2474
  if (existsSync(dir) && !statSync(dir).isDirectory()) {
2284
2475
  throw new SkillsInstallError(`${dir} exists and is not a directory, so no skill can be installed into it; nothing was written`);
2285
2476
  }
2477
+ const discovered = [...byName.values()]
2478
+ .map((list) => {
2479
+ if (list.length === 1) return list[0];
2480
+ const ranked = [...list].sort((a, b) => {
2481
+ if (a.inProjectNodeModules !== b.inProjectNodeModules) return a.inProjectNodeModules ? -1 : 1;
2482
+ if (a.isScoped !== b.isScoped) return a.isScoped ? -1 : 1;
2483
+ return 0;
2484
+ });
2485
+ return {
2486
+ ...ranked[0],
2487
+ chosenOver: ranked.slice(1).map((item) => ({ packageName: item.packageName, source: item.source })),
2488
+ };
2489
+ })
2490
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
2286
2491
  const realDir = realpathAhead(dir);
2287
2492
  const planned: SkillsInstallEntry[] = [];
2288
2493
  const refused: string[] = [];
2289
- for (const name of names) {
2494
+ for (const item of discovered) {
2495
+ const { name, packageName, source: from, realFrom, chosenOver } = item;
2290
2496
  const target = join(dir, name);
2291
- const from = join(source, name);
2292
- const realFrom = realpathSync(from);
2293
2497
  const link = relative(realDir, realFrom);
2294
2498
  let action: SkillsInstallAction = copy ? 'copied' : 'linked';
2295
2499
  let found: string | null = null;
@@ -2312,11 +2516,11 @@ function installSkills(source: string, dir: string, copy: boolean): SkillsInstal
2312
2516
  found = 'a plain file';
2313
2517
  }
2314
2518
  if (found !== null) refused.push(`${target} is ${found}; ${copy ? `a copy of ${from}` : `a symlink to ${link}`} was required`);
2315
- else planned.push({ target, source: from, action, link });
2519
+ else planned.push({ target, source: from, packageName, action, link, chosenOver });
2316
2520
  }
2317
2521
  if (refused.length > 0) {
2318
2522
  throw new SkillsInstallError(
2319
- `${refused.length} of the ${names.length} skill(s) cannot be installed into ${dir}, and nothing was written:\n` +
2523
+ `${refused.length} of the ${discovered.length} skill(s) cannot be installed into ${dir}, and nothing was written:\n` +
2320
2524
  refused.map((line) => ` ${line}`).join('\n') +
2321
2525
  '\nRemove the entries named above, or pass --dir to install somewhere else.',
2322
2526
  );
@@ -2343,7 +2547,7 @@ export function cmdSkills(flags: Record<string, string>, positional: string[]):
2343
2547
  if (sub === undefined) {
2344
2548
  throw new UsageError(
2345
2549
  `skills takes a subcommand: ${SKILLS_SUBCOMMANDS.join(', ')} — \`rigc skills install\` links every skill this ` +
2346
- `package ships into ${DEFAULT_SKILLS_DIR}`,
2550
+ `package and any installed @rig-c/* or alias package ship into ${DEFAULT_SKILLS_DIR}`,
2347
2551
  );
2348
2552
  }
2349
2553
  if (!SKILLS_SUBCOMMANDS.includes(sub)) {
@@ -2364,9 +2568,15 @@ export function cmdSkills(flags: Record<string, string>, positional: string[]):
2364
2568
  }
2365
2569
  const copy = flags.copy !== undefined;
2366
2570
  const dir = resolve(process.cwd(), flags.dir ?? DEFAULT_SKILLS_DIR);
2367
- const entries = installSkills(join(PACKAGE_ROOT, 'skills'), dir, copy);
2571
+ const entries = installSkills(discoverSkillSources(process.cwd(), PACKAGE_ROOT), dir, copy);
2368
2572
  for (const entry of entries) {
2369
- const ends = entry.action.endsWith('linked') ? `${entry.target} -> ${entry.link} (${entry.source})` : `${entry.target} <- ${entry.source}`;
2573
+ const choiceNote =
2574
+ entry.chosenOver !== undefined && entry.chosenOver.length > 0
2575
+ ? ` (chose ${entry.packageName} at ${entry.source} over ${entry.chosenOver.map((o) => `${o.packageName} at ${o.source}`).join(', ')})`
2576
+ : '';
2577
+ const ends = entry.action.endsWith('linked')
2578
+ ? `${entry.target} -> ${entry.link} (${entry.source}) [${entry.packageName}]${choiceNote}`
2579
+ : `${entry.target} <- ${entry.source} [${entry.packageName}]${choiceNote}`;
2370
2580
  console.log(` ${entry.action.padEnd(14)} ${ends}`);
2371
2581
  }
2372
2582
  const wrote = entries.filter((entry) => entry.action === 'linked' || entry.action === 'copied').length;
package/src/cli/shared.ts CHANGED
@@ -933,20 +933,26 @@ export const DEFAULT_BALLOT = 'ballot.html';
933
933
  export const DEFAULT_LEDGER = 'votes.jsonl';
934
934
 
935
935
  // ---------------------------------------------------------------------------
936
- // skills install — put the shipped skills where an agent host looks (issue #831)
936
+ // skills install — put the shipped skills where an agent host looks (issues #831, #1672)
937
937
  // ---------------------------------------------------------------------------
938
938
  //
939
- // After `bun add -d rig-c` the skills sit at `node_modules/rig-c/skills/`,
940
- // which no host reads. Codex, Gemini CLI and Antigravity all read
941
- // `<workspace>/.agents/skills/<name>/`, so this links every `skills/<name>/` the
942
- // package ships into one directory — `.agents/skills` under the working
943
- // directory unless `--dir` says otherwise.
939
+ // After `bun add -d @rig-c/core` (and sibling packages such as `@rig-c/parts` or
940
+ // `@rig-c/play`) the skills sit at `node_modules/@rig-c/*/skills/`, which no
941
+ // host reads. Codex, Gemini CLI and Antigravity all read
942
+ // `<workspace>/.agents/skills/<name>/`, so this links every `skills/<name>/` this
943
+ // package and every installed `@rig-c/*` or rig-c alias package ship into one
944
+ // directory — `.agents/skills` under the working directory unless `--dir` says
945
+ // otherwise.
944
946
  //
945
- // ⭐ The skills are found from THIS FILE's location, never from the working
946
- // directory: the command installs the package it is, and a cwd that happens to
947
- // hold some other `skills/` is not a source. That is also why it lives here and
948
- // not in `src/`: its one input is where the CLI was installed, nothing else
949
- // calls it, and `src/` is about rigs.
947
+ // ⭐ The skills are found from `PACKAGE_ROOT/skills` and every `node_modules/@rig-c/*`
948
+ // and rig-c alias package reachable from the working directory's and `PACKAGE_ROOT`'s
949
+ // `node_modules` — never a loose `skills/` in the working directory itself. Two
950
+ // paths that resolve (`realpathSync`) to the same skill folder are deduplicated;
951
+ // two copies of the same product (`rig-c` ↔ `@rig-c/core`, `rig-<name>` ↔ `@rig-c/<name>`)
952
+ // choose the project's `node_modules` copy over the running package and the scoped
953
+ // package over the unscoped alias, printing which copy was chosen; two different
954
+ // products shipping the same skill name refuse the call naming both sources and
955
+ // write nothing.
950
956
  //
951
957
  // A RELATIVE symlink by default, so the directory survives the project being
952
958
  // moved or cloned elsewhere and an upgrade of the package is seen with no second
@@ -1986,15 +1992,26 @@ export const COMMANDS: CommandDoc[] = [
1986
1992
  name: 'skills',
1987
1993
  runtime: false,
1988
1994
  spineFormat: false,
1989
- usage: [`rigc skills install [--dir ${DEFAULT_SKILLS_DIR}] [--copy] (every skill this package ships, where an agent host looks)`],
1995
+ usage: [`rigc skills install [--dir ${DEFAULT_SKILLS_DIR}] [--copy] (every skill this package and any installed rig-c package ship, where an agent host looks)`],
1990
1996
  flags: ['dir', 'copy'],
1991
1997
  notes: [
1992
- 'the skills installed are the skills/ directory of the package this command runs from,',
1993
- 'never whatever the working directory holds. Each becomes <dir>/<name>: a relative',
1994
- 'symlink into that folder, or with --copy a copy of it. An entry already there that',
1995
- 'is not a link to the same folder — or, with --copy, not the same bytes — is refused',
1996
- 'by name, exit 1, and nothing is written; a run over only what this command made has',
1997
- 'nothing to do and exits 0. Codex, Gemini CLI and Antigravity read',
1998
+ 'scans the skills/ directory of the package this command runs from and every',
1999
+ 'node_modules/@rig-c/*/skills/<name>/SKILL.md and rig-c alias package (resolving by',
2000
+ 'realpathSync into @rig-c/* or declaring a github.com/rig-c/ repository in',
2001
+ 'package.json, never by directory name alone) reachable',
2002
+ 'from the project\'s node_modules (never a loose skills/ in the working directory),',
2003
+ 'reporting which package each skill came from. An alias or symlink that resolves',
2004
+ '(realpathSync) to the same skill folder is deduplicated; two copies of the same',
2005
+ 'product (rig-c ↔ @rig-c/core, rig-<name> ↔ @rig-c/<name>) choose the project\'s',
2006
+ 'node_modules copy first and the scoped package over the alias, printing which copy',
2007
+ 'was chosen; two different products shipping the same skill name refuse the call',
2008
+ 'naming both sources and write nothing. Each skill becomes <dir>/<name>: a relative',
2009
+ 'symlink into that folder, or with --copy a copy of it (use `bunx @rig-c/core skills',
2010
+ 'install --copy` in a project without @rig-c/core installed so no link points into a',
2011
+ 'temporary .bunx cache). An entry already there that is not a link to the same folder',
2012
+ '— or, with --copy, not the same bytes — is refused by name, exit 1, and nothing is',
2013
+ 'written; a run over only what this command made has nothing to do and exits 0. Codex,',
2014
+ 'Gemini CLI and Antigravity read',
1998
2015
  `<workspace>/${DEFAULT_SKILLS_DIR}; Claude Code installs the plugin instead (README,`,
1999
2016
  '"Install it into your agent").',
2000
2017
  ],
@@ -2172,12 +2189,14 @@ const USAGE_PARAGRAPHS: ReadonlyArray<{ about: readonly string[] | { flag: strin
2172
2189
  {
2173
2190
  about: ['skills'],
2174
2191
  lines: () => [
2175
- 'skills install puts the agent skills this package ships where an agent host looks',
2176
- 'for them, since none of them reads node_modules:',
2192
+ 'skills install puts the agent skills this package and every installed @rig-c/* or',
2193
+ 'alias package ship where an agent host looks for them, since none reads node_modules:',
2177
2194
  ` rigc skills install relative links in ${DEFAULT_SKILLS_DIR}, which Codex, Gemini CLI`,
2178
2195
  ' and Antigravity read; --copy writes the folders instead',
2179
- 'An entry already there that this command did not make is refused by name and',
2180
- 'nothing is written; a second run has nothing to do. See `rigc skills --help`.',
2196
+ 'Two copies of the same product choose the project copy first and the scoped package',
2197
+ 'over the alias; an entry already there that this command did not make, or a duplicate',
2198
+ 'skill name across two different products, is refused by name and nothing is written.',
2199
+ 'See `rigc skills --help`.',
2181
2200
  ],
2182
2201
  },
2183
2202
  {