claude-dev-env 7.7.0 → 7.7.1

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.
@@ -21,8 +21,10 @@ every rule below.
21
21
  Before returning, the resumed agent runs the code-rules gate with the same bare
22
22
  call every other surface in this skill uses:
23
23
 
24
- 1. Run `~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo
25
- root>` with no file paths and no `--only-under` prefix.
24
+ 1. Run `python ~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root
25
+ <repo root>` with no file paths and no `--only-under` prefix. The `python`
26
+ prefix is required. On Windows a bare `.py` path opens the file in Cursor,
27
+ which then crashes on the gate flags.
26
28
  2. If the gate reports violations **on lines this fix already owns** (the files
27
29
  and added lines the fix changed), fix them and re-run the exact same command.
28
30
  3. A violation on a path or added line outside this fix's own work is reported
@@ -48,8 +48,10 @@ no advisor call reopens that.
48
48
  ## Required checks
49
49
 
50
50
  "Run required checks" means: run
51
- `~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo root>`
52
- with no file paths and no `--only-under` prefix. On a violation on lines this
51
+ `python ~/.claude/_shared/pr-loop/scripts/code_rules_gate.py --repo-root <repo root>`
52
+ with no file paths and no `--only-under` prefix. The `python` prefix is
53
+ required. On Windows a bare `.py` path opens the file in Cursor, which then
54
+ crashes on the gate flags. On a violation on lines this
53
55
  round already owns, fix it and re-run the exact same command until clean. A
54
56
  violation outside the review target is reported or skipped, not force-fixed into
55
57
  unrelated files, and the skip is logged with path and reason.
@@ -111,6 +111,38 @@ test('Codex preferences skip an excluded pair in a panel', () => {
111
111
  }
112
112
  });
113
113
 
114
+ test('selector exclusions remove Sol Medium before its Luna remap enters a panel', () => {
115
+ const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-sol-exclusion-'));
116
+ try {
117
+ writeHostPreferences(preferencesDirectory, 'codex', {
118
+ 'how critics': [
119
+ { model: ' Sol ', effort: 'Medium' },
120
+ { model: 'gpt-5.6-sol', effort: 'medium' },
121
+ { model: 'gpt-5.6-luna', effort: 'high' },
122
+ { model: 'gpt-6-astra', effort: 'medium' },
123
+ ],
124
+ });
125
+ const selection = selectWithPreferences(preferencesDirectory, {
126
+ role: 'how critics',
127
+ panel: { agentCount: 2, requiresDistinctModels: true },
128
+ availableModelIds: ['gpt-5.6-sol', 'gpt-5.6-luna', 'gpt-6-astra'],
129
+ confirmedSuitableModelIds: ['gpt-5.6-sol'],
130
+ });
131
+ assert.equal(selection.canDelegate, true);
132
+ assert.deepEqual(selection.panel.selectedModelPairs, [
133
+ { model: 'gpt-5.6-luna', effort: 'high' },
134
+ { model: 'gpt-6-astra', effort: 'medium' },
135
+ ]);
136
+ assert.deepEqual(selection.nativeSpawnArguments, {
137
+ model: 'gpt-5.6-luna',
138
+ reasoning_effort: 'high',
139
+ });
140
+ assert.deepEqual(selection.routingDiagnostics, []);
141
+ } finally {
142
+ rmSync(preferencesDirectory, { recursive: true, force: true });
143
+ }
144
+ });
145
+
114
146
  test('a foreign host reads only its own saved preferences', () => {
115
147
  const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-host-'));
116
148
  try {
@@ -563,6 +595,23 @@ test('a model-only preference holds when the host supplies no usable effort', ()
563
595
  }
564
596
  });
565
597
 
598
+ test('a model-only Sol preference holds when no Sol effort remains approved', () => {
599
+ const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-sol-missing-effort-'));
600
+ try {
601
+ writeHostPreferences(preferencesDirectory, 'codex', {
602
+ 'feature, refactoring': ['Sol'],
603
+ });
604
+ const selection = selectWithPreferences(preferencesDirectory, {
605
+ requestedEffort: undefined,
606
+ availableModelIds: ['gpt-5.6-sol', 'gpt-5.6-luna'],
607
+ });
608
+ assert.equal(selection.canDelegate, false);
609
+ assert.match(selection.failure, /effort is required for this model/);
610
+ } finally {
611
+ rmSync(preferencesDirectory, { recursive: true, force: true });
612
+ }
613
+ });
614
+
566
615
  test('non-Codex inheritance keeps its ordered parent fallback', () => {
567
616
  const preferencesDirectory = mkdtempSync(join(tmpdir(), 'pstack-routing-parent-'));
568
617
  try {
@@ -1,6 +1,6 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { execFileSync } from 'node:child_process';
3
+ import { execFileSync, spawnSync } from 'node:child_process';
4
4
  import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { dirname, join } from 'node:path';
@@ -35,6 +35,7 @@ const replacements = [
35
35
  [['Luna', 'low'], ['gpt-5.6-luna', 'high']],
36
36
  [['Luna', 'medium'], ['gpt-5.6-luna', 'high']],
37
37
  [['Sol', 'low'], ['gpt-5.6-luna', 'xhigh']],
38
+ [['Sol', 'medium'], ['gpt-5.6-luna', 'max']],
38
39
  [['Terra', 'low'], ['gpt-5.6-luna', 'xhigh']],
39
40
  [['Terra', 'medium'], ['gpt-5.6-luna', 'xhigh']],
40
41
  [['Terra', 'high'], ['gpt-5.6-luna', 'xhigh']],
@@ -59,7 +60,6 @@ test('every approved pair returns its literal native pair', () => {
59
60
  [['Luna', 'high'], ['gpt-5.6-luna', 'high']],
60
61
  [['Luna', 'xhigh'], ['gpt-5.6-luna', 'xhigh']],
61
62
  [['Luna', 'max'], ['gpt-5.6-luna', 'max']],
62
- [['Sol', 'medium'], ['gpt-5.6-sol', 'medium']],
63
63
  [['Astra', 'low'], ['gpt-6-astra', 'low']],
64
64
  [['Astra', 'medium'], ['gpt-6-astra', 'medium']],
65
65
  ];
@@ -67,6 +67,9 @@ test('every approved pair returns its literal native pair', () => {
67
67
  const result = route({ model, reasoning_effort: effort });
68
68
  assert.ok(['pass', 'remapped'].includes(result.status));
69
69
  assert.deepEqual(result.selected, { model: expectedModel, effort: expectedEffort });
70
+ const nativeRoute = route({ model: expectedModel, reasoning_effort: expectedEffort });
71
+ assert.equal(nativeRoute.status, 'pass');
72
+ assert.deepEqual(nativeRoute.selected, { model: expectedModel, effort: expectedEffort });
70
73
  }
71
74
  const advisor = advisorRoute({
72
75
  model: 'Astra',
@@ -77,12 +80,12 @@ test('every approved pair returns its literal native pair', () => {
77
80
  assert.deepEqual(advisor.selected, { model: 'gpt-6-astra', effort: 'high' });
78
81
  });
79
82
 
80
- test('selector exclusions leave direct Sol Medium routing valid', () => {
83
+ test('selector exclusions keep Sol Medium out while direct routing remaps', () => {
81
84
  const policy = loadSubagentModelPolicy();
82
85
  assert.equal(policy.selectorExclusions.has('sol/medium'), true);
83
86
  const result = route({ model: 'Sol', reasoning_effort: 'medium' });
84
- assert.ok(['pass', 'remapped'].includes(result.status));
85
- assert.deepEqual(result.selected, { model: 'gpt-5.6-sol', effort: 'medium' });
87
+ assert.equal(result.status, 'remapped');
88
+ assert.deepEqual(result.selected, { model: 'gpt-5.6-luna', effort: 'max' });
86
89
 
87
90
  const rawPolicy = JSON.parse(readFileSync(POLICY_SOURCE, 'utf8'));
88
91
  rawPolicy.selectorExclusions = [{ model: 'missing', effort: 'medium' }];
@@ -174,6 +177,25 @@ test('the advisor bridge blocks malformed input', () => {
174
177
  );
175
178
  });
176
179
 
180
+ test('the advisor bridge routes Sol Medium to Luna Max', () => {
181
+ const bridgePath = join(PACKAGE_ROOT, 'scripts', 'resolve_advisor_model_route.mjs');
182
+ const bridgeExecution = spawnSync(
183
+ process.execPath,
184
+ [bridgePath],
185
+ {
186
+ encoding: 'utf8',
187
+ input: JSON.stringify({ model: 'Sol', reasoning_effort: 'medium' }),
188
+ },
189
+ );
190
+ assert.equal(bridgeExecution.status, 0);
191
+ assert.equal(bridgeExecution.stderr, '');
192
+ const advisorDecision = JSON.parse(bridgeExecution.stdout);
193
+ assert.equal(advisorDecision.status, 'remapped');
194
+ assert.equal(advisorDecision.role, 'advisor');
195
+ assert.equal(advisorDecision.diagnostic, null);
196
+ assert.deepEqual(advisorDecision.selected, { model: 'gpt-5.6-luna', effort: 'max' });
197
+ });
198
+
177
199
  test('missing worker settings preserve parent inheritance', () => {
178
200
  const input = { message: 'keep', agent_type: 'worker' };
179
201
  const result = routeSubagentToolInput(input);
@@ -181,6 +203,15 @@ test('missing worker settings preserve parent inheritance', () => {
181
203
  assert.strictEqual(result.updatedInput, input);
182
204
  });
183
205
 
206
+ test('Sol without an effort stays blocked after its only approved pair is removed', () => {
207
+ const result = route(
208
+ { model: 'Sol' },
209
+ { allowMissingEffort: true },
210
+ );
211
+ assert.equal(result.status, 'blocked');
212
+ assert.equal(result.diagnostic, 'effort is required for this model');
213
+ });
214
+
184
215
  test('unknown input holds with a short diagnostic', () => {
185
216
  assert.match(route({ model: 'Unknown', reasoning_effort: 'medium' }).diagnostic, /model is unknown/);
186
217
  assert.match(route({ model: 'Terra', reasoning_effort: 'future' }).diagnostic, /effort is unknown/);
@@ -201,16 +232,16 @@ test('native values with case or whitespace differences are canonicalized', () =
201
232
  });
202
233
  assert.equal(result.status, 'remapped');
203
234
  assert.deepEqual(result.updatedInput, {
204
- model: 'gpt-5.6-sol',
205
- reasoning_effort: 'medium',
235
+ model: 'gpt-5.6-luna',
236
+ reasoning_effort: 'max',
206
237
  message: 'keep',
207
238
  });
208
239
  });
209
240
 
210
241
  test('unavailable replacements hold the spawn', () => {
211
242
  const result = route(
212
- { model: 'Terra', reasoning_effort: 'medium' },
213
- { availableModelIds: ['gpt-5.6-terra'] },
243
+ { model: 'Sol', reasoning_effort: 'medium' },
244
+ { availableModelIds: ['gpt-5.6-sol'] },
214
245
  );
215
246
  assert.equal(result.status, 'blocked');
216
247
  assert.match(result.diagnostic, /replacement model is unavailable/);
@@ -246,7 +277,7 @@ test('the tool route copies every non-routing field', () => {
246
277
 
247
278
  test('a second route pass is idempotent', () => {
248
279
  const first = routeSubagentToolInput(
249
- { model: 'Terra', reasoning_effort: 'medium', message: 'keep' },
280
+ { model: 'Sol', reasoning_effort: 'medium', message: 'keep' },
250
281
  {},
251
282
  );
252
283
  const second = routeSubagentToolInput(first.updatedInput);
@@ -261,17 +292,22 @@ test('a temporary policy copy changes the next route without source edits', () =
261
292
  const temporaryPolicy = join(temporaryRoot, 'policy.json');
262
293
  const sourceText = readFileSync(POLICY_SOURCE, 'utf8');
263
294
  const policy = JSON.parse(sourceText);
295
+ writeFileSync(temporaryPolicy, sourceText);
296
+ assert.deepEqual(route(
297
+ { model: 'Sol', reasoning_effort: 'medium' },
298
+ { policyPath: temporaryPolicy },
299
+ ).selected, { model: 'gpt-5.6-luna', effort: 'max' });
264
300
  policy.replacements = policy.replacements.map(replacement => (
265
- replacement.requested.model === 'terra' && replacement.requested.effort === 'medium'
266
- ? { ...replacement, selected: { model: 'luna', effort: 'max' } }
301
+ replacement.requested.model === 'sol' && replacement.requested.effort === 'medium'
302
+ ? { ...replacement, selected: { model: 'luna', effort: 'xhigh' } }
267
303
  : replacement
268
304
  ));
269
305
  writeFileSync(temporaryPolicy, JSON.stringify(policy));
270
- const result = route(
271
- { model: 'Terra', reasoning_effort: 'medium' },
306
+ const updatedRoute = route(
307
+ { model: 'Sol', reasoning_effort: 'medium' },
272
308
  { policyPath: temporaryPolicy },
273
309
  );
274
- assert.deepEqual(result.selected, { model: 'gpt-5.6-luna', effort: 'max' });
310
+ assert.deepEqual(updatedRoute.selected, { model: 'gpt-5.6-luna', effort: 'xhigh' });
275
311
  assert.equal(readFileSync(POLICY_SOURCE, 'utf8'), sourceText);
276
312
  } finally {
277
313
  rmSync(temporaryRoot, { recursive: true, force: true });
@@ -347,10 +383,11 @@ test('model names cannot collide after normalization', () => {
347
383
 
348
384
  test('combined role routes must end at an approved terminal pair', () => {
349
385
  const policy = JSON.parse(readFileSync(POLICY_SOURCE, 'utf8'));
350
- policy.replacements.push({
351
- requested: { model: 'sol', effort: 'medium' },
352
- selected: { model: 'astra', effort: 'low' },
353
- });
386
+ const solMediumReplacement = policy.replacements.find(replacement => (
387
+ replacement.requested.model === 'sol'
388
+ && replacement.requested.effort === 'medium'
389
+ ));
390
+ solMediumReplacement.selected = { model: 'astra', effort: 'low' };
354
391
  policy.roleReplacements.push({
355
392
  role: 'worker',
356
393
  requested: { model: 'astra', effort: 'low' },
@@ -32,8 +32,11 @@ example, change the `terra` and `medium` entry in `replacements` to:
32
32
 
33
33
  The next routing call reads the file again. The resolver source stays unchanged.
34
34
 
35
- To keep a pair out of selector panels, add it to `selectorExclusions`. This
36
- does not block the shared resolver or the spawn hook. For example:
35
+ Selector exclusions keep a pair out of panels. They do not change direct route
36
+ decisions. The shipped policy keeps Sol Medium out of panels and routes direct
37
+ requests for Sol Medium to Luna Max through `replacements`. Sol Medium is absent
38
+ from `approvedPairs`. The selector drops the requested pair before routing it,
39
+ so its Luna replacement does not enter the panel through that preference.
37
40
 
38
41
  ```json
39
42
  "selectorExclusions": [
@@ -41,9 +44,25 @@ does not block the shared resolver or the spawn hook. For example:
41
44
  ]
42
45
  ```
43
46
 
47
+ The matching replacement is:
48
+
49
+ ```json
50
+ {
51
+ "requested": {"model": "sol", "effort": "medium"},
52
+ "selected": {"model": "luna", "effort": "max"}
53
+ }
54
+ ```
55
+
44
56
  Unknown values, invalid policy data, and unavailable replacement models stop the
45
57
  spawn with a short diagnostic. Parent inheritance stays unchanged.
46
58
 
59
+ The direct spawn hook and advisor bridge use the same shared resolver. A Sol
60
+ Medium remap changes only the model and effort fields in the hook's tool input.
61
+ The hook returns an allow decision with the updated input and emits no remap
62
+ notice. Luna High, Xhigh, and Max and Astra Low and Medium keep their routes.
63
+ Trusted advisors also keep Astra High. A worker request for Sol without an
64
+ effort stops because Sol has no approved pair to supply a default effort.
65
+
47
66
  The hook blocks advisor role claims because the current Codex hook input has no
48
67
  host-provided role binding. The Python advisor bridge passes trusted session
49
68
  metadata to the shared resolver. The native advisor hook path stays pending
@@ -1,5 +1,5 @@
1
1
  import assert from 'node:assert/strict';
2
- import { execFileSync } from 'node:child_process';
2
+ import { execFileSync, spawnSync } from 'node:child_process';
3
3
  import { test } from 'node:test';
4
4
  import { fileURLToPath } from 'node:url';
5
5
 
@@ -11,22 +11,22 @@ function buildHookPayload(toolInput, metadata = {}) {
11
11
  return { tool_name: targetToolName, tool_input: toolInput, ...metadata };
12
12
  }
13
13
 
14
- test('remaps Terra Medium to Luna Xhigh', () => {
14
+ test('remaps Sol Medium to Luna Max', () => {
15
15
  const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
16
- model: 'Terra',
16
+ model: 'Sol',
17
17
  reasoning_effort: 'Medium',
18
18
  }));
19
19
 
20
20
  assert.equal(hookResponse.hookSpecificOutput.updatedInput.model, 'gpt-5.6-luna');
21
21
  assert.equal(
22
22
  hookResponse.hookSpecificOutput.updatedInput.reasoning_effort,
23
- 'xhigh',
23
+ 'max',
24
24
  );
25
25
  });
26
26
 
27
27
  test('emits only the allow response shape after a remap', () => {
28
28
  const hookResponse = buildSubagentModelRoutingResponse(buildHookPayload({
29
- model: 'Terra',
29
+ model: 'Sol',
30
30
  reasoning_effort: 'medium',
31
31
  }));
32
32
 
@@ -41,7 +41,7 @@ test('emits only the allow response shape after a remap', () => {
41
41
  permissionDecision: 'allow',
42
42
  updatedInput: {
43
43
  model: 'gpt-5.6-luna',
44
- reasoning_effort: 'xhigh',
44
+ reasoning_effort: 'max',
45
45
  },
46
46
  });
47
47
  });
@@ -52,7 +52,7 @@ test('preserves every non-routing tool field', () => {
52
52
  message: 'do the work',
53
53
  child_permissions: { shell: 'ask' },
54
54
  output_contract: ['summary'],
55
- model: 'Terra',
55
+ model: 'Sol',
56
56
  reasoning_effort: 'medium',
57
57
  };
58
58
 
@@ -64,14 +64,14 @@ test('preserves every non-routing tool field', () => {
64
64
  child_permissions: { shell: 'ask' },
65
65
  output_contract: ['summary'],
66
66
  model: 'gpt-5.6-luna',
67
- reasoning_effort: 'xhigh',
67
+ reasoning_effort: 'max',
68
68
  });
69
69
  assert.deepEqual(toolInput, {
70
70
  assignment: 'preserve',
71
71
  message: 'do the work',
72
72
  child_permissions: { shell: 'ask' },
73
73
  output_contract: ['summary'],
74
- model: 'Terra',
74
+ model: 'Sol',
75
75
  reasoning_effort: 'medium',
76
76
  });
77
77
  });
@@ -94,8 +94,8 @@ test('blocks malformed policy, unknown input, unavailable replacement, and untru
94
94
  buildHookPayload({ model: 'Unknown', reasoning_effort: 'medium' }),
95
95
  );
96
96
  const unavailableReplacementResponse = buildSubagentModelRoutingResponse(
97
- buildHookPayload({ model: 'Terra', reasoning_effort: 'medium' }),
98
- { availableModelIds: ['gpt-5.6-terra'] },
97
+ buildHookPayload({ model: 'Sol', reasoning_effort: 'medium' }),
98
+ { availableModelIds: ['gpt-5.6-sol'] },
99
99
  );
100
100
  const untrustedAdvisorResponse = buildSubagentModelRoutingResponse(
101
101
  buildHookPayload({
@@ -122,25 +122,62 @@ test('blocks malformed policy, unknown input, unavailable replacement, and untru
122
122
  }
123
123
  });
124
124
 
125
- test('allows a registered session advisor with trusted session metadata', () => {
126
- const hookResponse = buildSubagentModelRoutingResponse(
127
- buildHookPayload({
125
+ for (const [eachModel, eachEffort, eachSelectedModel, eachSelectedEffort] of [
126
+ ['Sol', 'medium', 'gpt-5.6-luna', 'max'],
127
+ ['Astra', 'high', 'gpt-6-astra', 'high'],
128
+ ]) {
129
+ test(`allows a trusted session advisor requesting ${eachModel} ${eachEffort}`, () => {
130
+ const hookResponse = buildSubagentModelRoutingResponse(
131
+ buildHookPayload({
132
+ agent_type: 'session-advisor',
133
+ model: eachModel,
134
+ reasoning_effort: eachEffort,
135
+ }),
136
+ {
137
+ trustedSessionMetadata: {
138
+ authorized: true,
139
+ registeredAgentType: 'session-advisor',
140
+ },
141
+ },
142
+ );
143
+
144
+ assert.deepEqual(hookResponse.hookSpecificOutput.updatedInput, {
128
145
  agent_type: 'session-advisor',
129
- model: 'Astra',
130
- reasoning_effort: 'high',
131
- }),
146
+ model: eachSelectedModel,
147
+ reasoning_effort: eachSelectedEffort,
148
+ });
149
+ });
150
+ }
151
+
152
+ test('the standalone hook silently remaps native Sol Medium and preserves other fields', () => {
153
+ const hookExecution = spawnSync(
154
+ process.execPath,
155
+ [fileURLToPath(new URL('./subagent_model_routing.mjs', import.meta.url))],
132
156
  {
133
- trustedSessionMetadata: {
134
- authorized: true,
135
- registeredAgentType: 'session-advisor',
136
- },
157
+ encoding: 'utf8',
158
+ input: JSON.stringify(buildHookPayload({
159
+ model: 'gpt-5.6-sol',
160
+ effort: 'medium',
161
+ message: 'keep',
162
+ child_permissions: { shell: 'ask' },
163
+ output_contract: ['summary'],
164
+ })),
137
165
  },
138
166
  );
139
-
140
- assert.deepEqual(hookResponse.hookSpecificOutput.updatedInput, {
141
- agent_type: 'session-advisor',
142
- model: 'gpt-6-astra',
143
- reasoning_effort: 'high',
167
+ assert.equal(hookExecution.status, 0);
168
+ assert.equal(hookExecution.stderr, '');
169
+ assert.deepEqual(JSON.parse(hookExecution.stdout), {
170
+ hookSpecificOutput: {
171
+ hookEventName: 'PreToolUse',
172
+ permissionDecision: 'allow',
173
+ updatedInput: {
174
+ model: 'gpt-5.6-luna',
175
+ effort: 'max',
176
+ message: 'keep',
177
+ child_permissions: { shell: 'ask' },
178
+ output_contract: ['summary'],
179
+ },
180
+ },
144
181
  });
145
182
  });
146
183
 
@@ -201,7 +238,7 @@ test('the standalone hook command blocks an advisor without host metadata', () =
201
238
 
202
239
  test('keeps a second routing pass unchanged', () => {
203
240
  const firstResponse = buildSubagentModelRoutingResponse(buildHookPayload({
204
- model: 'Terra',
241
+ model: 'Sol',
205
242
  reasoning_effort: 'medium',
206
243
  message: 'keep',
207
244
  }));
@@ -212,7 +249,7 @@ test('keeps a second routing pass unchanged', () => {
212
249
  assert.deepEqual(secondResponse, {});
213
250
  assert.deepEqual(firstResponse.hookSpecificOutput.updatedInput, {
214
251
  model: 'gpt-5.6-luna',
215
- reasoning_effort: 'xhigh',
252
+ reasoning_effort: 'max',
216
253
  message: 'keep',
217
254
  });
218
255
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "7.7.0",
3
+ "version": "7.7.1",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,7 +53,6 @@
53
53
  {"model": "luna", "effort": "high"},
54
54
  {"model": "luna", "effort": "xhigh"},
55
55
  {"model": "luna", "effort": "max"},
56
- {"model": "sol", "effort": "medium"},
57
56
  {"model": "astra", "effort": "low"},
58
57
  {"model": "astra", "effort": "medium"},
59
58
  {"model": "astra", "effort": "high", "roles": ["advisor"]}
@@ -66,6 +65,7 @@
66
65
  {"requested": {"model": "luna", "effort": "low"}, "selected": {"model": "luna", "effort": "high"}},
67
66
  {"requested": {"model": "luna", "effort": "medium"}, "selected": {"model": "luna", "effort": "high"}},
68
67
  {"requested": {"model": "sol", "effort": "low"}, "selected": {"model": "luna", "effort": "xhigh"}},
68
+ {"requested": {"model": "sol", "effort": "medium"}, "selected": {"model": "luna", "effort": "max"}},
69
69
  {"requested": {"model": "terra", "effort": "low"}, "selected": {"model": "luna", "effort": "xhigh"}},
70
70
  {"requested": {"model": "terra", "effort": "medium"}, "selected": {"model": "luna", "effort": "xhigh"}},
71
71
  {"requested": {"model": "terra", "effort": "high"}, "selected": {"model": "luna", "effort": "xhigh"}},