@mrciphersmith/keryx 0.3.44 → 0.3.48

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.
@@ -0,0 +1,669 @@
1
+ # Reviewer Skill Creator — detail
2
+
3
+ Overflow reference for `core/reviewer-skill-creator`, linked from its SKILL.md
4
+ section "Bulk import of overlay reviewers". Nothing here overrides SKILL.md.
5
+
6
+ ## Importing reviewers from an overlay — a worked example
7
+
8
+ The output below was produced by running the commands against this tree, kept
9
+ inside the project at `vendor/acme-overlay`. Lines cut for length are marked `…`.
10
+
11
+ ```
12
+ vendor/acme-overlay/
13
+ skills/
14
+ review-acme-api/SKILL.md reviewer; triggers and flags only in its description
15
+ review-acme-styling/SKILL.md reviewer; declares metadata.paths and metadata.flags
16
+ review-acme-naming/SKILL.md reviewer; its description names one file and no glob
17
+ review-acme-legacy/SKILL.md `deprecated: true` alias of review-acme-api
18
+ acme-review/SKILL.md the overlay's own review entry point (an orchestrator)
19
+ review-logic/SKILL.md the overlay's copy of a reviewer keryx ships
20
+ rules/core/
21
+ error-handling.mdc same filename as a rule keryx ships
22
+ acme-api.mdc a name only the overlay has
23
+ ```
24
+
25
+ The project already has one reviewer of its own, `review-house-api`, selected
26
+ by `--api` and gated on `src/api/**`. It matters in section 2.
27
+
28
+ `--from` takes that tree (a directory of packages, or a parent that contains
29
+ `skills/`), or one package directory. `keryx review import` is the review-shaped
30
+ spelling of `keryx skills import --module review`: one importer, the module
31
+ implied. For a non-review skill, or a GitHub `SKILL.md` URL, use
32
+ `keryx skills import` directly.
33
+
34
+ ### 1. A tree without `--only` is refused
35
+
36
+ ```
37
+ $ keryx review import --from ./vendor/acme-overlay
38
+ keryx review import: ./vendor/acme-overlay holds 6 packages and the import targets module review, where review-orchestrator dispatches every package it finds. Say which ones with --only <glob> (repeatable; matched against the package directory name).
39
+
40
+ Candidates:
41
+ - acme-review
42
+ - review-acme-api
43
+ - review-acme-legacy (deprecated — skipped in a tree import)
44
+ - review-acme-naming
45
+ - review-acme-styling
46
+ - review-logic (bundled keryx skill name — skipped unless --force)
47
+
48
+ Example:
49
+ keryx review import --from ./vendor/acme-overlay --only 'review-acme-api'
50
+ ```
51
+
52
+ Exit code 1, nothing written. The reason is what module `review` means:
53
+ `review-orchestrator` dispatches every package registered there, and a package
54
+ with no path gate is dispatched on every round. A tree imported whole brings its
55
+ entry point, its deprecated aliases and whatever else sits beside the reviewers,
56
+ and each of those becomes a sub-agent that runs on every review. So the
57
+ selection is yours to state, not something inferred from how one overlay names
58
+ its packages. The candidate list is the input for that decision: read it and
59
+ write the glob.
60
+
61
+ `--only` is a glob over the package **directory name** as it is on disk (`*`
62
+ any run of characters, `?` one), and it is repeatable: `--only 'review-acme-*'
63
+ --only 'code-acme-*'`. A glob that matches nothing is refused with the same
64
+ list. A single package directory, or a `SKILL.md` file, needs no `--only`.
65
+
66
+ An `--only` with no glob in it is an error, not "no selection":
67
+
68
+ ```
69
+ $ keryx review import --from ./vendor/acme-overlay --only ''
70
+ keryx review import: --only needs a glob; an empty value selects nothing. Pass a package directory name or a glob over them (`*`, `?`), or drop --only.
71
+ ```
72
+
73
+ The directory name is what you select by; the **slug** of that name is what
74
+ gets written. A directory `review_acme_api` is matched by `--only
75
+ 'review_acme_*'` (and by `--name review_acme_api` in `keryx skills import`),
76
+ and lands as `review/review-acme-api`: the destination, the "already exists"
77
+ and bundled-name checks, and the row the import prints all use the slug. Two
78
+ selected directories that slug to one destination are refused before anything
79
+ is written — here a tree holding both `review-dup` and `review_dup`:
80
+
81
+ ```
82
+ $ keryx review import --from ./dup --only 'review*' --dry-run
83
+ keryx review import: review-dup and review_dup both import as review/review-dup. Narrow --only to one of them, or rename one directory.
84
+ ```
85
+
86
+ A directory name with no letter or digit has no slug to be written under, and
87
+ is refused the same way:
88
+
89
+ ```
90
+ $ keryx review import --from ./vendor/odd/___ --dry-run
91
+ keryx review import: package directory ___ has no letter or digit to name it by. Rename the directory, or import its SKILL.md under a name you choose: keryx skills import --from ./vendor/odd/___/SKILL.md --module review --name <name>
92
+ ```
93
+
94
+ Before the first write, every destination the import would touch — the
95
+ package, the registry in `.metaproject/metaproject.json`, the catalog, each
96
+ rule — is checked for a symlink on the way that resolves outside the project.
97
+ One is refused, nothing is written, and a dry run refuses it with the same
98
+ message. `keryx skills update` checks the same way. Here the package directory
99
+ had been replaced by a link to a directory outside the project:
100
+
101
+ ```
102
+ $ keryx review import --from ./vendor/acme-overlay/skills/review-acme-naming --dry-run
103
+ keryx review import: .metaproject/project-skills/review/review-acme-naming/SKILL.md: refuses to write through a symlink at .metaproject/project-skills/review/review-acme-naming that resolves outside the project root. Nothing was written.
104
+ ```
105
+
106
+ ### 2. Dry-run first
107
+
108
+ ```
109
+ $ keryx review import --from ./vendor/acme-overlay --only 'review-acme-*' --dry-run
110
+ # skills import
111
+
112
+ from: ./vendor/acme-overlay
113
+ dry-run: yes
114
+ force: no
115
+ imported: 0 overwritten: 0 updated: 0 skipped: 1 would-import: 3 would-overwrite: 0
116
+
117
+ ## would import (3) — dry run, nothing written
118
+
119
+ - review/review-acme-api
120
+ - review/review-acme-naming
121
+ - review/review-acme-styling
122
+
123
+ ## packages
124
+
125
+ - review/review-acme-api: would-import — review-orchestrator will dispatch this after `keryx review reviewers` lists it
126
+ - warning: flag --api is carried by one existing project reviewer, review-house-api: it becomes a family flag for both, so --api now selects review-house-api path-gated instead of dispatching it outright.
127
+ - review/review-acme-legacy: skipped — deprecated
128
+ - review/review-acme-naming: would-import — review-orchestrator will dispatch this after `keryx review reviewers` lists it
129
+ - warning: paths: none — dispatched on every round. Declare `metadata.paths: "<glob>, <glob>"` in its frontmatter to gate it on the diff.
130
+ - review/review-acme-styling: would-import — review-orchestrator will dispatch this after `keryx review reviewers` lists it
131
+ - warning: metadata.flags: "--acme_css" dropped — a flag is `--` and a name of lower-case letters, digits and dashes that starts with a letter
132
+
133
+ A package whose frontmatter says `deprecated: true` is skipped in a tree import. To import one anyway, pass its own directory as --from.
134
+
135
+ ## rules the skills cite
136
+
137
+ - core/acme-api.mdc: would-import — from vendor/acme-overlay/rules/core/acme-api.mdc (cited by review-acme-api)
138
+ - core/acme-styling.mdc: unresolved — no rules/ directory beside the source has it (cited by review-acme-styling)
139
+ - core/error-handling.mdc: differs — .metaproject/rules/core/error-handling.mdc is not the overlay's version; the overlay's would be written to .metaproject/rules/project/core/error-handling.mdc, which is the file the reviewer would read — from vendor/acme-overlay/rules/core/error-handling.mdc (cited by review-acme-api)
140
+ …
141
+ ```
142
+
143
+ Read three things off it before running for real:
144
+
145
+ - **The `would import` list is the whole answer to "what lands".** Here the glob
146
+ also matched `review-acme-legacy`, and it is skipped as `deprecated` — a tree
147
+ import skips a deprecated package even with `--force`. `acme-review` and
148
+ `review-logic` did not match the glob, so they are not rows at all.
149
+ - **Every `warning:` row.** There are three kinds here, and each is a decision
150
+ to make before the real run:
151
+ - `paths: none` — that reviewer will run on every round whatever the diff
152
+ touches. The remedy is `metadata.paths` in the overlay's frontmatter
153
+ (section 4).
154
+ - `metadata.flags: … dropped` — an entry of the declared list is not a flag
155
+ and will not select the reviewer. Fix the spelling in the overlay. When no
156
+ entry survives, a second line, `metadata.flags: no entry is a flag`, says
157
+ the reviewer now has no selection flags at all (section 4, `flags`).
158
+ - `flag … is carried by one existing project reviewer` — the import changes
159
+ how a reviewer the project **already has** is dispatched. Until now `--api`
160
+ dispatched `review-house-api` outright; once a second reviewer carries it,
161
+ `--api` is a family flag for both and each stays path-gated. If the
162
+ existing reviewer must keep its own flag, rename the flag in the overlay.
163
+ The warning is about reviewers that were on disk before the import and are
164
+ not replaced by it; packages arriving together that share a flag (`--acme`)
165
+ are the overlay's own family and get no warning.
166
+
167
+ A fourth kind appears only on a `--force` overwrite: `flag … is dropped by
168
+ this version of …, which leaves … its only carrier` — the reverse change,
169
+ shown under "Refresh when the origin moves".
170
+ - **Every rule row that is not `would-import` or `present`.** `unresolved` means
171
+ the reviewer will cite a rule the project will not have. `differs` means a
172
+ file already answers that reference with other content.
173
+
174
+ A dry-run does not run the security gate that a real run applies to every file
175
+ it writes, so a `would-…` status can be optimistic: the real run may still block
176
+ a package, or report a rule as `unresolved` with `blocked by the security gate`.
177
+
178
+ ### 3. The real run, and what it writes
179
+
180
+ ```
181
+ $ keryx review import --from ./vendor/acme-overlay --only 'review-acme-*'
182
+ # skills import
183
+
184
+ from: ./vendor/acme-overlay
185
+ dry-run: no
186
+ force: no
187
+ imported: 3 overwritten: 0 updated: 0 skipped: 1 would-import: 0 would-overwrite: 0
188
+
189
+ - review/review-acme-api: imported — review-orchestrator will dispatch this after `keryx review reviewers` lists it
190
+ - warning: flag --api is carried by one existing project reviewer, review-house-api: it becomes a family flag for both, so --api now selects review-house-api path-gated instead of dispatching it outright.
191
+ - review/review-acme-legacy: skipped — deprecated
192
+ - review/review-acme-naming: imported — review-orchestrator will dispatch this after `keryx review reviewers` lists it
193
+ - warning: paths: none — dispatched on every round. Declare `metadata.paths: "<glob>, <glob>"` in its frontmatter to gate it on the diff.
194
+ - review/review-acme-styling: imported — review-orchestrator will dispatch this after `keryx review reviewers` lists it
195
+ - warning: metadata.flags: "--acme_css" dropped — a flag is `--` and a name of lower-case letters, digits and dashes that starts with a letter
196
+ …
197
+ ## rules the skills cite
198
+
199
+ - core/acme-api.mdc: imported — from vendor/acme-overlay/rules/core/acme-api.mdc (cited by review-acme-api)
200
+ - core/acme-styling.mdc: unresolved — no rules/ directory beside the source has it (cited by review-acme-styling)
201
+ - core/error-handling.mdc: differs — .metaproject/rules/core/error-handling.mdc is not the overlay's version; the overlay's was written to .metaproject/rules/project/core/error-handling.mdc, which is the file the reviewer reads — from vendor/acme-overlay/rules/core/error-handling.mdc (cited by review-acme-api)
202
+
203
+ A reviewer that cites `<dir>/<name>.mdc` reads `.metaproject/rules/project/<dir>/<name>.mdc` when that file exists,
204
+ and `.metaproject/rules/<dir>/<name>.mdc` otherwise. `keryx init`, `keryx update` and `keryx skills install` overwrite
205
+ rules/core with keryx's own rules and leave rules/project alone. `keryx review reviewers` lists each such reference
206
+ under `shadowedRules`.
207
+
208
+ Reviewers: `keryx review reviewers` must list every imported review/* name. That is the same call review-orchestrator makes.
209
+ ```
210
+
211
+ The note's "`keryx skills install` overwrite rules/core" is true of one of its
212
+ two forms. **`keryx init`, `keryx update` and the legacy-profile form of
213
+ `keryx skills install`** (`--profile minimal|recommended|full|custom` with no
214
+ manifest flag) copy keryx's rules over `rules/core`. The manifest form with
215
+ `--target keryx-shell` also writes `rules/core`, but skips an existing file its
216
+ install-state did not record (or that drifted from it) unless `--force`;
217
+ `--target claude` writes `.claude/rules/` and leaves `rules/core` alone. None
218
+ of them writes or cleans `rules/project`. The other mentions below mean this.
219
+
220
+ Per imported package, four things now exist:
221
+
222
+ - **The package directory** `.metaproject/project-skills/review/<name>/`, holding
223
+ `SKILL.md` and `skill-changelog.md`. The import copies `SKILL.md` and the rules
224
+ it cites, nothing else: a checklist, template or script the reviewer points at
225
+ is not brought along (section 4, `unresolvedReferences`).
226
+ - **A header stamped into `SKILL.md`**, directly after the frontmatter. The body
227
+ below it is the source's, unchanged:
228
+
229
+ ```
230
+ Version: 1.2.0
231
+ Target: review-acme-api
232
+ Module: review
233
+ Origin: vendor/acme-overlay/skills/review-acme-api/SKILL.md
234
+ Origin Hash: sha256:d6cfb23a47093ac46c5b1788bd1325cbba0c08b76e749c40f371f474589eef2f
235
+ Imported At: 2026-09-30T15:49:48.204Z
236
+ Status: active
237
+ Last Verified: never
238
+ ```
239
+
240
+ `Origin`, `Origin Hash` and `Imported At` are what drift is computed from.
241
+ `Origin` is recorded project-relative when the source is inside the project,
242
+ as `~/…` when it is under the home directory, and absolute only otherwise — an
243
+ absolute origin is true on one machine and reads as drift `missing` on every
244
+ other.
245
+ - **A `projectSkillRegistry` entry** in `.metaproject/metaproject.json`
246
+ (`modules.gdskills.projectSkillRegistry`):
247
+
248
+ ```json
249
+ {
250
+ "module": "review",
251
+ "name": "review-acme-api",
252
+ "target": "review-acme-api",
253
+ "path": ".metaproject/project-skills/review/review-acme-api",
254
+ "version": "1.2.0",
255
+ "status": "active",
256
+ "updatedAt": "2026-09-30T15:49:48.246Z"
257
+ }
258
+ ```
259
+ - **A catalog row** in the Project Skills section of `.metaproject/skills/catalog.md`:
260
+
261
+ ```
262
+ | review | review-acme-api | `review-acme-api` | .metaproject/project-skills/review/review-acme-api/SKILL.md |
263
+ ```
264
+
265
+ **Rules.** For each backticked `.mdc` rule a selected package cites, the import
266
+ looks for the file in a `rules/` directory beside the overlay's skills and
267
+ reports one status:
268
+
269
+ - `imported` — the project did not have it; copied to `.metaproject/rules/<ref>`
270
+ (here `acme-api.mdc`, into the project's `rules/core`).
271
+ - `present` — the project already has it, identical to the overlay's, or the
272
+ overlay has no version to compare with.
273
+ - `differs` — a file already answers that reference with other content. The
274
+ overlay's version is written to the reference's project slot,
275
+ `.metaproject/rules/project/<dir>/<name>.mdc`, and the existing file is left
276
+ untouched. This is the ordinary case for a filename keryx also ships
277
+ (`error-handling.mdc` above, written to `rules/project/core/`): the file in
278
+ `rules/core` is keryx's generic rule, not the overlay's.
279
+ - `imported-project` — a `core/<name>.mdc` that keryx ships under the same name
280
+ and the project has no file for yet; the overlay's goes to
281
+ `.metaproject/rules/project/core/<name>.mdc` rather than to `rules/core`,
282
+ because `keryx init`, `keryx update` and the legacy-profile
283
+ `keryx skills install` overwrite `rules/core` with keryx's own rules.
284
+ - `unresolved` — no `rules/` directory beside the source has it. Add the file by
285
+ hand, or the reviewer keeps citing a rule the project lacks.
286
+
287
+ The project slot keeps the whole reference, directory included:
288
+ `core/error-handling.mdc` resolves to
289
+ `.metaproject/rules/project/core/error-handling.mdc` first and to
290
+ `.metaproject/rules/core/error-handling.mdc` second, and a
291
+ `house/error-handling.mdc` would resolve to
292
+ `.metaproject/rules/project/house/error-handling.mdc` first — two rules with
293
+ one filename never share a slot. A file lying directly in `rules/project/` is nobody's slot:
294
+ it is read only by a reviewer whose text cites it literally, as
295
+ `project/<name>.mdc`. Nothing loads rules for a reviewer, so the order takes
296
+ effect in one place: `review-orchestrator` reads `shadowedRules` from the
297
+ inventory and tells the reviewer, in its dispatch prompt, which file to read. A
298
+ reviewer run outside the orchestrator reads what its text names. An existing
299
+ project-slot copy that differs from the overlay's is left as it is — it may be
300
+ a hand edit — unless `--force` is passed.
301
+
302
+ Running the same command again is safe and is how rules are fetched for an
303
+ import made earlier: packages come back `skipped — already exists; pass --force
304
+ to overwrite`, and the rule rows are re-resolved — `present` for both files
305
+ written above, the second with `the reviewer reads
306
+ .metaproject/rules/project/core/error-handling.mdc`.
307
+
308
+ ### 4. Reading `keryx review reviewers`
309
+
310
+ ```
311
+ $ keryx review reviewers
312
+ …
313
+ ## project-local
314
+
315
+ - review-acme-api (vendor/acme-overlay/skills/review-acme-api/SKILL.md — clean)
316
+ - paths: src/api/**, src/server/routes.ts [description]
317
+ - flags: --acme, --acme-api, --api
318
+ - family flags: --acme, --api — shared with another project reviewer: selects it, stays path-gated
319
+ - review-acme-naming (vendor/acme-overlay/skills/review-acme-naming/SKILL.md — clean)
320
+ - paths: none — dispatched on every round [none]
321
+ - flags: --acme-naming
322
+ - review-acme-styling (vendor/acme-overlay/skills/review-acme-styling/SKILL.md — clean)
323
+ - paths: src/**/*.css, src/theme/** [metadata]
324
+ - flags: --acme, --acme-styling
325
+ - family flags: --acme — shared with another project reviewer: selects it, stays path-gated
326
+ - warning: metadata.flags: "--acme_css" dropped — a flag is `--` and a name of lower-case letters, digits and dashes that starts with a letter
327
+ - review-house-api (house/review-house-api/SKILL.md — clean)
328
+ - paths: src/api/** [description]
329
+ - flags: --api
330
+ - family flags: --api — shared with another project reviewer: selects it, stays path-gated
331
+
332
+ ## rules cited but not in .metaproject/rules
333
+
334
+ - review-acme-styling: core/acme-styling.mdc
335
+ …
336
+ ## rules read from .metaproject/rules/project
337
+
338
+ - review-acme-api: `core/error-handling.mdc` → .metaproject/rules/project/core/error-handling.mdc
339
+ …
340
+ ```
341
+
342
+ This is the call `review-orchestrator` makes (with `--json`), so what it prints
343
+ is what a round will do. `review-house-api`'s last row is what the import
344
+ warned about: `--api` is now a family flag for it. The frontmatter the three
345
+ imported rows were derived from:
346
+
347
+ ```yaml
348
+ # review-acme-api — nothing declared; everything is read out of the description
349
+ description: |
350
+ Use when reviewing Acme API handlers against the Acme error contract.
351
+ Dispatched for --acme, --acme-api, --api, --all, or changes under src/api/** and
352
+ src/server/routes.ts. NOT for styling.
353
+ metadata:
354
+ stack_requires: "http-server"
355
+
356
+ # review-acme-styling — declared; the description is not consulted for these
357
+ metadata:
358
+ paths: "src/**/*.css, src/theme/**"
359
+ flags:
360
+ - acme
361
+ - --Acme-Styling
362
+ - --acme_css
363
+
364
+ # review-acme-naming — a file path in prose, and no glob anywhere
365
+ description: |
366
+ Use when reviewing names against the Acme naming guide. Dispatched for
367
+ --acme-naming or --all. The pattern to follow is in src/app/names.ts.
368
+ ```
369
+
370
+ The frontmatter is read by one reader, shared by the import, this inventory,
371
+ `keryx review jev-rules` and the stack gate. It reads a subset of YAML, and a
372
+ shape outside the subset reads as not declared — the same as an absent key:
373
+
374
+ - the block opens with `---` on the first line; a leading BOM is ignored and
375
+ CRLF line endings read as LF;
376
+ - a `# comment` after whitespace, outside quotes, is dropped;
377
+ - `metadata.paths` and `metadata.flags` may be a comma-separated string, a flow
378
+ list (`[a, b]`) or a block list of `- item` lines; empty items are skipped.
379
+ `metadata.stack_requires` is read as a string only;
380
+ - only keys directly under `metadata:` are its fields — a `paths:` nested a
381
+ level deeper is not `metadata.paths`;
382
+ - a flow mapping, an anchor, a nested list or a multi-line quoted scalar is
383
+ outside the subset;
384
+ - `deprecated: true` and `category` count at the top level or under
385
+ `metadata:`.
386
+
387
+ How each field of a `project` entry in `--json` is derived:
388
+
389
+ - **`flags`** — `metadata.flags` when it has at least one non-empty entry;
390
+ otherwise every `--flag` the description names. So `flags: []` and
391
+ `flags: ''` leave the description's flags in force — they do not switch them
392
+ off. A declared flag entry is normalised before anything compares it: split
393
+ on commas and whitespace, trimmed, lower-cased, and given `--` when it has no
394
+ leading dash — so `acme` and `--Acme-Styling` above became `--acme` and
395
+ `--acme-styling`. `--all` is excluded either way: it selects every reviewer
396
+ already. A list with entries replaces the description even when none of them
397
+ survives: a list of only non-flags (`[--acme_css]`) and a list of only
398
+ `--all` both leave the reviewer with no flags, selected by its paths or by
399
+ `--all`.
400
+ - **`flagWarnings`** — one line per `metadata.flags` entry that is still not
401
+ `--` plus a lower-case name after normalising (`--acme_css` above). The entry
402
+ is dropped from `flags`, and the line is printed as a `warning:` row here and
403
+ on the import row. When dropping left no flag, a second line says so. A list
404
+ of only `--all` gets no warning. Empty when there is nothing to say.
405
+ - **`familyFlags`** — the subset of `flags` that at least one other project
406
+ reviewer also carries (`--acme` and `--api` above). A passed family flag
407
+ selects the reviewer and leaves it path-gated; a passed flag only this
408
+ reviewer carries (`--acme-styling`) selects it explicitly, with no path gate.
409
+ - **`paths` and `pathsSource`** — `metadata.paths` when declared
410
+ (`pathsSource: metadata`); it wins. Otherwise the triggers found in the
411
+ description (`pathsSource: description`): any token with a `*` that looks like
412
+ a path, and — only in a description that has at least one such glob — any
413
+ literal repo-relative file path beside it, such as `src/server/routes.ts`;
414
+ `src/**/*.ts(x)` expands to both spellings. A cited document (`.md`, `.mdc`)
415
+ and a path under `.metaproject/` are never triggers, and neither is prose
416
+ without a glob or an extension (`date/temporal utils`). A description with no
417
+ glob yields no trigger at all, whatever file paths it names: prose is full of
418
+ things shaped like one (`React/Next.js`), and a trigger that matches no file
419
+ would gate the reviewer off every round. That is `review-acme-naming` above —
420
+ `src/app/names.ts` is not a trigger, the entry has `paths: []` and
421
+ `pathsSource: none`, and the reviewer is dispatched on every round, with the
422
+ import warning.
423
+
424
+ The description reader works on prose, so it can be wrong in both directions:
425
+ beside a glob, a file the description merely mentions ("see src/app/main.ts
426
+ for the pattern") becomes a trigger, and a directory named without a glob
427
+ yields nothing. When the printed `paths` are not what the reviewer should be
428
+ gated on, declare `metadata.paths` in the overlay's frontmatter and refresh
429
+ the import — do not reword the description until the guess comes out right.
430
+ - **`stackRequires`** — `metadata.stack_requires`, a comma-separated string of the
431
+ tags `keryx review stack` knows (`nestjs`, `react`, `mobx`, `prisma`,
432
+ `playwright`, `sql`, `http-server`). An unknown tag is dropped, which leaves
433
+ the reviewer unscoped rather than excluded.
434
+ - **`unresolvedRules`** — backticked `.mdc` rules the text cites that exist
435
+ neither at `.metaproject/rules/project/<ref>` nor at `.metaproject/rules/<ref>`.
436
+ Printed under "rules cited but not in .metaproject/rules". The reviewer is
437
+ still dispatched; add the rule, or accept that it reviews without it.
438
+ - **`shadowedRules`** — `[{ ref, resolved }]`: rules the text cites as
439
+ `<dir>/<name>.mdc` that the project-slot copy answers first — above,
440
+ `{ "ref": "core/error-handling.mdc", "resolved":
441
+ ".metaproject/rules/project/core/error-handling.mdc" }`. Printed under
442
+ "rules read from .metaproject/rules/project". Expected after a `differs` or
443
+ `imported-project` row; it is a fact to check, not a defect.
444
+ - **`unresolvedReferences`** — `[{ ref, reason }]`, printed under "references
445
+ that do not resolve". `missing`: a backticked path starting `skills/` or
446
+ `rules/` and ending `.md` or `.json` with no file at that path under
447
+ `.metaproject/` — the import did not bring it; copy it there or edit the
448
+ reference out. `non-portable`: a rule cited by an absolute or `~` path, which
449
+ exists on one machine at most; cite it as `core/<name>.mdc` instead.
450
+ - **`drift`** — `clean`, `changed` or `missing`, from re-hashing the file
451
+ `Origin` names against `Origin Hash`. It is the word after the dash on the
452
+ reviewer's row. `none` means no origin was recorded — a reviewer made with
453
+ `keryx skills create` and no `--origin` — and its row reads
454
+ `- <name> (no recorded origin)`, with no dash. A remote (`https://`) origin
455
+ always lists as `clean`: listing opens no connection, and `keryx skills
456
+ update` is what re-reads it.
457
+
458
+ ## What is not a reviewer
459
+
460
+ Module `review` is a dispatch list, not a folder for review-adjacent skills. A
461
+ package belongs there only if it can be handed a bounded diff and return
462
+ `REVIEW_RESULT` under the orchestrated review contract. Leave these out of
463
+ `--only`:
464
+
465
+ - **An orchestrator, facade or entry point** (`acme-review` above). Registered
466
+ as a reviewer it is dispatched as a sub-agent of the round it was written to
467
+ run.
468
+ - **A deprecated alias.** The tree import skips a package whose frontmatter says
469
+ `deprecated: true`; one whose deprecation is only stated in prose is yours to
470
+ exclude.
471
+ - **A report generator, comment interpreter or other post-processing skill.** It
472
+ consumes findings; it does not produce them.
473
+ - **Anything with no review contract** — a rule digest, a style guide, a
474
+ checklist with no output schema.
475
+ - **A copy of a reviewer keryx ships** (`review-logic` above). It is skipped
476
+ unless `--force`, and forcing it shadows the bundled one.
477
+
478
+ Naming a package by its own directory is the operator asking for it, so none of
479
+ the tree protections apply: a deprecated package is imported, with a warning.
480
+ If one of these is worth having in the project, import it under another module
481
+ (`keryx skills import --from <package-dir> --module orchestration`): it is
482
+ registered for `keryx skills route` and is not dispatched by review rounds.
483
+
484
+ ## Undo
485
+
486
+ ```
487
+ $ keryx skills remove review/review-acme-naming --dry-run
488
+ Would remove project skill: review/review-acme-naming
489
+ - absent: verification report — .metaproject/data/gdskills/reports/review-review-acme-naming-verification.json
490
+ - would remove: catalog row — .metaproject/skills/catalog.md
491
+ - would remove: package directory — .metaproject/project-skills/review/review-acme-naming
492
+ - would remove: registry entry — .metaproject/metaproject.json
493
+ Dry run: nothing was changed.
494
+
495
+ $ keryx skills remove review/review-acme-naming
496
+ Removed project skill: review/review-acme-naming
497
+ - absent: verification report — .metaproject/data/gdskills/reports/review-review-acme-naming-verification.json
498
+ - removed: catalog row — .metaproject/skills/catalog.md
499
+ - removed: package directory — .metaproject/project-skills/review/review-acme-naming
500
+ - removed: registry entry — .metaproject/metaproject.json
501
+ ```
502
+
503
+ One skill per call, named exactly as registered. It removes the four things an
504
+ import registered, plus the `review/` directory when that was the last skill in
505
+ it, in the order printed: the verification report, the catalog row, the
506
+ package directory, the module directory, and the registry entry last. `absent`
507
+ means the part was already gone and is not an error.
508
+
509
+ If a step fails — a read-only directory, say — the error names the part that
510
+ failed, what is already gone and what is still in place; a package directory
511
+ whose recursive delete failed part-way is listed as `(may be partly removed)`,
512
+ since some of its files may already be gone. The registry entry is
513
+ still there, so running the same command again once the cause is fixed
514
+ finishes the removal. A skill whose entry and package are already gone is
515
+ still found by a leftover catalog row or verification report, so a removal
516
+ half-done by hand is finished the same way.
517
+
518
+ Names are case-sensitive, on every filesystem. A spelling that differs from the
519
+ registered one only in case is refused and names the registered spelling:
520
+
521
+ ```
522
+ $ keryx skills remove Review/review-acme-naming --dry-run
523
+ keryx skills remove: Review/review-acme-naming is not registered under that spelling; the registered spelling is review/review-acme-naming. Skill names are case-sensitive: run `keryx skills remove review/review-acme-naming`. Nothing was changed.
524
+ ```
525
+
526
+ The command deletes a directory recursively and takes its target from a
527
+ registry anyone can edit, so it checks where it is about to reach and refuses
528
+ (exit 1) before changing anything — a refused run leaves the registry, the
529
+ catalog and the disk as they were:
530
+
531
+ - a bundled skill, or a name that is not a project skill;
532
+ - a skill whose package directory is on disk only under another spelling
533
+ (`review/Alpha/` for `review/alpha`). On a filesystem that ignores case the
534
+ two are one directory, so removing the entry and row would leave that package
535
+ in place and still found as a skill. The refusal names the on-disk spelling;
536
+ rename the directory to `.metaproject/project-skills/<module>/<name>` and
537
+ retry;
538
+ - a symlink where something would be deleted: `.metaproject/project-skills`
539
+ itself, the module directory, the package, or the reports directory
540
+ `.metaproject/data/gdskills/reports`. It does not delete through the link and
541
+ does not unlink it; remove the link by hand if that is the intent;
542
+ - a `.metaproject/metaproject.json` or `.metaproject/skills/catalog.md` that
543
+ resolves outside the project. One reached through a symlink to a file inside
544
+ the project is rewritten in place, and the row names where the write lands;
545
+ - a registry entry whose `path` is not its own
546
+ `.metaproject/project-skills/<module>/<name>` — an entry pointing at the
547
+ module directory or at another skill's package would otherwise delete that;
548
+ - a registry entry whose key is not two plain path segments.
549
+
550
+ A verification report is removed only when it is this skill's: a report whose
551
+ body names another package (`skillPath`) is left alone even if its file name
552
+ matches.
553
+
554
+ It leaves in place, deliberately: the rules the import copied into
555
+ `.metaproject/rules/` — `rules/project` included — because other skills may
556
+ cite them; runtime exports under `.metaproject/runtime/skills/`; and learning
557
+ proposals under `.metaproject/data/gdskills/proposals/`. Delete a rule by hand
558
+ once nothing cites it, and check with `keryx review reviewers` that no reviewer
559
+ lists it as unresolved afterwards.
560
+
561
+ ## Refresh when the origin moves
562
+
563
+ After the overlay's `review-acme-api/SKILL.md` was edited:
564
+
565
+ ```
566
+ $ keryx review reviewers
567
+ …
568
+ - review-acme-api (vendor/acme-overlay/skills/review-acme-api/SKILL.md — changed)
569
+ …
570
+ ## origins that moved on
571
+
572
+ - review-acme-api: `vendor/acme-overlay/skills/review-acme-api/SKILL.md` changed since 2026-09-30T15:49:48.204Z
573
+ …
574
+ $ keryx skills update review/review-acme-api --dry-run
575
+ …
576
+ ## would import (1) — dry run, nothing written
577
+
578
+ - review/review-acme-api (overwrites the existing one)
579
+ …
580
+ $ keryx skills update review/review-acme-api
581
+ …
582
+ - review/review-acme-api: updated — review-orchestrator will dispatch this after `keryx review reviewers` lists it
583
+ ```
584
+
585
+ `changed` does not mean the reviewer is wrong; it still runs, from the older
586
+ text. `keryx skills update <module>/<name>` (or `--all`) re-reads each `Origin`
587
+ and overwrites the project's `SKILL.md`, re-stamping the hash — so an edit made
588
+ to the imported copy is lost. If the project deliberately differs from the
589
+ overlay, make the change in the overlay, or record the divergence and leave the
590
+ row `changed`.
591
+
592
+ `keryx skills update` refreshes `SKILL.md` only; it reports no rule rows. When
593
+ the overlay's rules moved too, re-run the import. `--force` on the import
594
+ overwrites packages that already exist and replaces a project-slot copy
595
+ (`rules/project/<dir>/<name>.mdc`) that differs from the overlay's:
596
+
597
+ ```
598
+ $ keryx review import --from ./vendor/acme-overlay/skills/review-acme-styling --force
599
+ …
600
+ - review/review-acme-styling: overwritten — review-orchestrator will dispatch this after `keryx review reviewers` lists it
601
+ - warning: metadata.flags: "--acme_css" dropped — a flag is `--` and a name of lower-case letters, digits and dashes that starts with a letter
602
+ …
603
+ ```
604
+
605
+ An overwrite can also change how a reviewer it leaves alone is dispatched.
606
+ When the new version drops a family flag and one other project reviewer is left
607
+ carrying it, that flag becomes the other reviewer's own and dispatches it past
608
+ its path gate — here the overlay dropped `--api` from `review-acme-api`:
609
+
610
+ ```
611
+ $ keryx review import --from ./vendor/acme-overlay/skills/review-acme-api --force --dry-run
612
+ …
613
+ - review/review-acme-api: would-overwrite — review-orchestrator will dispatch this after `keryx review reviewers` lists it
614
+ - warning: flag --api is dropped by this version of review-acme-api, which leaves review-house-api its only carrier: --api now dispatches review-house-api outright instead of selecting it path-gated.
615
+ …
616
+ ```
617
+
618
+ An origin that reads `missing` cannot be updated from: pass the new location
619
+ with `keryx skills update <module>/<name> --from <new-origin>`.
620
+
621
+ ## Registration is per clone where `.metaproject/` is ignored
622
+
623
+ Everything above lives under `.metaproject/`. Many projects git-ignore that
624
+ directory, and then an import registers reviewers in the clone it ran in and
625
+ nowhere else: a teammate, a fresh worktree and CI see none of them until the
626
+ import is run there too. Check with `git check-ignore .metaproject` before
627
+ telling anyone the reviewers are wired for the team.
628
+
629
+ Where `.metaproject/` is tracked, commit what the import wrote:
630
+ `.metaproject/project-skills/review/`, the registry entry in
631
+ `.metaproject/metaproject.json`, the catalog row, and the rules — including
632
+ `.metaproject/rules/project/`, which `keryx init`, `keryx update` and
633
+ `keryx skills install` (either form) never write or clean, so a clone that lacks it
634
+ silently reads keryx's generic rule under the same name. Keep the overlay at a path that resolves for everyone
635
+ (inside the repository, or the same `~/…` location), or `drift` reads `missing`
636
+ on every machine but the importer's.
637
+
638
+ ## `review-learning.config.json`
639
+
640
+ Optional, and separate from importing: it names which pull-request comment
641
+ authors `keryx review learn` may turn into lessons for one project skill. The
642
+ file is `.metaproject/review-learning.config.json`:
643
+
644
+ ```json
645
+ {
646
+ "schemaVersion": 1,
647
+ "skill": "review/review-acme-api",
648
+ "repo": "acme/shop",
649
+ "authors": ["alice", "bob"],
650
+ "reviewerProfiles": ["alice"]
651
+ }
652
+ ```
653
+
654
+ - `schemaVersion` — required, exactly `1`.
655
+ - `skill` — required, `<module>/<skill>` naming a project skill under
656
+ `.metaproject/project-skills/`: one `/`, no whitespace. The loader checks the
657
+ shape only.
658
+ - `repo` — required, `owner/repo`: the repository whose collected comments
659
+ teach that skill.
660
+ - `authors` — required, at least one GitHub login. Matching is
661
+ case-insensitive and otherwise exact. A present file with no authors is an
662
+ error rather than a pass that teaches nothing.
663
+ - `reviewerProfiles` — optional array of logins that also get a per-reviewer
664
+ profile rule. Every entry must be one of `authors` (case-insensitive);
665
+ omitted means nobody gets a profile.
666
+
667
+ There are no defaults: every required field must be present. No file at all is
668
+ the supported "this project does not learn" state and is not an error; a file
669
+ that is present and malformed is refused with the field named.