omp-llm-role 1.0.0 → 1.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.
Files changed (49) hide show
  1. package/README.md +179 -11
  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 +84 -27
  6. package/docs/dev/ci.md +152 -0
  7. package/docs/dev/current-state.md +327 -24
  8. package/docs/dev/data-sources.md +202 -38
  9. package/docs/dev/explorer.md +157 -46
  10. package/docs/dev/quirks.md +45 -4
  11. package/docs/dev/releasing.md +129 -17
  12. package/docs/dev/scoring.md +789 -28
  13. package/docs/dev/testing.md +206 -0
  14. package/docs/dev/writes-and-state.md +176 -28
  15. package/docs/llm-role-rankings.md +141 -140
  16. package/package.json +754 -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 +104 -6
  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 +409 -46
  29. package/src/error-report.ts +562 -0
  30. package/src/explorer/boot.ts +36 -24
  31. package/src/explorer/explain.ts +110 -13
  32. package/src/explorer/scopes.ts +110 -0
  33. package/src/explorer/server.ts +112 -32
  34. package/src/extension.ts +407 -34
  35. package/src/features.ts +103 -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/role-archetypes.ts +12 -2
  42. package/src/role-settings.ts +22 -0
  43. package/src/settings.ts +205 -24
  44. package/src/state.ts +1 -1
  45. package/src/updater.ts +150 -31
  46. package/web/app.js +411 -12
  47. package/web/index.html +1 -0
  48. package/web/style.css +44 -1
  49. package/docs/dev/spec.md +0 -633
@@ -4,7 +4,7 @@ Maintainer map of the `omp-llm-role` plugin: how the modules fit together, the
4
4
  runtime contract they must satisfy, and the invariants a change must not break.
5
5
 
6
6
  - User-facing install/usage: [`../../README.md`](../../README.md).
7
- - Normative behavior contract: [`spec.md`](spec.md).
7
+ - How it is verified: [`testing.md`](testing.md).
8
8
  - Where the numbers come from: [`data-sources.md`](data-sources.md).
9
9
 
10
10
  ## Module map
@@ -25,32 +25,34 @@ the extension's `/refresh-roles`, `/explore-roles` and `/create-agent`.
25
25
 
26
26
  | File | Role / key exports |
27
27
  |---|---|
28
- | `src/engine.ts` | Ranking engine shared by CLI and plugin. Fetch/cache chain per source, cardinal transforms, value scoring. Exports `loadRankData`, `computeRankings`, `buildModels`, `rankRole`, `paretoFrontier`, `cardinalMetric`, `roleLambda`, `thinkingPriceFactor`, `CAPABILITY_FILL`, `SUFFIX_LEVELS`, `META_LEVELS`, `THINKING_TOKEN_OVERHEAD`, `parseFindData`, `parseModelPage`, `buildOpenRouterEnrichment`, `applyOpenRouterData`, `applyDesignPercentiles`, `applyWritingScores`, `extractFlight`, `extractJsonArray`, the cache readers/writers, and the `Model`/`RoleDef`/`Ranked`/`RankData` types. Re-exports `normalizeDesignId` and `parseWritingEvidence` from the benchmark-source registry. |
29
- | `src/benchmark-sources.ts` | The benchmark-source registry: the single source of truth for which sources exist, what metric each feeds, and how to fetch/parse/join each. Exports `BENCHMARK_SOURCES` (the shipped static sources), `resolveBenchmarkSource`, `declaredSourceForLink`, `sourceForMetric`, `parseBenchmarkPayload`, `loadBenchmarkScores`, `applyBenchmarkScores`, `joinBenchmarkScores`, `normalizeMetricKey`, `externalMetricKey`, `normalizeDesignId`, `parseWritingEvidence`, `parseLlmStatsBenchmark`, `parseBenchmarkCatalog`, `loadBenchmarkCatalog`, `catalogMetric`, `SHIPPED_CATALOG_METRICS`, the declarative-source surface (`SourceDeclaration`, `validateDeclaration`, `parseSourceDeclaration`, `loadDeclaredSources`, `saveDeclaredSource`, `declaredSourcesPath`, `declarationToSource`, `executeDeclaration`, `llmStatsBenchmarkDeclaration`, `genericBenchmarkSource`, `dryRunDeclaration`), the fetch helpers (`fetchJson`, `fetchText`), and the `BenchmarkSource`/`JoinRule`/`BenchmarkScores`/`DeclarationDryRun` types. Imports `engine.ts` for types only, so there is no runtime cycle. |
30
- | `src/settings.ts` | Shipped defaults and the settings merge/validate path. Exports `DEFAULT_ROLES`, `DEFAULT_SETTINGS`, `SHIPPED_AGENTS`, `KNOWN_METRICS`, `isKnownMetric` (a shipped key or an external `<ns>:<local>`), `PLUGIN_SETTINGS_PATH`, `ACTIVATE_DEFAULT_KEY`, `deriveSettingsSchema`, `readPluginSettingsMap`, `resolveSettings`, `roleUniverse`, `deepMergeInto`, and the `ResolvedSettings`/`PluginSettingSchema`/`UniverseEntry`/`RoleKind` types. |
28
+ | `src/engine.ts` | Ranking engine shared by CLI and plugin. Fetch/cache chain per source, cardinal transforms, value scoring. Exports `loadRankData`, `computeRankings`, `buildModels`, `rankRole`, `paretoFrontier`, `cardinalMetric`, `roleLambda`, `thinkingPriceFactor`, `CAPABILITY_FILL`, `SUFFIX_LEVELS`, `META_LEVELS`, `THINKING_TOKEN_OVERHEAD`, `parseFindData`, `parseModelPage`, `narrowEndpointRecord`, `buildOpenRouterEnrichment`, `applyOpenRouterData`, `hasEndpointFilters`, `routePassesEndpointFilters`, `modelPassesEndpointFilters`, `endpointFilterDrops`, `applyDesignPercentiles`, `applyWritingScores`, `extractFlight`, `extractJsonArray`, the cache readers/writers, and the `Model`/`RoleDef`/`Ranked`/`RankData`/`OpenRouterEndpointRecord` types. Re-exports `normalizeDesignId` and `parseWritingEvidence` from the benchmark-source registry. |
29
+ | `src/benchmark-sources.ts` | The benchmark-source registry: the single source of truth for which sources exist, what metric each feeds, and how to fetch/parse/join each. Exports `BENCHMARK_SOURCES` (the shipped static sources), `resolveBenchmarkSource`, `declaredSourceForLink`, `sourceForMetric`, `parseBenchmarkPayload`, `parseBenchmarkPayloadMeta`, `loadBenchmarkScores`, `applyBenchmarkScores`, `joinBenchmarkScores`, `normalizeMetricKey`, `externalMetricKey`, `normalizeDesignId`, `parseWritingEvidence`, `parseLlmStatsBenchmark`, `parseBenchmarkCatalog`, `loadBenchmarkCatalog`, `cachedSourceInfo`, `catalogMetric`, `SHIPPED_CATALOG_METRICS`, the declarative-source surface (`SourceDeclaration`, `validateDeclaration`, `parseSourceDeclaration`, `loadDeclaredSources`, `saveDeclaredSource`, `declaredSourcesPath`, `declarationToSource`, `executeDeclaration`, `llmStatsBenchmarkDeclaration`, `genericBenchmarkSource`, `dryRunDeclaration`), the fetch helpers (`fetchJson`, `fetchText`), and the `BenchmarkSource`/`JoinRule`/`BenchmarkScores`/`BenchmarkPayloadMeta`/`FocusSourceInfo`/`BenchmarkCatalogEntry`/`DeclarationDryRun` types. Imports `engine.ts` for types only, so there is no runtime cycle. |
30
+ | `src/settings.ts` | Shipped defaults and the settings merge/validate path. Exports `DEFAULT_ROLES`, `DEFAULT_SETTINGS`, `SHIPPED_AGENTS`, `KNOWN_METRICS`, `isKnownMetric` (a shipped key or an external `<ns>:<local>`), `PLUGIN_SETTINGS_PATH`, `ACTIVATE_DEFAULT_KEY`, `deriveSettingsSchema`, `readPluginSettingsMap`, `resolveSettings`, `roleUniverse`, `deepMergeInto`, `findProjectAnchor`, `projectLockPath`, and the `ResolvedSettings`/`PluginSettingSchema`/`UniverseEntry`/`RoleKind` types. `readPluginSettingsMap` merges the project lock file (`projectLockPath(cwd)` = `<cwd>/.omp/plugins/omp-plugins.lock.json`, omp's project dir with **no walk-up**) over the user-level one; `findProjectAnchor` (the walk-up `.omp`/`.git` anchor) is kept only for the `/project-roles` divergence warning. |
31
31
  | `src/agent-pins.ts` | Agent → pinned-role derivation. Exports `parseAgentPin` (first `@<role>` in the `model:` frontmatter) and `discoverAgentPins` (scans the shipped, user and project agent dirs; project > user > plugin). Drives the `task.disabledAgents` sync. |
32
- | `src/availability.ts` | Key tier gate, catalog filter, variant resolution, provider-allowlist probe. Exports `fetchKeyMeta`, `tierGate`, `filterCatalog`, `rankingIdOf`, `currentRankingId`, `resolveVariant`, `enrichThinkingLevels`, `probeModel`, `catalogFromOmpModelsJson`, `THINKING_LEVELS`, and the `CatalogEntry`/`KeyMeta`/`Tier`/`ProbeVerdict` types. |
32
+ | `src/availability.ts` | Key tier gate, catalog filter, variant resolution, provider-allowlist probe, and the keyed-catalog availability primitive. The keyed source is the Bearer-authenticated `GET /api/v1/models` (the account setting "Filter the model catalog for API keys" makes it a per-key allowlist; set membership is the only signal), shared by the explorer and the updater's companion fast path. Exports `fetchKeyMeta`, `tierGate`, `filterCatalog`, `rankingIdOf`, `currentRankingId`, `resolveVariant`, `enrichThinkingLevels`, `probeModel`, `computeKeyAvailability`, `fetchKeyAvailability`, `catalogFromOmpModelsJson`, `THINKING_LEVELS`, and the `CatalogEntry`/`KeyMeta`/`Tier`/`ProbeVerdict`/`KeyAvailability` types. |
33
33
  | `src/config-edit.ts` | Surgical line-oriented YAML patch for `modelRoles` + `retry.fallbackChains` + `task.disabledAgents`, plus the atomic writer. Exports `parseConfig`, `patchConfig`, `writeConfigAtomic`, `ConfigEditError`, and the `ConfigPatch` type. No runtime YAML dependency. |
34
34
  | `src/state.ts` | State/history/lock files under the agent dir, and agent-dir resolution. Exports `agentDir`, `loadState`, `saveState`, `appendHistory`, `acquireLock`, `releaseLock`, `freshState`, and the `PluginState` type (which carries `managedDisabledAgents` — the `task.disabledAgents` names the plugin added). |
35
- | `src/updater.ts` | Orchestration: rank → tier gate → probe → hysteresis → chains → agent-disable sync → config write. Exports `runUpdater`, the `Deps`/`Decision`/`RunResult`/`Trigger`/`DecisionReason` types. `agentDisablePatch` also removes a previously-managed `task.disabledAgents` name whose agent file is gone. |
36
- | `src/extension.ts` | omp extension entry (default export). Registers `session_start` (awaited day-gated run), `/refresh-roles`, `/explore-roles` (in-process explorer), `/create-agent` (architect + benchmark-link resolution + catalog discovery), `/remove-agent`, and `session_shutdown`. Owns `extDeps` (the extension's `Deps`) and the live-session model hook. |
35
+ | `src/error-report.ts` | Opt-in reporting of **unexpected** plugin errors as GitHub issues on `Gott50/omp-llm-role` (the `errorReporting` setting). The plugin's only GitHub client and the only home for a fingerprint, a redaction rule, the agent-dir ledger (`ERROR_REPORT_LEDGER_FILE`) and the issue body. **Entry point:** `reportUnexpectedError(input, deps)` — the feature's one test seam (per-call input + fully injected `deps`; tests supply a fake `fetch`, clock, env, dir and token resolver). Also exports `defaultErrorReportDeps()` (production deps: resolved settings, global `fetch`, real clock, `agentDir`, the shipped manifest, `gh auth token`, the OS opener) and `resetErrorReportGuard()` (the in-process dedupe guard's test hook). Never throws, never rejects; a no-op when `errorReporting` is `off`. |
36
+ | `src/updater.ts` | Orchestration: rank → tier gate → probe → hysteresis → chains → agent-disable sync → config write. Exports `runUpdater`, the `Deps`/`Decision`/`RunResult`/`Trigger`/`DecisionReason` types. Resolves the project scope (`<cwd>/.omp` when it carries an `omp-llm-role` settings entry) and scopes the config/state/history/lock to it. `agentDisablePatch` also removes a previously-managed `task.disabledAgents` name whose agent file is gone. `RunResult.defect` is set on the exception-abort paths only (a `ConfigEditError`/self-check mismatch and the catch-all throw); the enumerated environment aborts leave it unset. |
37
+ | `src/extension.ts` | omp extension entry (default export). Registers `session_start` (awaited day-gated run), `/refresh-roles`, `/explore-roles` (in-process explorer), `/create-agent` (architect + benchmark-link resolution + catalog discovery), `/remove-agent`, `/project-roles` (profile architect + `setupProject` + in-process updater), and `session_shutdown`. Owns `extDeps` (the extension's `Deps`) and the live-session model hook. Every unexpected command-handler throw and every `runUpdater` defect is routed through `src/error-report.ts`: `reportCommandError` for the six command-handler catch sites and `reportDefect` for a `runUpdater` result whose `defect` is set — each keeps the pre-existing one-line warning and adds the reporting module's own lines. |
37
38
  | `src/guards.ts` | The package's one type guard: `isRecord`. |
38
39
  | `src/role-settings.ts` | The one validated role write path (validate → merge → backup → atomic write) and its removal half. Exports `validateRole`, `mergeExport`, `writeRoleSettings`, `mergeRemove`, `removeRoleSettings`, `timestamp`. Shared by the explorer's Export, `src/cli/create-role.ts`, `/create-agent` and `/remove-agent`. |
39
40
  | `src/role-archetypes.ts` | Purpose → weight archetype table (10 sets) + keyword fitting. Exports `ARCHETYPES`, `FALLBACK_ARCHETYPE`, `fitArchetype`, `archetypeById`, and the `Archetype`/`ArchetypeMatch` types. |
40
41
  | `src/agent-file.ts` | Agent `.md` rendering/placement. Exports `renderAgentFile`, `writeAgentFile`, `removeAgentFile`, `isReadOnlyTools`, `userAgentsDir`, `projectAgentsDir`, `READ_ONLY_TOOLS`, `RESERVED_AGENT_NAMES`, `AGENT_NAME_RE`, and the `AgentFileSpec`/`AgentWriteResult`/`AgentRemoveResult` types. |
41
- | `src/agent-architect.ts` | omp's agent-creation architect, run in-process (extension-only). Exports `generateAgentSpec`, `parseAgentSpec`, `extractJsonObject`, `extractAssistantText`, and the `ArchitectOptions` type. Imports `@oh-my-pi/pi-coding-agent` at the package root. |
42
- | `src/benchmark-author.ts` | Benchmark-source authoring for an unknown link: fetch the link, run an in-process architect (`src/prompts/benchmark-source-architect.md`) to propose a declarative source spec, and validate it. Extension-only (imports `@oh-my-pi/pi-coding-agent`). Exports `authorBenchmarkSource`, `parseSourceDeclarationJson`, and the `BenchmarkAuthorOptions` type. |
42
+ | `src/agent-architect.ts` | omp's in-process architects: the agent-creation architect (`/create-agent`, `/agents` hub) and the project-profile architect (`/project-roles`). Exports `generateAgentSpec`, `generateProjectProfile`, `parseAgentSpec`, `parseProjectProfile`, `extractJsonObject`, `extractAssistantText`, and the `ArchitectOptions`/`ProjectArchitectOptions` types. Imports `@oh-my-pi/pi-coding-agent` **dynamically** (inside the runner), so the module is importable under plain Node. |
43
+ | `src/benchmark-author.ts` | Benchmark-source authoring for an unknown link: fetch the link, run an in-process architect (`src/prompts/benchmark-source-architect.md`) to propose a declarative source spec, and validate it. Imports `@oh-my-pi/pi-coding-agent` **dynamically** (inside the function). Exports `authorBenchmarkSource`, `parseSourceDeclarationJson`, and the `BenchmarkAuthorOptions` type. |
43
44
  | `src/benchmark-discovery.ts` | Judge-backed benchmark relevance for `/create-agent` discovery: one `noul` (yes/no probability) question per catalog candidate, batched into a single judgment through omp's configured `judge` role (TypeSafe jev or its fallback chain). Extension-only (imports `@oh-my-pi/pi-coding-agent` and its `./judgment` subpath — the deeper `./judgment/standalone` path does not resolve under omp's SDK injection). The SDK is imported dynamically so a resolution failure degrades to "no discovery" instead of failing the whole extension load. Exports `judgeBenchmarkRelevance`. |
44
- | `src/agent-create.ts` | `/create-agent` core: purpose → archetype → validated role → agent `.md`, plus the shared input parser and report formatter both hosts use. Exports `createAgent`, `parseCreateAgentInput` (flag form or free text), `parseCreateAgentArgs`, `tokenizeArgs`, `extractBenchmarks`, `extractBenchmarkLinks`, `applyFocusBenchmarks`, `discoverBenchmarks`, `formatBenchmarks`, `formatArchetypes`, `formatCreateAgentReport`, `CREATE_AGENT_USAGE`, and the request/result types. |
45
+ | `src/agent-create.ts` | `/create-agent` core: purpose → archetype → validated role → agent `.md`, plus the shared input parser and report formatter both hosts use. Exports `createAgent`, `resolveRole`, `parseCreateAgentInput` (flag form or free text), `parseCreateAgentArgs`, `tokenizeArgs`, `extractBenchmarks`, `extractBenchmarkLinks`, `applyFocusBenchmarks`, `assessFocusMetric`, `belowBarReason`, `focusCoverageOk`, `countMetricCoverage`, `discoverBenchmarks`, `checkWeightMath`, `formatBenchmarks`, `formatArchetypes`, `formatCreateAgentReport`, `CREATE_AGENT_USAGE`, and the request/result types (incl. `FocusMetricAssessment`/`FocusCoverageEntry`/`FocusAssessor`). |
45
46
  | `src/agent-remove.ts` | `/remove-agent` core: delete an agent `.md` and its role, plus the shared flag parser and report formatter. Exports `removeAgent`, `parseRemoveAgentArgs`, `formatRemoveAgentReport`, `REMOVE_AGENT_USAGE`, and the request/result types. Refuses shipped default roles. |
47
+ | `src/project-setup.ts` | `/project-roles` core: a discovered `ProjectProfile` → a project-scoped role set (kept shipped roles, dropped ones, new fitted roles) written to the project plugin settings lock file, plus the project agents for the new roles. All-or-nothing (every check — names, metrics, weight math, the `--force` gate, agent-file collisions — before any write), `--dry-run`. Exports `setupProject`, `parseProjectRolesArgs`, `parseRolesSpec`, `applyProfileOverrides`, `formatProjectRolesReport`, `PROJECT_ROLES_USAGE`, and the `ProjectProfile`/`ProposedRole`/`SetupProjectOpts`/`SetupProjectResult` types (the result carries an `archetypes` map). The plan report prints the capability flags each new role will author (`features: <id>=<on>`). Dual-runtime safe (no SDK import); the extension command does the LLM work and calls it. |
46
48
 
47
49
  ### `src/explorer/`
48
50
 
49
51
  | File | Role / key exports |
50
52
  |---|---|
51
53
  | `src/explorer/boot.ts` | Shared explorer launcher: bind/port fallback, lock-file roles, browser open, close. Exports `startExplorer`, `EXPLORER_DEFAULT_PORT`, and the `ExplorerHandle`/`ExplorerBootOpts` types. Used by `/explore-roles`. |
52
- | `src/explorer/server.ts` | Zero-dependency HTTP surface (static SPA + JSON API). Exports `createExplorerServer` and the `ExplorerOpts` type. Endpoints: `GET /api/bootstrap`, `POST /api/rank`, `POST /api/explain`, `POST /api/export`, `POST /api/refresh`. |
53
- | `src/explorer/explain.ts` | Pure explanation layer: rank rows with baseline deltas, per-model decomposition, inverse-cardinal targets. Exports `rankRows`, `explainModel`, `inverseCardinal`, `METRIC_META`, `metricMeta`, `metricMetaFor`, `weightableMetrics`, and the `RankRow`/`Explanation`/`Contribution`/`Closing`/`MetricMeta` types. |
54
+ | `src/explorer/server.ts` | Zero-dependency HTTP surface (static SPA + JSON API). Exports `createExplorerServer` and the `ExplorerOpts` type. Endpoints: `GET /api/bootstrap` (carries `focusAssessments`), `POST /api/rank`, `POST /api/explain`, `POST /api/export`, `POST /api/refresh`. |
55
+ | `src/explorer/explain.ts` | Pure explanation layer: rank rows with baseline deltas, per-model decomposition, inverse-cardinal targets, and the nine-axis focus assessment. Exports `rankRows`, `explainModel`, `inverseCardinal`, `focusMetricsOf`, `focusAssessments`, `METRIC_META`, `metricMeta`, `metricMetaFor`, `weightableMetrics`, and the `RankRow`/`Explanation`/`Contribution`/`Closing`/`MetricMeta` types. |
54
56
 
55
57
  ### Assets and non-code
56
58
 
@@ -58,6 +60,7 @@ the extension's `/refresh-roles`, `/explore-roles` and `/create-agent`.
58
60
  |---|---|
59
61
  | `src/prompts/agent-creation-architect.md`, `src/prompts/agent-creation-user.md` | omp's architect prompts, shipped verbatim; read by `src/agent-architect.ts` via `new URL("./prompts/…", import.meta.url)`. |
60
62
  | `src/prompts/benchmark-source-architect.md`, `src/prompts/benchmark-source-user.md` | The benchmark-source architect prompts; read by `src/benchmark-author.ts`. |
63
+ | `src/prompts/project-profile-architect.md`, `src/prompts/project-profile-user.md` | The `/project-roles` profile-discovery architect prompts; read by `src/agent-architect.ts`. |
61
64
  | `web/index.html`, `web/app.js`, `web/style.css` | Explorer SPA — no framework, no build step, no external requests. Served by `src/explorer/server.ts`. |
62
65
  | `agents/designer.md` | The shipped `designer` subagent, discovered from the plugin's extension root (`<ext>/agents/*.md`). Opt-in: kept in `task.disabledAgents` until `roles.designer.enabled=true`. |
63
66
  | `skills/omp-llm-role-create-agent/SKILL.md` | Shipped skill, discovered from the plugin's `skills/` root. Hand-driven equivalent of `/create-agent`. |
@@ -70,20 +73,35 @@ the extension's `/refresh-roles`, `/explore-roles` and `/create-agent`.
70
73
  | `skills-lock.json` | Lock for the local `.agents/skills/` collection. |
71
74
  | `.agents/skills/` | Local dev-skill collection (Matt Pocock skills); not part of the plugin's shipped surface. |
72
75
  | `docs/agents/domain.md`, `docs/agents/issue-tracker.md`, `docs/agents/triage-labels.md` | Repo process docs (domain glossary, issue tracker, triage labels). |
73
- | `docs/dev/spec.md` | Normative spec (moved from the repo root). |
76
+ | `docs/dev/testing.md` | The test-suite seam map and the live checks. |
74
77
  | `docs/dev/architecture.md`, `docs/dev/data-sources.md` | This file and the data-source map. |
75
- | `tests/` | `node --test tests/` fixtures (tier gate, variant resolution, config edit, hysteresis, chain pruning, chain suffixes, explorer, role creation, writing metric, benchmark sources, thinking-price, openrouter-blend, agent disable, agent pins, settings schema, session model, role lock, role enable, probe gate, create-agent, create-role, **remove-agent**). |
78
+ | `tests/` | `node --test tests/` fixtures — the seam map is in [`testing.md`](testing.md). |
76
79
  | `docs/llm-role-rankings.md` | Generated report (regenerate with `--out`); the README's worked example. |
77
80
  | `cache/*.json` | Daily UTC caches (gitignored; see [`data-sources.md`](data-sources.md)). |
78
81
 
82
+ ## Variant resolution (ranking id → selector)
83
+
84
+ A ranking row (an llm-stats bare id) resolves to an `openrouter/*` catalog id by
85
+ matching the id after the last `/` (org prefix — including `~`-prefixed —
86
+ ignored), or after stripping a trailing `-latest`. Among the matches,
87
+ `resolveVariant` emits in order: **exact id** (bare or dated, whatever equals the
88
+ ranking id) → **newest dated id** (max trailing `-MMDD`/date suffix) → **bare
89
+ id** → `~org/…-latest` alias (last resort — a valid selector beats no update).
90
+ Never `:batch`; `:free` only on the free-tier branch of the tier gate. Ties
91
+ break lexicographically. The emitted selector is always `openrouter/<catalogId>`.
92
+
79
93
  ## Data flow
80
94
 
81
95
  One pipeline, driven by `runUpdater(trigger, deps, opts)` in `src/updater.ts`.
82
96
  The omp extension injects the production `Deps`; tests inject fakes.
83
97
 
84
98
  1. **Trigger.** `session_start` (awaited, day-gated) or `/refresh-roles`
85
- (forced). `runUpdater` loads `loadState()` and the raw
86
- settings (`deps.getSettings?.() ?? readPluginSettingsMap()`), then
99
+ (forced). `runUpdater` resolves the **project scope** first: when
100
+ `<cwd>/.omp/plugins/omp-plugins.lock.json` carries an `omp-llm-role` settings
101
+ entry, the config, the settings read, and the state/history/lock are scoped to
102
+ `<cwd>/.omp` (the global `~/.omp/agent/config.yml` is never written in project
103
+ mode); otherwise the global agent dir is used. It then loads `loadState()` and
104
+ the raw settings (`deps.getSettings?.() ?? readPluginSettingsMap()`), then
87
105
  `resolveSettings(raw)`; any validation error aborts with no write.
88
106
  2. **Agent-disable sync.** `agentDisablePatch` computes the
89
107
  `task.disabledAgents` adds/removes from `roleUniverse` + `discoverAgentPins`,
@@ -101,7 +119,10 @@ The omp extension injects the production `Deps`; tests inject fakes.
101
119
  `enrichThinkingLevels(rank.models, catalog)`. Per role, candidates are
102
120
  `resolveVariant`-resolved and then verified by a bounded `probeModel` walk
103
121
  (current selector first, then rank order; budget `PROBE_BUDGET = 12`; verdicts
104
- cached per run). Blocked candidates are excluded and recorded on the decision.
122
+ cached per run). Only the narrow no-allowed-providers 404 disqualifies; every
123
+ other failure (5xx, timeout, unknown model) counts as usable and stays in
124
+ omp's runtime-fallback domain. Blocked candidates are excluded and recorded on
125
+ the decision.
105
126
  6. **Hysteresis.** `no-current` → adopt; ineligible current → `adopted`; best
106
127
  beats current by `switchMargin` → `switched`; inside the margin but
107
128
  `cheaperInsideMargin` → `switched-cost`; else `kept-margin` / `kept-eligible`.
@@ -154,10 +175,16 @@ Rules every module must satisfy:
154
175
  `undefined` at runtime, not a compile error. Run the script after edits and
155
176
  sanity-check stderr match/eligible counts.
156
177
 
157
- The one deliberate exception is `src/agent-architect.ts`: it imports
158
- `@oh-my-pi/pi-coding-agent` (the **package root** only — subpath imports do not
159
- resolve in the compiled binary) and is therefore **extension-only**. The tests
160
- import `src/agent-create.ts`, which never imports the architect.
178
+ The one deliberate exception is the SDK import in `src/agent-architect.ts` and
179
+ `src/benchmark-author.ts`: they import `@oh-my-pi/pi-coding-agent` (the
180
+ **package root** only — subpath imports do not resolve in the compiled binary)
181
+ **dynamically, inside the runner function**. A static import would be fatal to
182
+ the whole `extension.ts` load (it would kill every plugin command, not just the
183
+ architect); dynamic, the modules — and `src/extension.ts` — stay importable
184
+ under plain Node (the command registration is testable) and a resolution failure
185
+ degrades to the caller's error path. The architect is still **extension-only in
186
+ practice**: it is only called from the extension. The tests import
187
+ `src/agent-create.ts`, which never imports the architect.
161
188
 
162
189
  ## Injected `Deps`
163
190
 
@@ -193,7 +220,8 @@ before `catalogFromOmpModelsJson`.
193
220
  (`runUpdater("manual", …, { force: true })`), `/explore-roles` (in-process
194
221
  `startExplorer`), `/create-agent` (architect → `createAgent` → in-process
195
222
  `runUpdater`), `/remove-agent` (`removeAgent` → in-process `runUpdater`),
196
- `session_shutdown` (closes the explorer handle).
223
+ `/project-roles` (profile architect → `setupProject` → in-process
224
+ `runUpdater`), `session_shutdown` (closes the explorer handle).
197
225
  - **CLI**: `src/cli/llm-role-rank.ts` (report), `src/cli/create-role.ts`
198
226
  (authoring). The updater and explorer have no CLI shim — they run through
199
227
  `/refresh-roles` and `/explore-roles`.
@@ -202,12 +230,18 @@ before `catalogFromOmpModelsJson`.
202
230
 
203
231
  `src/engine.ts` is the shared engine; `src/cli/llm-role-rank.ts` is a thin CLI over it.
204
232
 
205
- - `loadRankData(opts?: { refresh?; url?; roles? }): Promise<RankData>` — the
206
- per-source cache → fetch → stale-cache chain. `opts.roles` names the roles the
207
- caller will rank: a role-exclusive source is fetched only when some role
233
+ - `loadRankData(opts?: { refresh?; url?; roles?; extraMetrics? }): Promise<RankData>`
234
+ — the per-source cache → fetch → stale-cache chain. `opts.roles` names the roles
235
+ the caller will rank: a role-exclusive source is fetched only when some role
208
236
  weights its metric — Design Arena (`website`), the writing leaderboard
209
237
  (`writing`), and every external metric (a declared source or the generic
210
- llm-stats benchmark). `undefined` = fetch all (what the explorer passes).
238
+ llm-stats benchmark). `opts.extraMetrics` adds metrics no role weights and also
239
+ forces the `website`/`writing` fetches. `undefined` roles = fetch every declared
240
+ source. The explorer passes the resolved roles **plus** the shipped keys
241
+ (`Object.keys(KNOWN_METRICS)`), so its dataset is a superset of any def the UI
242
+ can rank — without it a role weighting an undeclared generic benchmark
243
+ (`bench:<id>`) would rank on a dataset missing that metric and disagree with the
244
+ updater.
211
245
  - `src/benchmark-sources.ts` is the registry: `resolveBenchmarkSource(link)` maps
212
246
  a link to a source, `sourceForMetric(metric)` maps a metric back, and
213
247
  `loadBenchmarkScores`/`applyBenchmarkScores` are the shared cache chain and
@@ -257,7 +291,8 @@ stderr `openrouter: matched N/<pool> models (throughput), M priced`, the
257
291
  - **One write path per artifact.** Roles go through `writeRoleSettings` (and
258
292
  `removeRoleSettings` for deletion); the config through `patchConfig` +
259
293
  `writeConfigAtomic`; agent files through `writeAgentFile` (and
260
- `removeAgentFile` for deletion). Nothing else writes those files.
294
+ `removeAgentFile` for deletion); the error-report ledger through `writeLedger`
295
+ in `src/error-report.ts` (temp + rename). Nothing else writes those files.
261
296
  - **Surgical config edits.** Only managed role lines, managed chain keys and the
262
297
  plugin-managed `task.disabledAgents` names change; comments, blank lines and
263
298
  unknown keys stay byte-identical. A no-change run writes nothing.
@@ -276,3 +311,25 @@ stderr `openrouter: matched N/<pool> models (throughput), M priced`, the
276
311
  field already sets and prices the effort.
277
312
  - **The engine is the only ranking math.** The explorer never reimplements
278
313
  `value = q − λ·$/M`; it calls `rankRole`/`explainModel`.
314
+
315
+ ## Non-goals
316
+
317
+ - **First-party provider selectors.** The plugin emits `openrouter/*` selectors
318
+ only — the ranking's price/throughput are OpenRouter-derived and the key is an
319
+ OpenRouter key.
320
+ - **Non-OpenRouter scoring sources.**
321
+ - **A `/rollback` command.** `llm-role-state.json` snapshots the previous
322
+ `modelRoles` block as a manual rollback aid; there is no command.
323
+ - **Auto-tuning `switchMargin`.**
324
+ - **A generic benchmark scraper** that auto-detects a payload with no user
325
+ review — the declarative source + authoring step is the mechanism.
326
+ - **A UI for browsing/editing benchmark sources** — the source file is edited by
327
+ hand or by the authoring step; the explorer's role editor gains the external
328
+ metrics already in use.
329
+ - **Changing the cardinal transform classes** — external metrics are normalized
330
+ to 0–1 at parse time.
331
+ - **Re-ranking or re-fetching beyond the daily UTC cache chain.**
332
+
333
+ Weight editing is the explorer's Export, `src/cli/create-role.ts` or
334
+ `/create-agent` (all validate through `resolveSettings`); role/agent removal is
335
+ `/remove-agent` (through `removeRoleSettings`).
package/docs/dev/ci.md ADDED
@@ -0,0 +1,152 @@
1
+ # CI/CD
2
+
3
+ The GitHub Actions pipeline that gates every change and delivers every release.
4
+ The release *policy* — when to bump, SemVer over the public surface, the
5
+ CHANGELOG rules, the three channels — lives in [`releasing.md`](releasing.md);
6
+ this doc owns the machinery.
7
+
8
+ ## The workflow
9
+
10
+ `.github/workflows/ci.yml` — one file, two jobs. One file because the npm
11
+ trusted-publisher registration names the workflow **filename**, so a second file
12
+ would need a second registration.
13
+
14
+ | Job | Runs on | Does |
15
+ |---|---|---|
16
+ | `test` | every `pull_request`, every push to `main`, every `v*` tag push, and `workflow_dispatch` | `npm ci`, then `node --test tests/` |
17
+ | `release` | `v*` tag pushes (and a `workflow_dispatch` against a tag ref) | verifies the tag, publishes to npm over OIDC, creates the GitHub Release |
18
+
19
+ `release` declares `needs: test`, so a red tree cannot be published: the same
20
+ ref must be green first. `test` runs **exactly** the command the local
21
+ pre-commit hook runs (`.githooks/pre-commit`), and `tests/ci.test.ts` fails when
22
+ the two drift apart — "green locally" and "green in CI" cannot diverge.
23
+
24
+ The Node version comes from `.node-version` at the repo root (the exact local
25
+ toolchain; bumping it is a normal commit). `engines` is deliberately not in
26
+ `package.json`: the plugin executes inside omp's runtime, so an `engines` field
27
+ would only produce install warnings for users.
28
+
29
+ `test` is the whole gate — no lint, no format, no typecheck, no coverage. The
30
+ repo runs Node type-stripping without a typecheck (see [`quirks.md`](quirks.md)).
31
+
32
+ ## The tag → publish contract
33
+
34
+ Pushing a `v<version>` tag is the delivery trigger. The `release` job:
35
+
36
+ 1. checks out the tag;
37
+ 2. runs `node scripts/release.ts notes "$GITHUB_REF_NAME"` as the **guard** —
38
+ tag ↔ package version ↔ lockstep ↔ CHANGELOG section — and writes the release
39
+ notes to a file. A mistyped tag, a half-applied lockstep bump or a missing
40
+ CHANGELOG section fails here, *before* anything is published;
41
+ 3. ensures npm ≥ 11.5.1 (the trusted-publishing floor);
42
+ 4. `npm publish` — authenticated by GitHub OIDC, with a provenance attestation
43
+ generated automatically. No `NPM_TOKEN`, no `.npmrc` step, no third-party
44
+ publish action;
45
+ 5. `gh release create <tag> --notes-file <notes>` — the GitHub Release body is
46
+ the CHANGELOG section, never retyped.
47
+
48
+ Because the publish runs from the tagged commit, npm's `gitHead` equals the tag.
49
+
50
+ ### One-time setup: register the trusted publisher
51
+
52
+ Required before the first CD run, and it cannot be done from the repo. On
53
+ npmjs.com → the package → Settings → Trusted publishing → GitHub Actions:
54
+
55
+ - **Organization or user**: `Gott50`
56
+ - **Repository**: `omp-llm-role`
57
+ - **Workflow filename**: `ci.yml` (the filename only, with the extension)
58
+ - **Allowed actions**: tick **direct publishing (`npm publish`)**.
59
+
60
+ That last tick is the trap. `npm stage publish` is always allowed, and a
61
+ configuration created after 2026-09-03 is automatically set to allow *only*
62
+ staged publishing — so a publisher registered today rejects the job's
63
+ `npm publish` **after** the guard has already passed, the worst possible failure
64
+ point. Staged publishing is not a substitute: it needs a separate
65
+ `npm stage approve` with 2FA, which breaks unattended CD. Do **not** tick
66
+ `npm dist-tag`; CD never moves a dist-tag.
67
+
68
+ Floors and constraints (npm's trusted-publishing docs): npm CLI ≥ 11.5.1, Node
69
+ ≥ 22.14.0 (the pinned `.node-version` clears both), and GitHub-**hosted** runners
70
+ only — hence `ubuntu-latest`.
71
+
72
+ ## The release script
73
+
74
+ `scripts/release.ts` — Node builtins only, no dependencies, outside the `files`
75
+ whitelist so it never ships in the tarball. Three subcommands, each with a
76
+ documented exit code:
77
+
78
+ | Command | Exit 0 | Exit 1 |
79
+ |---|---|---|
80
+ | `due` | `## [Unreleased]` has at least one entry under an allowed section | it is empty (message on stdout) |
81
+ | `notes <tag>` | prints **only** the CHANGELOG section body, ready for `--notes-file` | the tag is not `v<semver>`, disagrees with the package version, the lockstep fields disagree, the CHANGELOG has no `## [<version>] - <date>` heading, or that section has no entries (message on stderr) |
82
+ | `cut <patch\|minor\|major> [--dry-run]` | the cut ran | a refusal (message on stderr) |
83
+
84
+ `due` is the agent's branch point: "not owed" is not an error, so a script can
85
+ branch on the code without parsing markdown.
86
+
87
+ ### The lockstep set
88
+
89
+ Exactly four fields, bumped together:
90
+
91
+ - `package.json` `version`
92
+ - `.omp-plugin/marketplace.json` `plugins[0].version`
93
+ - `package-lock.json` `version`
94
+ - `package-lock.json` `packages[""].version`
95
+
96
+ The marketplace catalog's own `metadata.version` is **deliberately excluded** —
97
+ `releasing.md` locks the plugin entry, not the catalog. The two numbers look
98
+ like they should agree; they do not.
99
+
100
+ ### What `cut` does, in order
101
+
102
+ 1. refuse a dirty tree;
103
+ 2. refuse an existing tag;
104
+ 3. refuse an empty `## [Unreleased]`;
105
+ 4. refuse when HEAD is not `origin/main` (so the tag always points at a pushed
106
+ commit and npm's `gitHead` resolves);
107
+ 5. compute the new version from the bump argument;
108
+ 6. write the four version fields;
109
+ 7. rename `## [Unreleased]` to `## [<version>] - <YYYY-MM-DD>` (UTC) and insert
110
+ a fresh empty `## [Unreleased]` above it;
111
+ 8. rewrite `[Unreleased]: …/compare/v<version>...HEAD` and prepend
112
+ `[<version>]: …/releases/tag/v<version>` to the footer link block;
113
+ 9. regenerate `docs/llm-role-rankings.md`
114
+ (`node src/cli/llm-role-rank.ts --all --out docs/llm-role-rankings.md`);
115
+ 10. commit `Release <version>`, staging only the touched paths (never `.`);
116
+ 11. push `main`, create the annotated tag `v<version>`, push the tag.
117
+
118
+ The tag push is the CD trigger. `--dry-run` writes nothing: it prints the target
119
+ version, the file plan and the changelog diff, and skips the network-dependent
120
+ report regeneration with a note.
121
+
122
+ `cut` does not run the suite itself. The commit it makes goes through
123
+ `.githooks/pre-commit` when `core.hooksPath` is wired, and the `release` job
124
+ re-verifies with `needs: test` on the tag — a second definition of the gate
125
+ inside the script would be a second thing to keep in sync.
126
+
127
+ There is no `--force` and no interactive prompt: every refusal is a message plus
128
+ exit 1, so the command is safe to run unattended.
129
+
130
+ ## Verifying the pipeline
131
+
132
+ - **The gate:** push a branch and open a PR, or push to `main`; `gh run watch`
133
+ the `test` job green.
134
+ - **The guard (negative control):** push a tag that disagrees with the package
135
+ version (e.g. `v0.0.1` against `1.1.0`); the `release` job must fail in the
136
+ `notes` guard *before* any publish and leave the registry untouched. Delete
137
+ the tag afterwards.
138
+ - **The publish:** run the release job against an already-cut tag —
139
+ `gh workflow run ci.yml --ref v<version>` — then confirm npm's `gitHead`
140
+ equals the tag commit, `dist-tags.latest` moved, and the GitHub Release body
141
+ is that version's CHANGELOG section. This is also the recovery path for a
142
+ failed publish: the tag exists, so no new version is needed.
143
+
144
+ The dispatch resolves the workflow **from the ref**, so the tag's commit must
145
+ contain `.github/workflows/ci.yml`. A tag cut before this workflow existed
146
+ cannot be dispatched (`HTTP 422: Workflow does not have 'workflow_dispatch'
147
+ trigger`) — the recovery path only works for tags cut after it landed.
148
+
149
+ `tests/ci.test.ts` asserts the offline half: the workflow's structure (jobs,
150
+ triggers, permissions, the command it runs), the hook/workflow command parity,
151
+ the tag-trigger ↔ CHANGELOG-link agreement, the lockstep fields, and the
152
+ script's exit codes and refusals against fixture repositories.