@mrciphersmith/keryx 0.3.27 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/cli.js +2083 -1510
- package/dist/core.js +7 -0
- package/package.json +1 -1
- package/src/gdskills/bundled/rules/core/model-selection.mdc +65 -18
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +2 -1
- package/src/gdskills/contracts/subagent-dispatch.schema.json +4 -4
package/dist/core.js
CHANGED
|
@@ -9178,12 +9178,19 @@ var init_export = __esm(() => {
|
|
|
9178
9178
|
SKILL_RUNTIMES = [...HARNESS_SKILL_RUNTIMES, "plugin"];
|
|
9179
9179
|
});
|
|
9180
9180
|
|
|
9181
|
+
// src/gdskills/model-version.ts
|
|
9182
|
+
var TRAILING_ALIAS_WORDS;
|
|
9183
|
+
var init_model_version = __esm(() => {
|
|
9184
|
+
TRAILING_ALIAS_WORDS = new Set(["latest", "preview"]);
|
|
9185
|
+
});
|
|
9186
|
+
|
|
9181
9187
|
// src/gdskills/model-tier.ts
|
|
9182
9188
|
function isModelTier(value) {
|
|
9183
9189
|
return typeof value === "string" && MODEL_TIERS.includes(value);
|
|
9184
9190
|
}
|
|
9185
9191
|
var MODEL_TIERS, TIER_ALIASES, SKILL_TIER_KEY = "model_tier", TIER_DECLARATION;
|
|
9186
9192
|
var init_model_tier = __esm(() => {
|
|
9193
|
+
init_model_version();
|
|
9187
9194
|
MODEL_TIERS = ["light", "standard", "deep"];
|
|
9188
9195
|
TIER_ALIASES = new Map([["cheap", "light"]]);
|
|
9189
9196
|
TIER_DECLARATION = new RegExp(`^\\s*(?:[-*]\\s*)?"?(?:${SKILL_TIER_KEY}|model-tier|modelTier)"?\\s*[:=]\\s*(.+?)\\s*,?\\s*$`, "i");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.28",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -57,13 +57,14 @@ candidate set. `src/gdskills/model-tier.ts` then:
|
|
|
57
57
|
| Tier | Resolves to |
|
|
58
58
|
|---|---|
|
|
59
59
|
| `standard` | the session's model, always |
|
|
60
|
-
| `deep` | the
|
|
61
|
-
| `light` | the
|
|
60
|
+
| `deep` | the best discovered model **above** the session's — a larger size class, or a NEWER generation of the session model's own family — else the session's |
|
|
61
|
+
| `light` | the **next size step below** the session's (an Opus-class session takes the Sonnet class, not the Haiku class), else the session's; the smallest class only when nothing sits between |
|
|
62
62
|
|
|
63
63
|
Anchoring on the session model is what makes "never a downgrade" checkable
|
|
64
64
|
rather than hoped for: a tier can only move away from the session model in the
|
|
65
65
|
direction its own name points, and a candidate at the session's own rank is a
|
|
66
|
-
lateral move and never taken.
|
|
66
|
+
lateral move and never taken. An OLDER sibling of the session's own family is
|
|
67
|
+
never `light` or `deep` either.
|
|
67
68
|
|
|
68
69
|
## What the hints are, and what they are not
|
|
69
70
|
|
|
@@ -83,13 +84,24 @@ residue, and it is kept in the smallest shape that works:
|
|
|
83
84
|
A model whose name is a codename carrying no size is unrankable by design. That
|
|
84
85
|
is the honest outcome, not a gap to be patched with folklore.
|
|
85
86
|
|
|
86
|
-
**Version is
|
|
87
|
-
version number out of an id
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
**Version is a second axis, not a size hint (flow 358).** `rankModelId` still
|
|
88
|
+
never parses a version number out of an id: the size words order size CLASSES.
|
|
89
|
+
Generation is ordered separately, and only within **the same family and
|
|
90
|
+
vendor** — the id with its version tokens removed (`familyKey`) must match. A
|
|
91
|
+
newer release outranks an older one of its own family, so a session on the
|
|
92
|
+
older one resolves `deep` to the newer sibling, and a session on the newer one
|
|
93
|
+
never resolves `light` or `deep` to the older. A version number is **never**
|
|
94
|
+
compared across families: a higher number on one line says nothing about a
|
|
95
|
+
different line.
|
|
96
|
+
|
|
97
|
+
**A larger class of an older generation is ambiguous, not "above".** When the
|
|
98
|
+
only discovered models above the session are a larger size class whose version is
|
|
99
|
+
older than the session's, size words alone cannot call them stronger, and
|
|
100
|
+
`deep` must not move onto a model that is older-generation and — by the
|
|
101
|
+
profile price — the more expensive one. Without an agent the tier keeps the
|
|
102
|
+
session's model as `session-fallback`, with the reason recorded (reason id
|
|
103
|
+
`kept-older-generation`, or `kept-older-generation-pricier` when the profile
|
|
104
|
+
price shows it costs more). The agent fallback below is the only other way out.
|
|
93
105
|
|
|
94
106
|
The routing "derived default table" (`deriveDefaultTable`,
|
|
95
107
|
`src/harness/routing/derive-default-table.ts`, flow 327/PRD §6.3) is that
|
|
@@ -102,8 +114,9 @@ version ranks higher there — the family word (a size marker from this same
|
|
|
102
114
|
`MODEL_RANK_HINTS` list) already carries the size class, so version is free
|
|
103
115
|
to order generations within it. That table reuses
|
|
104
116
|
`rankModelId`/`MODEL_RANK_HINTS` UNMODIFIED for the size axis and adds its
|
|
105
|
-
own separate, conservative version parse on top of it
|
|
106
|
-
|
|
117
|
+
own separate, conservative version parse on top of it. Since flow 358 the tier
|
|
118
|
+
resolution reads the same version parse (`src/gdskills/model-version.ts`, shared
|
|
119
|
+
so neither module imports the other).
|
|
107
120
|
|
|
108
121
|
That version parse (`parseModelVersion`/`familyKey`, round 2) reads BOTH a
|
|
109
122
|
dotted id (`vendor-opus-5.5`, `gemini-3.8-flash`) and a real
|
|
@@ -152,6 +165,35 @@ Do not "fix" a fallback by adding model ids to the code. Add a size word to the
|
|
|
152
165
|
hints if one genuinely applies, or leave it: running on the session's model is a
|
|
153
166
|
correct answer.
|
|
154
167
|
|
|
168
|
+
## The agent fallback: comparing candidate models, nothing else
|
|
169
|
+
|
|
170
|
+
When the ranking is **refused** but the provider did report other models, or a
|
|
171
|
+
tier is **ambiguous** (an older-generation larger class, above), an injected
|
|
172
|
+
agent may be asked to put the candidate models in order. It is a port
|
|
173
|
+
(`TierRankAgent`): `src/gdskills/model-tier.ts` holds no network and no
|
|
174
|
+
filesystem, the caller supplies the real one, and a test supplies a fake.
|
|
175
|
+
|
|
176
|
+
Its limits are the point:
|
|
177
|
+
|
|
178
|
+
- **It compares candidate models and never rates a task's own difficulty.** It is
|
|
179
|
+
shown no task, no diff and no user text — only the discovered ids and the
|
|
180
|
+
profile prices — and it is asked one question: which of these models is more
|
|
181
|
+
capable than which. The tier is still computed by `assignTier` from signals
|
|
182
|
+
the orchestrator holds.
|
|
183
|
+
- It must answer JSON, `{"order": [...]}`, best first, and must place the session's
|
|
184
|
+
own model. An id that was not discovered is dropped. An agent error, a
|
|
185
|
+
timeout, a malformed answer, or one that leaves out the session's model
|
|
186
|
+
resolves to the session's model, exactly as without an agent.
|
|
187
|
+
- It never places `standard` or `deep` below the session: `deep` is only ever taken
|
|
188
|
+
from above the session in its order, `standard` is never asked. A larger
|
|
189
|
+
class of an older generation that costs more than the session's model is
|
|
190
|
+
refused whatever it says.
|
|
191
|
+
- The result is cached by a hash of the candidate catalogue with prices, so an
|
|
192
|
+
identical catalogue asks once.
|
|
193
|
+
- It runs on the **light** tier of the session's own provider — the cheap step
|
|
194
|
+
below the session, or the session model itself when nothing sits below.
|
|
195
|
+
- A resolution it produced is recorded `tier_resolution: agent-ranked`.
|
|
196
|
+
|
|
155
197
|
## Where this actually runs
|
|
156
198
|
|
|
157
199
|
Two places, and they are not the same shape:
|
|
@@ -236,8 +278,9 @@ arithmetic straight back in the caller's head.
|
|
|
236
278
|
|
|
237
279
|
## Adaptive: `inherit: true`
|
|
238
280
|
|
|
239
|
-
A model is named only when discovery
|
|
240
|
-
|
|
281
|
+
A model is named only when discovery — or, failing that, the agent fallback —
|
|
282
|
+
assigned one **other than** the session's (`tier_resolution: discovered` or
|
|
283
|
+
`agent-ranked`). In every other case — no session named, ranking
|
|
241
284
|
refused, or the tier resolving to the session's own model — the block carries the
|
|
242
285
|
tier and `inherit: true`, and the **host** resolves it against the models its own
|
|
243
286
|
dispatch tool offers:
|
|
@@ -255,18 +298,19 @@ fallback. Exit status is always 0.
|
|
|
255
298
|
## Recording the decision
|
|
256
299
|
|
|
257
300
|
The dispatch carries the tier, the ordered rule ids that produced it, which of
|
|
258
|
-
the
|
|
259
|
-
`model.tier`, `model.tier_reasons
|
|
301
|
+
the outcomes occurred, and what was on the table when it did —
|
|
302
|
+
`model.tier`, `model.tier_reasons` (the assignment rule ids, then `resolve:<reason id>` for the resolution), `model.tier_resolution` and
|
|
260
303
|
`model.model_discovery` in
|
|
261
304
|
`.metaproject/core/gdskills/contracts/subagent-dispatch.schema.json`.
|
|
262
305
|
|
|
263
|
-
`tier_resolution` distinguishes
|
|
306
|
+
`tier_resolution` distinguishes four facts that must not be flattened:
|
|
264
307
|
|
|
265
308
|
| Value | Means |
|
|
266
309
|
|---|---|
|
|
267
310
|
| `discovered` | a discovered model was assigned to this tier |
|
|
268
311
|
| `session-ranked` | ranking worked and placed the tier at the session's model |
|
|
269
|
-
| `session-fallback` | ranking was refused; the session's model is kept |
|
|
312
|
+
| `session-fallback` | ranking was refused or was ambiguous; the session's model is kept |
|
|
313
|
+
| `agent-ranked` | the agent fallback put the candidates in order and its answer was used |
|
|
270
314
|
|
|
271
315
|
"Fell back to the session model" and "assigned the session model because it
|
|
272
316
|
ranked there" are different facts. A run that cannot be explained afterwards is
|
|
@@ -286,6 +330,9 @@ recording them is one copy rather than four decisions.
|
|
|
286
330
|
the resolution code.
|
|
287
331
|
- Take the candidate set from runtime detection. Never from a literal list of
|
|
288
332
|
what exists.
|
|
333
|
+
- The agent fallback compares candidate models. Never ask any model to rate a
|
|
334
|
+
task's own difficulty, and never let the fallback place `standard` or `deep`
|
|
335
|
+
below the session's model.
|
|
289
336
|
- When the candidates cannot be ranked, the block is adaptive (`inherit: true`).
|
|
290
337
|
The host never goes below its session model for `standard` or `deep`, and takes
|
|
291
338
|
a lighter class only for a `light` tier.
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
"model": {
|
|
86
|
-
"description": "Which model runs this dispatch. Skills declare a `tier`; at dispatch time src/gdskills/model-tier.ts DISCOVERS the models runtime provider detection reported for the active provider, ranks them by size markers in their names, and places the tier relative to the session's own model — `standard` is the session model, `deep` a discovered model
|
|
86
|
+
"description": "Which model runs this dispatch. Skills declare a `tier`; at dispatch time src/gdskills/model-tier.ts DISCOVERS the models runtime provider detection reported for the active provider, ranks them by size markers in their names, and places the tier relative to the session's own model — `standard` is the session model, `deep` a discovered model above it (a larger size class, or a newer generation of the session model's own family), `light` the next size step below it. When the ranking is refused or ambiguous an injected agent may order the candidate models instead (it compares models, never rates a task); its answer is recorded as `agent-ranked`. No table of model ids exists anywhere in the resolution. When the candidates cannot be ranked, every tier keeps the session's model. `provider`/`model` remain available for an explicit lateral selection, and an omitted block inherits the parent verbatim. Two things fill this block. The interactive `spawn_subagent` tool does it in code: it builds the tier map with `buildTierMap` from its host's detection result, hands it to `resolveChildModel`, and records the outcome on the dispatch's run trace. An orchestrator authoring a dispatch document RUNS `keryx review tier`, which takes the §4.4 signals as flags and prints exactly the fields below, ready to paste (`--json` prints this object alone). It is a command rather than a documented function call because an orchestrator is an agent following prose and cannot call a TypeScript function: told to `decideDispatchModel`, it computed the tier in its head from a table, which is the mechanical step that belongs in code. When nothing can be worked out the command emits `inherit: true` instead of `provider`/`model` — the dispatch runs on the caller's own model, never a downgrade. Neither path is enforced by a reader — nothing validates a recorded resolution against the model that actually ran — so these fields are a record, not a gate.",
|
|
87
87
|
"type": "object",
|
|
88
88
|
"additionalProperties": false,
|
|
89
89
|
"properties": {
|
|
@@ -95,14 +95,14 @@
|
|
|
95
95
|
"enum": ["light", "standard", "deep"]
|
|
96
96
|
},
|
|
97
97
|
"tier_reasons": {
|
|
98
|
-
"description": "The ordered rule ids `assignTier` applied, e.g. [\"base:standard\", \"floor:blast-radius\"]. Produced by `keryx review tier` from the signals passed as flags, never written by hand: a reason list an author composes records the story they tell about the tier rather than the rules that produced it. Recorded so a tier can be explained after the run instead of re-derived from a state nobody kept. Ids rather than prose, because they are compared across runs.",
|
|
98
|
+
"description": "The ordered rule ids `assignTier` applied, e.g. [\"base:standard\", \"floor:blast-radius\"], followed by one `resolve:<reason-id>` entry from the model resolution (e.g. `resolve:picked-above`, `resolve:kept-older-generation-pricier`, `resolve:agent-picked-below`). Produced by `keryx review tier` from the signals passed as flags, never written by hand: a reason list an author composes records the story they tell about the tier rather than the rules that produced it. Recorded so a tier can be explained after the run instead of re-derived from a state nobody kept. Ids rather than prose, because they are compared across runs.",
|
|
99
99
|
"type": "array",
|
|
100
100
|
"items": { "type": "string", "minLength": 1 }
|
|
101
101
|
},
|
|
102
102
|
"tier_resolution": {
|
|
103
|
-
"description": "Which of
|
|
103
|
+
"description": "Which of four outcomes produced the model, as `resolveTierModel` reports it on `TierResolution.source`. `discovered` — a model reported by runtime provider detection was assigned to this tier. `session-ranked` — ranking WORKED and placed the tier at the session's own model (always so for `standard`; for `light`/`deep` when nothing discovered ranks below/above the session). `session-fallback` — ranking was refused or was ambiguous (a larger size class that is an older generation), or the ranking carried no rank for the session model, so the session's model is used because nothing could be worked out. `agent-ranked` — the ranking was refused or ambiguous and an injected agent, shown only the discovered ids and profile prices, put the candidate models in order and its validated answer picked the model; it compares models and never rates a task. Collapsing these would hide whether the environment was understood. Recorded, never read back: it explains a finished run and gates nothing.",
|
|
104
104
|
"type": "string",
|
|
105
|
-
"enum": ["discovered", "session-ranked", "session-fallback"]
|
|
105
|
+
"enum": ["discovered", "session-ranked", "session-fallback", "agent-ranked"]
|
|
106
106
|
},
|
|
107
107
|
"model_discovery": {
|
|
108
108
|
"description": "What was on the table when the tier was resolved: which models runtime detection reported for the active provider, how the size-marker hints ordered them, and — when ranking was refused — why. `tier_resolution` alone says a fallback happened and never says what it fell back FROM.",
|