omp-llm-role 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +202 -12
  2. package/agents/designer.md +4 -3
  3. package/docs/dev/README.md +44 -7
  4. package/docs/dev/agent-authoring.md +59 -1
  5. package/docs/dev/architecture.md +106 -28
  6. package/docs/dev/ci.md +163 -0
  7. package/docs/dev/current-state.md +364 -24
  8. package/docs/dev/data-sources.md +231 -38
  9. package/docs/dev/explorer.md +175 -46
  10. package/docs/dev/quirks.md +77 -4
  11. package/docs/dev/releasing.md +130 -17
  12. package/docs/dev/scoring.md +851 -27
  13. package/docs/dev/testing.md +253 -0
  14. package/docs/dev/writes-and-state.md +232 -28
  15. package/docs/llm-role-rankings.md +149 -148
  16. package/package.json +814 -80
  17. package/skills/omp-llm-role-create-agent/SKILL.md +7 -1
  18. package/src/agent-architect.ts +148 -19
  19. package/src/agent-create.ts +800 -40
  20. package/src/agent-file.ts +2 -5
  21. package/src/architect-provenance.ts +16 -0
  22. package/src/availability.ts +191 -15
  23. package/src/benchmark-author.ts +9 -4
  24. package/src/benchmark-sources.ts +283 -18
  25. package/src/cli/create-role.ts +29 -1
  26. package/src/cli/llm-role-rank.ts +40 -1
  27. package/src/config-edit.ts +207 -80
  28. package/src/engine.ts +519 -75
  29. package/src/error-report.ts +562 -0
  30. package/src/explorer/boot.ts +36 -24
  31. package/src/explorer/explain.ts +128 -13
  32. package/src/explorer/scopes.ts +110 -0
  33. package/src/explorer/server.ts +126 -33
  34. package/src/extension.ts +464 -48
  35. package/src/features.ts +105 -0
  36. package/src/project-registry.ts +65 -0
  37. package/src/project-setup.ts +382 -0
  38. package/src/prompts/agent-creation-architect.md +2 -0
  39. package/src/prompts/project-profile-architect.md +33 -0
  40. package/src/prompts/project-profile-user.md +8 -0
  41. package/src/provider-display.ts +67 -0
  42. package/src/role-archetypes.ts +12 -2
  43. package/src/role-settings.ts +22 -0
  44. package/src/settings.ts +211 -24
  45. package/src/state.ts +1 -1
  46. package/src/updater.ts +234 -37
  47. package/web/app.js +434 -12
  48. package/web/index.html +1 -0
  49. package/web/style.css +44 -1
  50. package/docs/dev/spec.md +0 -633
package/README.md CHANGED
@@ -48,6 +48,16 @@ omp plugin install omp-llm-role@gott50-plugins
48
48
  session actually booted on the previous `default` selector (an explicit
49
49
  `omp --model X` is never clobbered), and only on a real change. Disable with
50
50
  `activateDefaultOnEmptySession=false`.
51
+ - **Provider in the model display.** When the live session model carries a
52
+ provider pin — a hand-set `providerPin`, or the automatic pin the
53
+ `providerPinning` capability writes — the status line's model segment shows
54
+ the route next to the model name, e.g. `DeepSeek V4.1 Flash @coreweave/fp8`.
55
+ The suffix is applied on session start, refreshed when a fallback switches the
56
+ model, and re-derived at the start of every turn, so it self-heals after omp
57
+ silently restores the primary model following a fallback cooldown. An unpinned
58
+ model's display is unchanged, and the suffix is display-only: it rides the
59
+ model object's `name`, so attribution, usage recording and model comparison
60
+ are unaffected.
51
61
  - **Surgical writes.** Only managed role lines, managed chain keys, and the
52
62
  plugin-managed `task.disabledAgents` names change; comments, blank lines and
53
63
  unknown keys stay byte-identical, and a no-change run writes nothing.
@@ -62,7 +72,7 @@ omp plugin install omp-llm-role@gott50-plugins
62
72
  | `smol` | Fast lightweight model: cheap and quick, still competent |
63
73
  | `slow` | Most capable model for hard problems; cost and speed as tiebreakers |
64
74
  | `vision` | Image understanding: vision index dominates |
65
- | `plan` | Planning: reasoning, math, long-context coherence |
75
+ | `plan` | Planning: reasoning and long-context coherence, priced so the cheapest capable planner wins |
66
76
  | `commit` | Commit messages: cheap and fast with decent general quality |
67
77
  | `tiny` | Background tasks (titles, memory): cheapest and fastest wins |
68
78
  | `task` | Subagents: agentic + tool calling, moderate cost sensitivity |
@@ -110,6 +120,7 @@ Global knobs:
110
120
  | `writeFallbackChains` | `true` | Write per-role fallback chains into `config.yml` |
111
121
  | `fallbackChainDepth` | `2` | Fallback models per role chain |
112
122
  | `activateDefaultOnEmptySession` | `true` | Reapply the new `default` selector to an empty session's active model |
123
+ | `errorReporting` | `off` | Opt-in reporting of **unexpected** plugin errors as GitHub issues on `Gott50/omp-llm-role`; one of `off`, `ask`, `auto` (see below) |
113
124
 
114
125
  Per-role knobs (`roles.<name>.*`):
115
126
 
@@ -120,10 +131,100 @@ Per-role knobs (`roles.<name>.*`):
120
131
  | `weights` | shipped set | Metric weights; must sum to 1.0 ± 0.01 |
121
132
  | `required` | shipped set | Eligibility gate (metrics a model must have); independent of weights |
122
133
  | `filters.image` | `false` (`vision`/`designer`: `true`) | Restrict the pool to image-capable models |
134
+ | `filters.tools` | `false` | Restrict the pool to models with a tool-capable route |
135
+ | `filters.minContextTokens` | `0` (off) | Drop routes whose endpoint context is below this (tokens) |
136
+ | `filters.minOutputTokens` | `0` (off) | Drop routes whose endpoint output ceiling is below this (tokens) |
137
+ | `filters.maxPriceUsdPerM` | `0` (off) | Drop a model whose thinking-adjusted blend exceeds this ($/M) |
123
138
  | `thinking` | shipped level | Thinking level appended to the selector (`off`…`max`, `auto`) |
139
+ | `providerPin` | `""` (unset) | Pin the role's requests to one OpenRouter provider route (`@<slug>`, may be tiered like `deepinfra/fp8`). The role is then priced and gated by that route — its billed price and p50 throughput, not the `1/price²` blend — and a model with no matching route is ineligible. A manual pin **overrides** the automatic pin the `providerPinning` capability would write |
140
+ | `cacheHitRate` | `0` (off) | Assumed cache-hit rate (0–1) for the role's input tokens; each route's effective input price becomes `h·cacheRead + (1−h)·input` before the 3:1 billed blend and the thinking factor. The `1/price²` weight basis stays the **listed** input price (the router's sort key), so a cheap cache on a lightly-weighted route cannot dominate; a route with no cache-read price keeps its full input price. `0`/absent = off (byte-identical ranking) |
124
141
  | `lambda` | derived | Explicit λ ($ per quality point) override |
125
142
  | `locked` | `false` | Leave the role alone: rank it but never rewrite its selector or chain (explorer toggle) |
126
143
 
144
+ **Capability flags** (`features.<id>` global, `roles.<role>.features.<id>` per
145
+ role). One named flag per opt-in capability; turning it on applies the plugin's
146
+ recommended settings for that capability to the role. Every individual knob
147
+ stays settable, and an explicit knob always beats the flag. **Every flag ships
148
+ OFF** — with no flags set, the resolved roles are byte-identical to the shipped
149
+ defaults.
150
+
151
+ | Flag | Recommended settings | What it buys |
152
+ |---|---|---|
153
+ | `endpointCeilings` | `filters.tools: true`, `filters.minOutputTokens: 16384` | Routes that cannot call tools and routes whose output ceiling is below 16 384 tokens leave the pool — a reasoning model is not cut off mid-thought |
154
+ | `cachePricing` | `cacheHitRate: 0.5` | The role is priced on a cache-heavy agent loop's invoice (the endpoint's `cacheReadPrice` blended in) rather than the sticker price |
155
+ | `providerPinning` | `preferOwnProvider: true`, `filters.preferredMinThroughput: 50` | Each request is bound to the model's **best provider route** — the max-value-for-money route (the `vfm` score, with a soft 50 tok/s floor that deprioritizes a pathologically slow route without dropping it) — the selector gains `@<slug>` — and the fallback chain carries the primary model on every gate-passing route except the pinned best, then each fallback model on every gate-passing route, so a down provider fails over to another provider of the same model before the chain switches models. The model is priced **and measured** on that route (its billed price and p50 throughput, so `q` moves too), falling back to the default blend only when it has no candidate route |
156
+ | `costCap` | `filters.maxPriceUsdPerM: 10` | The priciest tail of the eligible pool leaves it, so the pick cannot be an expensive outlier |
157
+
158
+ Precedence is one rule:
159
+
160
+ ```
161
+ effective def = shipped default ⊕ preset(enabled flags) ⊕ explicit user keys
162
+ ```
163
+
164
+ A flag is on when `roles.<role>.features.<id>` is present (it wins either way)
165
+ and otherwise when the global `features.<id>` is `true` — so a per-role `false`
166
+ beats a global `true`. The preset fills only knobs the role does not already
167
+ set, and an explicit key is merged last, so it always wins. `providerPinning`'s
168
+ recommended setting is a **soft per-model policy** (`preferOwnProvider`), not a
169
+ hard `providerPin`: it prices a model on its **best route** (the max-`vfm` route
170
+ — the value-for-money score, with the soft 50 tok/s floor — among the
171
+ filter-passing, non-degraded, priced routes the account's
172
+ allowed-providers whitelist permits) — the route's billed price
173
+ **and** its p50 throughput, so `q` moves as well as `priceEff` — and never makes
174
+ a model ineligible (the hard `providerPin` stays available by hand, and overrides
175
+ the automatic choice). `minContextTokens` is deliberately **not** part of `endpointCeilings` —
176
+ the complaint it would answer is an endpoint advertising a *fraction of the
177
+ model's own window*, which an absolute token floor cannot express, so it stays a
178
+ hand-set knob.
179
+
180
+ The flags are settable from every role-authoring surface: `--feature <id,...>`
181
+ (comma-separated) on `node src/cli/create-role.ts`, `/create-agent` and
182
+ `/project-roles` (`--list-features` lists them on the first two), plus the
183
+ explorer's Features panel. An
184
+ unknown flag id or a non-boolean value aborts the run with the offending key and
185
+ no write.
186
+
187
+ **Error reporting (`errorReporting`).** This knob ships **`off`** — installing
188
+ or upgrading the plugin never starts uploading anything. When something
189
+ *genuinely unexpected* happens (a `/refresh-roles` or authoring command throws,
190
+ or the updater hits an internal error), the plugin can file one structured
191
+ report — the plugin version, runtime, platform, the command that failed, the
192
+ error name/message/stack and a timestamp — as a GitHub issue on
193
+ `Gott50/omp-llm-role`. The three values:
194
+
195
+ - **`off`** — never files; behaviour is exactly today's (one warning line).
196
+ - **`ask`** — shows you the exact text and files it only if you confirm. In a
197
+ session with no UI (`omp -p`, RPC, a subagent) it never uploads: it notifies a
198
+ prefilled new-issue URL instead, so you can submit it yourself.
199
+ - **`auto`** — files without asking.
200
+
201
+ The payload is redacted before it leaves the machine (your home directory → `~`,
202
+ token-shaped strings → `<redacted>`) and carries **no** config contents, model
203
+ selectors, prompts or history rows. Filing goes through the GitHub API when a
204
+ token is available (`GITHUB_TOKEN` → `GH_TOKEN` → `gh auth token` — the token is
205
+ never stored in a settings file); a token that cannot write issues here, or no
206
+ token at all, falls through to a prefilled `…/issues/new` URL (opened in your
207
+ browser, or printed). A repeated crash comments on the existing open issue
208
+ instead of opening a new one, and the path is capped (one open issue per
209
+ fingerprint, one creation per fingerprint per UTC day, 3 per day) so a crash
210
+ loop cannot spam the tracker. Reporting is **best-effort**: a failure in it only
211
+ adds one notification line and never affects the run it reports on. Expected
212
+ environment conditions (no key budget, a concurrent refresh holding the lock,
213
+ write conflicts, unavailable ranking data, invalid settings) are **never**
214
+ reported. In project mode the project lock file's value applies.
215
+
216
+ **Validation.** A run aborts (notify, no write) on: a weight ≤ 0, or a role's
217
+ weights not summing to 1.0 ± 0.01; a `required` entry or a weighted metric
218
+ outside the known-metric set; a `thinking` level outside
219
+ `off|minimal|low|medium|high|xhigh|max|auto`; `switchMargin`/`priceSwitchFraction`
220
+ outside `[0, 1]`; a non-boolean `enabled`; a `cacheHitRate` outside `[0, 1]`; a
221
+ `providerPin` that is empty or
222
+ contains `@`/`:` (the selector's own delimiters); an unknown capability id
223
+ (global or per-role) or a non-boolean flag value; a non-boolean
224
+ `preferOwnProvider`. `weights: null` opts a role out;
225
+ the legacy `suffixes.*` keys are rejected with a migration hint (moved into
226
+ `roles.<role>.thinking`).
227
+
127
228
  **Typed writes.** `omp plugin config set <plugin> <key> <value>` stores every
128
229
  value as a **string**, which the validator rejects for numbers/arrays/booleans,
129
230
  so it is only usable for string-valued keys. Write typed role defs with
@@ -144,10 +245,11 @@ with the offending role/key and no write.
144
245
  /create-agent <request> [options] # agent + role + wiring (free text or flags)
145
246
  /create-agent --name <n> --purpose <text> [options] # agent + role + wiring
146
247
  /remove-agent --name <n> [--scope user|project] [--lock PATH] [--yes] [--dry-run] # delete an agent and its role
248
+ /project-roles [--purpose <text>] [--roles <spec>] [--feature <id,...>] [--force] [--yes] [--dry-run] [--json] # project-scoped role set
147
249
 
148
250
  # CLI
149
251
  node src/cli/llm-role-rank.ts [--top N] [--json] [--out FILE] [--refresh] [--all] [--url URL]
150
- node src/cli/create-role.ts --name <role> --weights m=w,... [--required m,...] [--thinking <level>] [--description <text>] [--image] [--lambda N] [--lock PATH] [--dry-run] [--json]
252
+ node src/cli/create-role.ts --name <role> --weights m=w,... [--required m,...] [--thinking <level>] [--description <text>] [--image] [--lambda N] [--feature <id,...>] [--list-features] [--lock PATH] [--dry-run] [--json]
151
253
  node --test tests/
152
254
  ```
153
255
 
@@ -161,6 +263,9 @@ node --test tests/
161
263
  - `/remove-agent` — the inverse of `/create-agent`: deletes the agent `.md` and
162
264
  its role, then runs the updater in-process so `modelRoles.<n>` is dropped;
163
265
  refuses shipped default roles (see *Remove a specialist agent*).
266
+ - `/project-roles` — discover the project's usecase from its own artifacts and
267
+ apply a project-scoped role set to `<cwd>/.omp/config.yml` (see *Project
268
+ roles*).
164
269
  - `src/cli/llm-role-rank.ts` — markdown report to stdout (per-role tables + suggested
165
270
  `modelRoles`). `--top N` rows per role (default 10); `--json` machine payload;
166
271
  `--out FILE` write instead of stdout; `--refresh` bypass both caches; `--all`
@@ -193,6 +298,25 @@ agent is ranked on WritingBench, Creative Writing v3, COLLIE and the other
193
298
  writing benchmarks, not just the one you named. Discovery is non-fatal and
194
299
  skippable with `--no-discover`; an explicit `--benchmarks` list wins over it.
195
300
 
301
+ Discovery is **quality-gated**: each candidate is assessed on nine axes —
302
+ coverage (share of the field carrying the metric), dispersion (does it separate
303
+ models), provider composition, freshness, trust (the share of the payload's
304
+ entries that are self-reported, so a mostly vendor-submitted source is flagged),
305
+ modality (the catalog row's modality and the payload's multimodal share — an
306
+ annotation, never a gate), maintenance (the catalog row's dataset age against
307
+ a 12-month bar — an annotation, never a gate), provenance (the benchmark's
308
+ owner and the source's own per-entry org mix, cross-checked against the
309
+ composition axis — an annotation, never a gate) and cross-source (the zeroeval
310
+ per-entry price/context against the plugin's own, flagging a divergence past a
311
+ tolerance — an annotation, never a gate, and never a change to the `1/price²`
312
+ blend) — and a discovered benchmark that
313
+ fails a gating axis is dropped rather than folded in (a benchmark you name yourself is
314
+ never dropped, only warned). The focus set is capped at three benchmarks so one
315
+ keeps a decisive share. The report prints the nine signals and warns per below-bar
316
+ axis, and warns when the new role's top pick is the same as the `default` role's
317
+ (the role adds nothing). The agent file is written before the role, so a partial
318
+ failure never leaves a dangling role.
319
+
196
320
  The flag form is equivalent and gives you the name explicitly:
197
321
 
198
322
  ```sh
@@ -206,7 +330,9 @@ validated role into the settings lock file, and runs the updater in-process so
206
330
  `modelRoles.review` lands in `config.yml` — nothing to remember. Options:
207
331
  `--archetype <id>`, `--weights m=w,...`, `--required`, `--thinking`, `--tools`,
208
332
  `--benchmarks m,...` (extra metrics to fold in; `--list-benchmarks` prints the
209
- weightable set, including any external benchmark in use), `--scope user|project`,
333
+ weightable set, including any external benchmark in use), `--feature <id,...>`
334
+ (comma-separated capability flags; `--list-features` prints the capability set),
335
+ `--scope user|project`,
210
336
  `--body`/`--body-file`, `--force` (overwrite an existing agent file), `--yes`
211
337
  (accept a proposed benchmark source without prompting), `--no-discover` (skip
212
338
  catalog discovery), `--dry-run`, `--json`. In
@@ -253,6 +379,32 @@ By hand, the same two artifacts:
253
379
  2. Delete the agent file: `rm ~/.omp/agent/agents/review.md`.
254
380
  3. Wire it: `/refresh-roles` in a session.
255
381
 
382
+ ## Project roles
383
+
384
+ `/project-roles` discovers a project's usecase from its own artifacts (README,
385
+ package.json, AGENTS.md, docs listing, git log, file tree) and applies a
386
+ **project-scoped** role set — the roles the project needs, with agents authored
387
+ for the new ones — to `<cwd>/.omp/config.yml`:
388
+
389
+ ```sh
390
+ /project-roles
391
+ /project-roles --purpose "a Python data pipeline repo" --dry-run
392
+ /project-roles --roles "data=build pipelines,-vision,default"
393
+ ```
394
+
395
+ It prints the plan (kept / dropped / added roles, each new role's archetype,
396
+ weights, focus benchmarks and top pick) before writing. `--purpose` overrides the
397
+ discovered usecase; `--roles` overrides the role set (`-name` drops a shipped
398
+ role, `name` keeps or adds one, `name=purpose` adds a new role); `--dry-run`
399
+ writes nothing; without `--force` an existing project role config is refused.
400
+
401
+ When a project carries its own `omp-llm-role` settings entry, the updater scopes
402
+ the whole run to `<cwd>/.omp` — the config, the state/history/lock files and the
403
+ day gate — so the project's `modelRoles` are refreshed independently of the
404
+ global `~/.omp/agent/config.yml`, which is never written in project mode. A
405
+ successful apply registers the project, so the explorer's **scope switcher**
406
+ lists it (see below).
407
+
256
408
  ## Explorer
257
409
 
258
410
  `/explore-roles` in omp boots a loopback-only web UI
@@ -260,6 +412,15 @@ By hand, the same two artifacts:
260
412
  `@slow`?" and "what happens if I care more about price than agents?" without
261
413
  editing source and re-running the CLI.
262
414
 
415
+ - **Scope switcher** — the header lists every role-config source: the
416
+ **user-level** scope, plus every project where `/project-roles` was used (the
417
+ plugin records each project root in a small global registry, and the current
418
+ session's project is listed immediately). The explorer opens on the session's
419
+ project when the cwd has a project role config, else user-level. Switching
420
+ re-reads that scope's roles and universe from disk and points **Export** at
421
+ that scope's lock file — a project scope resolves as the project lock merged
422
+ over the user-level lock, exactly what the updater ranks. A project whose lock
423
+ file is gone is shown disabled. Switching with unsaved edits asks first.
263
424
  - **Role tabs** — every role the plugin knows: the nine omp built-in roles, the
264
425
  shipped opt-in `designer`, and any role present only in the lock file. Each
265
426
  tab is tinted by provenance — `default` (omp built-in), `plugin` (shipped but
@@ -307,12 +468,30 @@ The plugin keeps only models the OpenRouter key can actually run:
307
468
  - **Tier/budget gate** — paid keys with budget get billed variants, free or
308
469
  exhausted keys get `:free` variants, `:batch` never. Every candidate must
309
470
  resolve in omp's catalog.
310
- - **Probe verification** — on top of the gate, every candidate is verified with
311
- a one-token completion probe, because the account's OpenRouter
312
- **allowed-providers privacy whitelist** is invisible to the key and catalog
313
- endpoints and a real request's 404 is the only reliable signal. Blocked
314
- candidates are excluded from selection and chains; a role with no clean
315
- candidate is left untouched.
471
+ - **Keyed-catalog fast path** — when the account's OpenRouter setting **"Filter the
472
+ model catalog for API keys"** is on, the key-authenticated `GET /api/v1/models` is a
473
+ per-key allowlist: allowed candidates skip the probe and key-blocked candidates are
474
+ pruned from the walk. With the setting off (or the fetch failing) the plugin falls
475
+ back to the probe walk below — no abort.
476
+ - **Probe verification** — the fallback: every candidate is verified with a one-token
477
+ completion probe, because the account's OpenRouter **allowed-providers privacy
478
+ whitelist** is invisible to the key endpoint and the unauthenticated catalog, and a
479
+ real request's 404 is the only reliable signal. Blocked candidates are excluded from
480
+ selection and chains; a role with no clean candidate is left untouched.
481
+ - **Allowed-providers whitelist** — the account's OpenRouter **allowed-providers
482
+ privacy whitelist** (openrouter.ai/settings/privacy) is learned once per run (a
483
+ canary request that bills no tokens) and every model's route pool is narrowed to
484
+ allowed providers before ranking, so a pin or a fallback hop is never written on a
485
+ provider the account cannot use. The run notes `OpenRouter allowed-providers
486
+ whitelist active — N providers; M routes pruned` (or `… whitelist: no whitelist`).
487
+ With no whitelist set — or when the harvest fails — nothing changes.
488
+ - **Explorer key marking** — the explorer overlays every ranked model with a
489
+ **usable** / **key-blocked** / **unknown** badge derived from the
490
+ key-authenticated `GET /api/v1/models` (the account setting **"Filter the model
491
+ catalog for API keys"** makes it a per-key allowlist). A **hide key-blocked**
492
+ toggle drops blocked rows, and when the setting is off every badge reads
493
+ *unknown* with a hint naming the OpenRouter setting. The mark is read-only: it
494
+ never edits roles, reorders ranks or changes an Export.
316
495
 
317
496
  ## Caveats
318
497
 
@@ -320,10 +499,21 @@ The plugin keeps only models the OpenRouter key can actually run:
320
499
  occupying its share of the denominator, so weight sparse metrics as
321
500
  differentiators, never as `required` gates. `website`, `writing` and
322
501
  `long_context` are capability-filled (0.195) instead, so absence is not a
323
- penalty for them.
502
+ penalty for them — with one residual inversion: the fill sits above the bottom
503
+ of the covered field, so a covered model in the bottom of the field can still
504
+ score below a model with no data (see
505
+ [docs/dev/quirks.md](docs/dev/quirks.md)).
324
506
  - **Provider whitelist.** The account's allowed-providers whitelist blocks many
325
507
  models, so the report's #1 and the written selector can differ — judge weights
326
- on the reachable pool.
508
+ on the reachable pool. The updater prunes *routes* to the whitelist (see
509
+ Availability), but the report has no token and is not whitelist-aware.
510
+ - **Provider pinning opts out of auto-Exacto.** A `providerPin` — or the
511
+ automatic pin the `providerPinning` capability writes — puts a trailing
512
+ `@<slug>` on the selector, which omp applies as OpenRouter
513
+ `provider: { only: [slug] }` routing. `only` is exclusive, so a pinned role's
514
+ requests bypass OpenRouter's automatic Exacto tool routing (which routes tool
515
+ calls by tool-call correctness) and run on the pinned route. Pin only where you
516
+ know the endpoint you want; leave tool-heavy roles on the default routing.
327
517
  - **Model concentration.** The cheapest pick concentrates on one vendor; the
328
518
  fallback chains mitigate it.
329
519
 
@@ -334,6 +524,6 @@ MIT — see [LICENSE](LICENSE).
334
524
  ## Documentation
335
525
 
336
526
  Maintainer docs (architecture, data sources, scoring, writes/state, explorer
337
- internals, releasing, agent authoring, the normative spec) live in
527
+ internals, releasing, agent authoring, testing) live in
338
528
  [docs/dev/README.md](docs/dev/README.md); release history is in
339
529
  [CHANGELOG.md](CHANGELOG.md).
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: designer
3
- description: UI/UX specialist for design implementation, review, visual refinement
3
+ description: MUST be used for UI/UX design implementation, review, and visual refinement.
4
4
  model: "@designer, @default"
5
- tools: [read, bash, edit, ast_grep, ast_edit, ask, debug, ida, eval, github, glob, grep, find, lsp, checkpoint, rewind, context_notes, new_context, security_scan, task, wait, todo, web_search, write, memory_edit, retain, recall, reflect, learn, manage_skill]
5
+ tools: [read, grep, glob, find, edit, write, bash, ast_grep, ast_edit, eval, lsp, web_search, ask, todo, task, wait, checkpoint, rewind]
6
6
  ---
7
7
 
8
8
  Implement/review UI designs; edit files, create components, run commands as needed.
@@ -30,6 +30,7 @@ Design system: foundation; UI without one becomes inconsistent. Four phases, in
30
30
  3. Implement states: loading, empty, error, disabled, hover, focus.
31
31
  4. Verify accessibility: contrast, focus rings, semantic HTML.
32
32
  5. Test responsive behavior.
33
+ 6. Render and inspect: start the project's dev server (or open the built page), open the route in the browser via `eval` (`browser.open`), `screenshot` it, and check the project's actual breakpoints, both light and dark themes, and focus states. The `browser` facade is an Eval prelude, not an agent tool, so it cannot appear in `tools:` — the instruction lives here in the body.
33
34
 
34
35
  ## Review
35
36
  1. Read reviewed files.
@@ -56,7 +57,7 @@ Design system: foundation; UI without one becomes inconsistent. Four phases, in
56
57
  - Same spacing everywhere: no rhythm; monotony
57
58
  - Center-aligning everything: left alignment with asymmetry feels more designed
58
59
  - Modals for everything: lazy, rarely best
59
- - Overused fonts: Inter, Roboto, Open Sans, system defaults
60
+ - Overused fonts when the project has no type system: Inter, Roboto, Open Sans, system defaults.
60
61
  - Pure black (`#000`) or white (`#fff`): ALWAYS tint neutrals
61
62
  - Gray text on colored backgrounds: use a background shade instead
62
63
  - Bounce/elastic easing: dated, tacky; use exponential easing (`ease-out-quart`/`expo`)
@@ -4,6 +4,18 @@ Maintainer docs for the omp-llm-role plugin. `README.md` is for people *using*
4
4
  the plugin; everything here is for people *changing* it. A fact has exactly one
5
5
  home — if it is here, it is not in the README.
6
6
 
7
+ ## Development setup
8
+
9
+ Clone, then install the dev dependency and wire the commit gate:
10
+
11
+ ```sh
12
+ npm install
13
+ git config core.hooksPath .githooks
14
+ ```
15
+
16
+ `.githooks/pre-commit` refuses staged `cache/` paths (daily UTC caches,
17
+ regenerated on every run) and runs the test suite (`node --test tests/`).
18
+
7
19
  ## Where to start
8
20
 
9
21
  | If you are changing… | Read |
@@ -14,9 +26,10 @@ home — if it is here, it is not in the README.
14
26
  | a config write, the state/history/lock files, or the write gate | [writes-and-state.md](writes-and-state.md) |
15
27
  | the explorer surface (server, explain layer, SPA) | [explorer.md](explorer.md) |
16
28
  | the dated numbers (matched/priced/eligible counts, per-role leaders) | [current-state.md](current-state.md) |
17
- | a release (version bump, npm/git/marketplace channels) | [releasing.md](releasing.md) |
29
+ | a release (version bump, SemVer, CHANGELOG rules, npm/git/marketplace channels) | [releasing.md](releasing.md) |
30
+ | the CI workflow, the release script, or the tag→publish contract | [ci.md](ci.md) |
18
31
  | the agent `.md` contract (frontmatter, routing, read-only rules) | [agent-authoring.md](agent-authoring.md) |
19
- | a normative decision (what the plugin MUST do) | [spec.md](spec.md) |
32
+ | the test suite or a verification scenario | [testing.md](testing.md) |
20
33
  | a debugging-only oddity (a quirk, not a contract) | [quirks.md](quirks.md) |
21
34
 
22
35
  ## The docs
@@ -41,14 +54,38 @@ home — if it is here, it is not in the README.
41
54
  - **[current-state.md](current-state.md)** — the dated snapshot:
42
55
  matched/priced/eligible counts, per-role leaders, and the open defects.
43
56
  Refresh it with each run.
44
- - **[releasing.md](releasing.md)** — the release switch (`package.json`
45
- `version`), the npm/git/marketplace channels, the tarball `files` whitelist,
46
- and the install-route verification.
57
+ - **[releasing.md](releasing.md)** — when and how to bump the version (the
58
+ `package.json` release switch, the SemVer surface), how to cut a release,
59
+ the CHANGELOG entry rules, the npm/git/marketplace channels, the tarball
60
+ `files` whitelist, and the install-route verification.
61
+ - **[ci.md](ci.md)** — the GitHub Actions pipeline: the `test`/`release` jobs
62
+ and their triggers, the tag→publish contract, the one-time trusted-publisher
63
+ registration, the `scripts/release.ts` subcommands and refusals, and how to
64
+ verify the pipeline.
47
65
  - **[agent-authoring.md](agent-authoring.md)** — the agent `.md` contract:
48
66
  frontmatter (`model: "@<role>, @default"`, `tools:`), the routing
49
67
  description, read-only classification, and where agents are discovered.
50
- - **[spec.md](spec.md)** — the normative spec: the confirmed decisions, the
51
- verified ground truth, and the contracts the implementation MUST satisfy.
68
+ - **[testing.md](testing.md)** — the test-suite seam map (which test file covers
69
+ which scenario) and the live checks that need a real omp session.
52
70
  - **[quirks.md](quirks.md)** — debugging-only oddities and gotchas that are not
53
71
  contracts (e.g. Node type-stripping not typechecking, stale in-process plugin
54
72
  code).
73
+
74
+ ## Research notes
75
+
76
+ External material that informs (but does not bind) the design. Not contracts.
77
+
78
+ - **[../research/agentic-engineering-benchmarks.md](../research/agentic-engineering-benchmarks.md)**
79
+ — IndyDevDan's "top 5 agentic-engineering benchmarks" video, mapped onto the
80
+ project's coverage/collinearity rules; records the two axes the project lacks
81
+ (guardrails, hallucination), the missing agentic categories, and the
82
+ per-task-vs-$/M cost gap. Source transcript alongside it
83
+ (`agentic-engineering-benchmarks.transcript.txt`).
84
+ - **[../research/openrouter-endpoint-routing.md](../research/openrouter-endpoint-routing.md)**
85
+ — Kai's "OpenRouter is quietly giving nerfed AI models" video, mapped onto the
86
+ project's OpenRouter data model; confirms the `1/price²` blend, argues
87
+ quantization labels are not a quality signal, and records the per-endpoint
88
+ capability ceilings (context, max output, tool support) the project now uses as
89
+ role filters (issue #18), the opt-in cache-read pricing (issue #30) and
90
+ provider pinning, and the remaining declared-rate-vs-measured-hit gap. Source
91
+ transcript alongside it (`openrouter-endpoint-routing.transcript.txt`).
@@ -128,10 +128,68 @@ effect on the next session.
128
128
  `src/benchmark-discovery.ts`) and folds them in. The resolved benchmark
129
129
  labels are given to the architect (and, on the template path, listed in the
130
130
  body's `<criteria>`), so the agent's rubric names the benchmark its model was
131
- chosen on.
131
+ chosen on. Discovery is **quality-gated**: the pre-fetch catalog gate is
132
+ count-only, and the handler then probe-fetches each selected candidate's source
133
+ and assesses the joined pool on nine axes (`assessFocusMetric`: coverage,
134
+ dispersion, composition, freshness, trust, modality, maintenance, provenance,
135
+ cross-source — see scoring.md). A
136
+ **discovered** candidate below-bar on any gating axis is dropped non-fatally
137
+ with a reason naming the axis; a **user-named** metric is never dropped — it is
138
+ annotated and warned. The
139
+ focus set is capped at `FOCUS_METRIC_CAP` (3) so one benchmark keeps a decisive
140
+ share. The report prints the nine signals and warns per below-bar axis;
141
+ `--dry-run` and `--json` carry the same signal. The agent file is written **before** the role,
142
+ so a partial failure leaves a harmless agent (its `@<name>, @default` chain
143
+ falls back to `@default`) rather than a dangling role; a failed role write
144
+ rolls the agent file back — restoring the prior file on a `--force` re-create,
145
+ else removing the new one — so a retry needs no `--force` and the user's prior
146
+ agent is never destroyed. A malformed architect response is retried once (a
147
+ parse failure only — a `run()` failure propagates immediately, since a second
148
+ session would fail the same way), and the final error carries the raw architect
149
+ output so the failure surfaces what the architect actually said. The step-3
150
+ benchmark prompt previews the request's
151
+ resolved weights (`resolveRole`), so the "already in this role's weights"
152
+ marker appears live.
153
+ - **Capability flags on `/create-agent`.** `--feature <id,...>` (comma-separated)
154
+ sets the new role's `features.<id>` so it starts from the plugin's recommended
155
+ settings for that capability; `--list-features` prints each capability id, the
156
+ settings it applies and what it buys, without creating anything. An unknown id
157
+ fails with a non-zero exit and no write. The archetype table
158
+ (`src/role-archetypes.ts`) carries an optional `features` list the architect
159
+ may *propose*, but **no archetype auto-applies a flag** — a role gets a flag
160
+ only when the user asks for it. `create-role.ts` exposes the same
161
+ `--feature`/`--list-features` surfaces, and `/project-roles` takes `--feature`
162
+ (its help points at `/create-agent --list-features`).
163
+ - **`/project-roles`** (in-session) discovers the project's usecase from its own
164
+ artifacts through the profile architect, then authors an agent per new role
165
+ **into the project scope** (`<cwd>/.omp/agents/`) and writes the project's
166
+ `modelRoles` to `<cwd>/.omp/config.yml`. The agents are ordinary `.md` files
167
+ under the project dir, discovered by omp's nearest-project lookup (project >
168
+ user > plugin) and by `discoverAgentPins`.
132
169
  - **The shipped skill** — the hand-driven workflow, for bodies that need real
133
170
  authoring rather than the archetype scaffold.
134
171
 
172
+ ### The vendored architect prompt
173
+
174
+ `src/prompts/agent-creation-architect.md` is copied verbatim from omp 18.4.8's
175
+ `/agents` hub prompt (the SDK subpath it lives under does not resolve from the
176
+ extension loader, so the plugin ships its own copy). Its first line carries the
177
+ source version, and `ARCHITECT_PROMPT_VERSION` (`src/architect-provenance.ts`,
178
+ re-exported by `src/agent-architect.ts`) holds the same value. `tests/architect-provenance.test.ts`
179
+ asserts the two agree and that this document names the same version, so an omp
180
+ upgrade that changes the prompt surfaces as a test failure rather than silent
181
+ drift.
182
+
183
+ **Re-sync procedure** (after an omp upgrade):
184
+
185
+ 1. Read the installed omp's agent-creation architect prompt (the `/agents` hub's
186
+ system prompt) and diff it against `src/prompts/agent-creation-architect.md`.
187
+ 2. If it changed, replace the body of the asset, keeping the header comment's
188
+ version current.
189
+ 3. Bump `ARCHITECT_PROMPT_VERSION` to the new omp version.
190
+ 4. Update the version named in this section.
191
+ 5. Run `node --test tests/architect-provenance.test.ts`.
192
+
135
193
  ## Removal
136
194
 
137
195
  The `/remove-agent` command deletes an agent **and** its role definition: