openmerit 0.1.7 → 0.1.8

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.8 — 2026-10-24
4
+
5
+ - Install an owned startup rule requiring Scout before explicit or implicit application-model choices, including pretrained/local/browser models, weight downloads and SDK defaults. Keep detailed access-policy and search instructions in the skill. These instructions are guidance, not hard enforcement.
6
+ - Upgrade unchanged skill-only installations without another device approval; include rule ownership, conflict detection, interrupted-update recovery, status, diagnostics and safe removal.
7
+ - Require a new Claude Code session after setup or upgrade; reconnecting `/mcp` alone does not reload startup instructions. Pause model selection when Scout is unavailable or lacks suitable candidates rather than falling back to a remembered model.
8
+
3
9
  ## 0.1.7 — 2026-10-24
4
10
 
5
11
  - Release coding-agent device sign-in, Claude Code MCP/Scout skill installation, status, diagnostics, removal and project endpoint policy on the stable `latest` channel. Setup requires browser approval; it never requests a pasted key.
@@ -7,7 +7,7 @@ import { promisify } from 'node:util';
7
7
  import { openBrowser } from './browser.mjs';
8
8
 
9
9
  // One-command coding-agent setup: device sign-in in the browser, then the MCP
10
- // server and Scout skill. Built to be run by a person or by a coding agent:
10
+ // server, startup rule and Scout skill. Built to be run by a person or by a coding agent:
11
11
  // no interactive prompts, bounded waits, resumable logins and explicit exit codes.
12
12
  export const exitCodes = { ok: 0, error: 1, pending: 10, denied: 11, conflict: 20, unreachable: 30 };
13
13
  const exec = promisify(execFile);
@@ -15,18 +15,45 @@ const hash = value => createHash('sha256').update(value).digest('hex');
15
15
  const policyPath = '.openmerit/scout.json';
16
16
  const clients = { 'claude-code': 'Claude Code' };
17
17
 
18
+ // No paths frontmatter: Claude loads this before the first model decision,
19
+ // including user-scope sessions in Desktop's automatically created folders.
20
+ export const rule = `# Application model decisions · OpenMerit Scout
21
+
22
+ Before choosing, recommending, adding, downloading, replacing or configuring
23
+ ANY application inference model, load the \`openmerit-scout\` skill and consult
24
+ \`scout_models\` on the \`openmerit\` MCP server before committing to a model.
25
+ This applies even when the user only asks you to build a feature, never mentions
26
+ models or Scout, or names a model themselves (assess it; do not silently replace it).
27
+
28
+ A pretrained model, local weights, browser inference, an SDK's default model,
29
+ embeddings, OCR, speech, image classification, detection and segmentation all
30
+ count as model choices, not implementation details. Identify this decision before
31
+ writing model-specific code, installing a model package or downloading weights.
32
+
33
+ Follow the skill's project access policy. If it is missing, ask and save it first.
34
+ If Scout is unavailable, fails or has no suitable evidence/candidate, disclose the
35
+ gap and pause the model decision; do not fall back to a remembered or SDK-default
36
+ model. You may continue work that does not commit to a model.
37
+
38
+ Do not consult Scout for unrelated edits, normal inference calls using an unchanged
39
+ approved model, or this coding agent's own model. Do not reselect approved models
40
+ unless the task introduces or changes a model decision.
41
+ `;
42
+
18
43
  export const skill = `---
19
44
  name: openmerit-scout
20
- description: Use when choosing, adding, replacing or configuring an application inference model (an LLM or other model the user's application calls), for example when building extraction, classification, generation or agent features. Consults OpenMerit Scout for an evidence-based shortlist. Not for unrelated edits, ordinary inference calls or this coding agent's own model.
45
+ description: Consults OpenMerit Scout before choosing, adding, downloading, replacing or configuring any application inference model, including implicit choices while building features, pretrained browser/local models, SDK defaults, embeddings, OCR, speech, classification, detection, segmentation, LLMs and agents. Not for unrelated edits, ordinary inference calls or this coding agent's own model.
21
46
  ---
22
47
 
23
48
  # OpenMerit · Scout
24
49
 
25
50
  Before introducing, recommending, replacing or configuring an application
26
51
  inference model, call the \`scout_models\` tool on the \`openmerit\` MCP server.
27
- This applies even when the user never mentions Scout. Preserve the user's explicit
28
- model choice; assess it rather than silently replacing it. Leave unchanged,
29
- approved application selections in place.
52
+ This applies even when the user never mentions models or Scout. Downloading local
53
+ weights, using a pretrained browser model or accepting an SDK default is a model
54
+ decision: consult Scout before model-specific code, installs or downloads.
55
+ Preserve the user's explicit model choice; assess it rather than silently replacing
56
+ it. Leave unchanged, approved application selections in place.
30
57
 
31
58
  ## Project policy
32
59
 
@@ -64,8 +91,10 @@ writes. Report recommendation, endpoint connectivity and application checks as
64
91
  separate results, each only after it succeeds. Do not switch this coding agent's
65
92
  own model.
66
93
 
67
- If Scout fails, say so and pause the model decision; do not substitute a
68
- remembered recommendation. If it reports that the device key is missing,
94
+ If Scout fails or has no suitable candidate/evidence for the required deployment,
95
+ say so and pause the model decision; do not substitute a remembered recommendation
96
+ or SDK default. Continue only model-independent work. Local/browser models are not
97
+ exempt when the catalogue lacks coverage. If it reports that the device key is missing,
69
98
  expired or revoked, run \`npx -y openmerit@latest login\` and then
70
99
  \`npx -y openmerit@latest install\` again. Never ask the user to paste a key.
71
100
  `;
@@ -81,7 +110,7 @@ export const help = `OpenMerit · coding-agent setup
81
110
  openmerit doctor [--json]
82
111
 
83
112
  install signs this device in through the browser (one Approve click), adds the
84
- OpenMerit MCP server and the Scout skill, then verifies the connection.
113
+ OpenMerit MCP server, startup rule and Scout skill, then verifies the connection.
85
114
  It opens the approval page and waits up to 9 minutes for the Approve click
86
115
  (30 seconds when no browser could be opened). If it exits with approval
87
116
  pending, rerun the same command: it resumes the same request.
@@ -134,12 +163,13 @@ function detectClient() {
134
163
  }
135
164
 
136
165
  // Claude Code locations. Project scope keeps the key out of the repository:
137
- // the MCP entry uses Claude's private "local" scope, only the skill is in the project.
166
+ // the MCP entry uses Claude's private "local" scope; rule and skill are in the project.
138
167
  function claudePaths(root, scope) {
139
168
  const config = resolve(process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'));
140
169
  const base = scope === 'user' ? config : join(root, '.claude');
141
170
  return {
142
171
  skill: join(base, 'skills/openmerit-scout/SKILL.md'),
172
+ rule: join(base, 'rules/openmerit-scout.md'),
143
173
  legacyRule: join(base, 'rules/openmerit.md'),
144
174
  record: join(base, 'openmerit-install.json'),
145
175
  mcpFile: process.env.CLAUDE_CONFIG_DIR ? join(config, '.claude.json') : join(homedir(), '.claude.json'),
@@ -164,16 +194,17 @@ async function claude(args, root, key) {
164
194
  // Inspect the current Claude Code installation without changing anything.
165
195
  async function inspect(root, scope) {
166
196
  const paths = claudePaths(root, scope);
167
- const [record, config, installedSkill, legacyRule] = await Promise.all([object(paths.record), object(paths.mcpFile), optional(paths.skill), optional(paths.legacyRule)]);
168
- const owned = record.version === 2 && record.scope === scope;
197
+ const [record, config, installedSkill, installedRule, legacyRule] = await Promise.all([object(paths.record), object(paths.mcpFile), optional(paths.skill), optional(paths.rule), optional(paths.legacyRule)]);
198
+ const owned = [2, 3].includes(record.version) && record.scope === scope;
169
199
  const current = configuredServer(config, root, scope);
170
200
  const otherScope = configuredServer(config, root, scope === 'user' ? 'project' : 'user');
171
201
  const projectFile = await object(join(root, '.mcp.json'));
172
202
  return {
173
- paths, record, owned, current, installedSkill,
203
+ paths, record, owned, current, installedSkill, installedRule,
174
204
  // A pending header hash covers an install interrupted between add and record.
175
205
  serverOwned: !current || owned && (serverHash(current) === record.serverHash || !!record.pendingAuth && hash(current.headers?.Authorization ?? '') === record.pendingAuth),
176
206
  skillOwned: installedSkill === null || owned && [record.skillHash, record.pendingSkill].includes(hash(installedSkill)),
207
+ ruleOwned: installedRule === null || owned && record.version === 3 && [record.ruleHash, record.pendingRule].includes(hash(installedRule)),
177
208
  // The pre-release installer recorded its rule hash; only an unchanged rule is removed.
178
209
  legacyRule: legacyRule !== null && record.version === 1 && hash(legacyRule) === record.ruleHash ? 'remove' : legacyRule !== null ? 'keep' : 'none',
179
210
  shadowed: !!(otherScope || projectFile.mcpServers?.openmerit),
@@ -184,6 +215,7 @@ function conflicts(state) {
184
215
  const found = [];
185
216
  if (!state.serverOwned) found.push('Claude Code already has an MCP server named "openmerit" that this installer did not create or that was edited. Remove it with "claude mcp remove openmerit", then retry.');
186
217
  if (!state.skillOwned) found.push(`${state.paths.skill} exists and was not created by this installer or was edited. Move or delete it, then retry.`);
218
+ if (!state.ruleOwned) found.push(`${state.paths.rule} exists and was not created by this installer or was edited. Move or delete it, then retry.`);
187
219
  if (state.shadowed) found.push('An "openmerit" MCP server exists in another Claude Code scope (or in .mcp.json). Remove it, then retry.');
188
220
  return found;
189
221
  }
@@ -251,6 +283,7 @@ async function install(root, { client, scope, plan, open, log, wait }) {
251
283
  const changes = [
252
284
  `${state.current ? 'Update' : 'Add'} MCP server "openmerit" in Claude Code (${state.paths.mcpScope} scope, ${state.paths.mcpFile})`,
253
285
  `${state.installedSkill === null ? 'Create' : 'Update'} ${state.paths.skill}`,
286
+ `${state.installedRule === null ? 'Create' : 'Update'} ${state.paths.rule} (loaded at session startup)`,
254
287
  ...(state.legacyRule === 'remove' ? [`Remove the unchanged earlier instructions at ${state.paths.legacyRule}`] : []),
255
288
  ...(await credentials() ? [] : [`Sign this device in through the browser and store its key in ${credentialsPath()} (mode 600)`]),
256
289
  ];
@@ -263,31 +296,33 @@ async function install(root, { client, scope, plan, open, log, wait }) {
263
296
  key ??= await login({ client, scope, open, log, wait });
264
297
  const next = serverConfig(key);
265
298
  // Record intent first so an interrupted install is repaired by rerunning.
266
- await save(state.paths.record, { version: 2, scope, client, serverHash: serverHash(state.current), skillHash: state.installedSkill === null ? null : hash(state.installedSkill), pendingAuth: hash(next.headers.Authorization), pendingSkill: hash(skill) });
299
+ await save(state.paths.record, { version: 3, scope, client, serverHash: serverHash(state.current), skillHash: state.installedSkill === null ? null : hash(state.installedSkill), ruleHash: state.installedRule === null ? null : hash(state.installedRule), pendingAuth: hash(next.headers.Authorization), pendingSkill: hash(skill), pendingRule: hash(rule) });
267
300
  if (state.current?.url !== next.url || state.current?.headers?.Authorization !== next.headers.Authorization) {
268
301
  if (state.current) await claude(['mcp', 'remove', '--scope', state.paths.mcpScope, 'openmerit'], root, key);
269
302
  await claude(['mcp', 'add-json', '--scope', state.paths.mcpScope, 'openmerit', JSON.stringify(next)], root, key);
270
303
  }
271
304
  await save(state.paths.skill, skill);
305
+ await save(state.paths.rule, rule);
272
306
  if (state.legacyRule === 'remove') await rm(state.paths.legacyRule, { force: true });
273
307
  // Record what Claude actually wrote, so later edits are detected precisely.
274
308
  const written = configuredServer(await object(state.paths.mcpFile), root, scope);
275
309
  if (!written) throw new Stop('Claude Code did not save the "openmerit" MCP server.', exitCodes.error, 'Run "openmerit doctor".');
276
- await save(state.paths.record, { version: 2, scope, client, serverHash: serverHash(written), skillHash: hash(skill), installedAt: new Date().toISOString() });
310
+ await save(state.paths.record, { version: 3, scope, client, serverHash: serverHash(written), skillHash: hash(skill), ruleHash: hash(rule), installedAt: new Date().toISOString() });
277
311
  const connection = await verify(key);
278
312
  if (connection === 'key_rejected') throw new Stop('OpenMerit rejected this device key (expired or revoked).', exitCodes.denied, 'Run "openmerit login", then rerun install.');
279
313
  return { action: 'install', applied: true, client, scope, changes, connection,
280
314
  next: connection === 'connected'
281
- ? `Scout is ready in new ${clients[client]} sessions. In a session that is already open, run /mcp and reconnect "openmerit".`
315
+ ? `Scout is ready in new ${clients[client]} sessions. Start a new session to load the startup rule and updated skill; reconnecting /mcp alone does not reload instructions.`
282
316
  : 'Installed, but the connection check failed. Run "openmerit doctor".' };
283
317
  }
284
318
 
285
319
  async function status(root, { client, scope, check }) {
286
320
  const state = await inspect(root, scope), saved = await credentials();
287
- const installed = state.owned && !!state.current && state.serverOwned && state.installedSkill !== null && state.skillOwned && !state.shadowed;
321
+ const installed = state.owned && !!state.current && state.serverOwned && state.installedSkill !== null && state.skillOwned && state.installedRule !== null && state.ruleOwned && !state.shadowed;
288
322
  return { action: 'status', client, scope, installed, signedIn: !!saved,
289
323
  server: state.current ? state.serverOwned ? 'matches' : 'modified_or_unowned' : 'missing',
290
324
  skill: state.installedSkill === null ? 'missing' : state.skillOwned ? 'matches' : 'modified_or_unowned',
325
+ rule: state.installedRule === null ? 'missing' : state.ruleOwned ? 'matches' : 'modified_or_unowned',
291
326
  conflicts: conflicts(state), connection: check && saved ? await verify(saved.key) : 'not_checked' };
292
327
  }
293
328
 
@@ -303,11 +338,13 @@ async function remove(root, { client, scope, plan }) {
303
338
  const state = await inspect(root, scope);
304
339
  if (!state.owned) return { action: 'remove', applied: false, client, scope, changes: [], note: 'No OpenMerit installation by this installer in this scope.' };
305
340
  const changes = [...(state.current && state.serverOwned ? [`Remove MCP server "openmerit" (${state.paths.mcpScope} scope)`] : []),
306
- ...(state.installedSkill !== null && state.skillOwned ? [`Delete ${state.paths.skill}`] : []), 'Revoke and delete this device key'];
307
- const kept = [...(state.serverOwned ? [] : ['the edited "openmerit" MCP server']), ...(state.skillOwned ? [] : [state.paths.skill]), policyPath];
341
+ ...(state.installedSkill !== null && state.skillOwned ? [`Delete ${state.paths.skill}`] : []),
342
+ ...(state.installedRule !== null && state.ruleOwned ? [`Delete ${state.paths.rule}`] : []), 'Revoke and delete this device key'];
343
+ const kept = [...(state.serverOwned ? [] : ['the edited "openmerit" MCP server']), ...(state.skillOwned ? [] : [state.paths.skill]), ...(state.ruleOwned ? [] : [state.paths.rule]), policyPath];
308
344
  if (plan) return { action: 'remove', applied: false, client, scope, changes, keeps: kept };
309
345
  if (state.current && state.serverOwned) await claude(['mcp', 'remove', '--scope', state.paths.mcpScope, 'openmerit'], root);
310
346
  if (state.installedSkill !== null && state.skillOwned) await rm(dirname(state.paths.skill), { recursive: true, force: true });
347
+ if (state.installedRule !== null && state.ruleOwned) await rm(state.paths.rule, { force: true });
311
348
  await rm(state.paths.record, { force: true });
312
349
  const revoked = await revokeKey();
313
350
  return { action: 'remove', applied: true, client, scope, changes, keeps: kept, keyRevoked: revoked };
@@ -335,13 +372,13 @@ async function doctor(root, { scope }) {
335
372
  add('key', !!saved, saved ? `Device key ${saved.keyId ?? ''} stored in ${credentialsPath()}` : 'Not signed in. Run "openmerit install" or "openmerit login".');
336
373
  if (saved && reach.status === 200) { const connection = await verify(saved.key); add('mcp', connection === 'connected', `MCP check: ${connection}`); }
337
374
  const state = await inspect(root, scope).catch(() => null);
338
- if (state) add('install', state.owned && !conflicts(state).length, state.owned ? conflicts(state).join(' ') || `Installed (${scope} scope)` : `Not installed in ${scope} scope`);
375
+ if (state) add('install', state.owned && !!state.current && state.installedSkill !== null && state.installedRule !== null && !conflicts(state).length, state.owned ? conflicts(state).join(' ') || (state.current && state.installedSkill !== null && state.installedRule !== null ? `Installed (${scope} scope)` : 'Installation incomplete. Rerun "openmerit install".') : `Not installed in ${scope} scope`);
339
376
  return { action: 'doctor', ok: checks.every(check => check.ok), checks };
340
377
  }
341
378
 
342
379
  function render(result) {
343
380
  if (result.action === 'doctor') return result.checks.map(c => `${c.ok ? '✓' : '✗'} ${c.name}: ${c.detail}`).join('\n');
344
- if (result.action === 'status') return [`OpenMerit in ${clients[result.client]} (${result.scope} scope): ${result.installed ? 'installed' : 'not installed'}`, `Signed in: ${result.signedIn ? 'yes' : 'no'} · MCP server: ${result.server} · skill: ${result.skill} · connection: ${result.connection}`, ...result.conflicts].join('\n');
381
+ if (result.action === 'status') return [`OpenMerit in ${clients[result.client]} (${result.scope} scope): ${result.installed ? 'installed' : 'not installed'}`, `Signed in: ${result.signedIn ? 'yes' : 'no'} · MCP server: ${result.server} · skill: ${result.skill} · rule: ${result.rule} · connection: ${result.connection}`, ...result.conflicts].join('\n');
345
382
  if (result.action === 'project') return `${result.applied ? 'Saved' : 'Would save'} ${result.path}: ${result.policy.policy}, ${result.policy.endpointIds.length} endpoint ID(s). ${result.note}`;
346
383
  if (result.action === 'login') return result.note;
347
384
  if (result.action === 'logout') return result.note;
package/docs/scout.md CHANGED
@@ -396,7 +396,7 @@ The capability index lists each area with its benchmark-series and model counts.
396
396
 
397
397
  ## Connect a coding agent
398
398
 
399
- **Device sign-in setup — npm 0.1.7:** use the stable `latest` tag for the commands below. Hosted Scout serves `/connect` and `/scout/mcp` independently of npm. Claude Code is the only supported client; other agents are not yet supported. The older `0.1.7-preview.5` package does not contain these commands.
399
+ **Device sign-in setup — npm 0.1.8:** use the stable `latest` tag for the commands below. Version 0.1.8 adds the startup model-decision rule to the device setup introduced in 0.1.7. Hosted Scout serves `/connect` and `/scout/mcp` independently of npm. Claude Code is the only supported client; other agents are not yet supported. The older `0.1.7-preview.5` package does not contain these commands.
400
400
 
401
401
  Setup takes four steps, and you act in each one only once:
402
402
 
@@ -408,13 +408,13 @@ Setup takes four steps, and you act in each one only once:
408
408
  ```
409
409
 
410
410
  3. **Click Approve** on `https://openmerit.ai/connect`, which the command opens for you. Sign in first if needed. The page shows the client, the device name, the scope, an eight-character code that matches the command's output, and the only permission granted: Scout recommendations through MCP. Only approve a request you started yourself.
411
- 4. **Done.** The command has kept waiting, so nothing needs rerunning. It stores the device key, adds the `openmerit` MCP server with `claude mcp add-json`, writes the Scout skill and checks the connection with a real MCP `initialize` and tool discovery (not a search). The approval page switches to *Claude Code is connected* once the command has collected its key, and Claude Code reports the result. Scout is available in new Claude Code sessions; in a session that is already open, reconnect `openmerit` in `/mcp`.
411
+ 4. **Done.** The command has kept waiting, so nothing needs rerunning. It stores the device key, adds the `openmerit` MCP server with `claude mcp add-json`, writes the Scout skill and startup rule, and checks the connection with a real MCP `initialize` and tool discovery (not a search). The approval page switches to *Claude Code is connected* once the command has collected its key, and Claude Code reports the result. **Start a new Claude Code session** to load the startup instructions; reconnecting `openmerit` in `/mcp` alone is not enough.
412
412
 
413
413
  The prompt contains no key, account data or task draft. If the Claude desktop app isn't installed or your browser blocks the link, paste the copied prompt into Claude Code instead. The command needs `npx` (Node.js 22.19 or newer) and the `claude` CLI on the PATH of that session; the installer runs `claude mcp add-json`. Having the desktop app alone does not establish that those commands are available. See [Claude Desktop](https://code.claude.com/docs/en/desktop).
414
414
 
415
415
  When the command can open a browser, it waits up to nine minutes for the Approve click. When it can't, for example over SSH, it prints the link and code and returns after 30 seconds with exit code 10 so the agent can show them. Rerunning the same command resumes the same request; it does not issue a new code. Codes expire after ten minutes and can be used once. `OPENMERIT_LOGIN_WAIT` sets the wait in seconds.
416
416
 
417
- `--client` defaults to the agent running the command (Claude Code sets `CLAUDECODE=1`). `--scope` defaults to `user`, which makes Scout available in every project. `--scope project` writes the skill to `.claude/skills/` in the current project and stores the MCP server in Claude Code's private per-project (`local`) configuration, so the key never enters the repository. `--plan` lists the changes without making them. `--json` prints one result object for agents.
417
+ `--client` defaults to the agent running the command (Claude Code sets `CLAUDECODE=1`). `--scope` defaults to `user`, which makes Scout available in every project. `--scope project` writes the skill to `.claude/skills/` and startup rule to `.claude/rules/` in the current project and stores the MCP server in Claude Code's private per-project (`local`) configuration, so the key never enters the repository. `--plan` lists the changes without making them. `--json` prints one result object for agents.
418
418
 
419
419
  | Exit code | Meaning |
420
420
  |---|---|
@@ -431,6 +431,8 @@ When the command can open a browser, it waits up to nine minutes for the Approve
431
431
 
432
432
  Each approval creates one key for one device and client, limited to `scout:recommend`. It is accepted only on `/scout/mcp`; the JSON API and browser still require a Clerk session. The server stores only SHA-256 hashes of keys and device codes. A key expires after 90 days without use; each use extends it. The usual per-user rate limits and analytics preferences apply.
433
433
 
434
+ Missing or invalid MCP credentials return HTTP 401 with `device_key_required`. The recovery command is `npx -y openmerit@latest install`; the older preview package does not include the installer.
435
+
434
436
  On the device, the key is stored in `~/.config/openmerit/credentials.json` (mode 600; `OPENMERIT_CONFIG_DIR` or `XDG_CONFIG_HOME` override the location) and in Claude Code's MCP header configuration, which Claude Code stores in plain text. The CLI never prints the key and scrubs it from any failing `claude` output. Do not paste keys into a chat.
435
437
 
436
438
  Revoke devices under **Connected devices** on `/connect`. `openmerit remove` and `openmerit logout` also revoke this device's key.
@@ -447,14 +449,24 @@ openmerit project --policy existing-only|existing-first [--endpoint-id <id> ...]
447
449
  openmerit doctor # Node.js, claude, reachability, key, MCP check and install state
448
450
  ```
449
451
 
450
- Removal deletes the MCP server, the unchanged skill and the install record, and revokes the key. It keeps edited files and `.openmerit/scout.json`.
452
+ Removal deletes the MCP server, the unchanged skill and startup rule, and the install record, and revokes the key. It keeps edited files and `.openmerit/scout.json`.
451
453
 
452
454
  ### Using Scout from the agent
453
455
 
454
- The `openmerit-scout` skill loads when an application inference model is being chosen, even if you do not mention Scout. It is not loaded for unrelated edits, ordinary inference calls or the agent's own model. On first use in a project, it asks once whether the project is **existing-only** or **existing-first** and which public catalogue endpoint IDs it can reach, then saves them with `openmerit project`. Every search passes the saved `endpointIds`, including `[]`. Existing-only never broadens; existing-first asks before an unscoped search. Task descriptions go to Scout and OpenAI; do not send source files, secrets or customer records.
456
+ Claude can load the `openmerit-scout` skill when choosing an application inference model, even if you do not mention Scout. The skill-only setup in npm 0.1.7 can miss implicit choices while building an app, including selecting pretrained browser models. Availability in the skill list does not prove invocation. Unrelated edits, ordinary inference calls and the coding agent's own model are outside its scope. On first use in a project, the skill instructs Claude to ask once whether the project is **existing-only** or **existing-first** and which public catalogue endpoint IDs it can reach, then save them with `openmerit project`. Every search passes the saved `endpointIds`, including `[]`. Existing-only never broadens; existing-first asks before an unscoped search. Task descriptions go to Scout and OpenAI; do not send source files, secrets or customer records.
455
457
 
456
458
  The agent compares model and endpoint, evidence, reference cost/time and unknowns, and helps configure the chosen route. New providers, credentials, spending and deployments need separate approval. A skill is a default, not enforcement. The MCP tool, `scout_models`, is read-only and does not execute models.
457
459
 
460
+ ### Startup model-decision rule — npm 0.1.8
461
+
462
+ The 0.1.8 installer adds `rules/openmerit-scout.md` alongside the skill. Unlike an on-demand skill, an unscoped [Claude Code rule](https://code.claude.com/docs/en/memory#organize-rules-with-claude/rules/) loads at session startup. The rule requires loading the skill and consulting Scout before any application-model choice, even when the user only asks for a feature. It explicitly includes pretrained/local/browser models, weight downloads, SDK defaults, embeddings, OCR, speech, classification, detection and segmentation. An explicit user model choice must be assessed, not silently replaced. Unchanged approved models and ordinary inference calls do not require another search.
463
+
464
+ Rerunning `npx -y openmerit@latest install --client claude-code` with version 0.1.8 or later upgrades an unchanged npm 0.1.7 installation without another device approval. User scope writes the rule under `~/.claude/rules/` (or `CLAUDE_CONFIG_DIR`) and covers new projects and Desktop's no-folder sessions. Project scope writes `.claude/rules/openmerit-scout.md` and applies only in that project. The existing skill, private MCP entry and key locations are unchanged. Start a **new Claude Code session** after installation or upgrade; reconnecting `/mcp` alone does not reload startup instructions.
465
+
466
+ The installer records rule ownership, repairs missing rules, resumes interrupted updates and refuses to overwrite edited or unowned rules. `--plan` includes the rule without writing it; `status` reports its state and `doctor` treats a missing rule as incomplete. `remove` deletes only the unchanged owned rule and retains edited rules and other user instructions.
467
+
468
+ If policy is missing, Claude must ask before searching. If Scout is unavailable or has no suitable evidence/candidate for the required deployment, the instructions require disclosing the gap and pausing model selection, not choosing from memory or an SDK default. Model-independent work may continue. Catalogue coverage is not guaranteed for local/browser models. These are behavioral instructions, **not hard enforcement**; Claude can still fail to follow them. Older 0.1.7 installations remain skill-only until upgraded; website deployment alone does not update an installed client.
469
+
458
470
  ## JSON API
459
471
 
460
472
  ```sh
package/docs/security.md CHANGED
@@ -57,6 +57,8 @@ The [coding-agent setup](scout.md#connect-a-coding-agent) in npm 0.1.7 uses host
57
57
 
58
58
  **Historical reset, superseded by npm 0.1.7 and hosted device setup.** The reset temporarily removed the installer and MCP transport. The current [Claude Code setup](scout.md#connect-a-coding-agent) uses browser-approved, device-specific keys scoped to MCP recommendations, not pasted session tokens or agent OAuth. The key is stored locally in mode-600 credentials and Claude Code's plain-text MCP configuration. Setup installs an owned Scout skill; conflicts are not overwritten, and removal revokes the device key. Browser Google/GitHub sign-in remains separate.
59
59
 
60
+ **npm 0.1.8:** setup also installs an owned, always-loaded `rules/openmerit-scout.md` instruction requiring Scout before implicit or explicit application-model choices, including pretrained/local/browser models and SDK defaults. Edited or unowned rules are not overwritten or removed. Missing policy requires asking; unavailable Scout or insufficient coverage requires pausing model selection. This is model guidance, not an enforced tool gate or authorization to download weights, add providers, spend or deploy. Scope, device-key permissions and task-data handling are unchanged. New sessions are required after upgrading; older npm 0.1.7 installations remain skill-only until upgraded.
61
+
60
62
  Scout serving, main-site documentation and npm publication are separate releases. This reset does not revoke remote grants or clean up files previously installed on other machines. Earlier hosted MCP/authentication descriptions in this guide refer to past deployments, not this build. Browser Google/GitHub sign-in and its Clerk account lifecycle are separate and unchanged.
61
63
 
62
64
  ## Hosted Scout analytics
package/docs/testing.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Testing and validation
2
2
 
3
+ ## Stable 0.1.7 acceptance — 24 October 2026
4
+
5
+ Published `openmerit@0.1.7` to npm's `latest` channel after full repository verification: 757 passing tests, one existing skip, syntax/type checks, builds and documentation checks. The `preview` tag remains `0.1.7-preview.5`. A fresh registry installation on macOS ARM64 with Node.js 22.23.1 matched the tested 104-file tarball byte-for-byte. Packed core/protocol/Pi exports and the installed terminal runtime smoke checks passed, including passive collection, source removal, runtime outage/recovery and shutdown.
6
+
7
+ The live Scout button opened a prefilled Claude Desktop Code session through Chrome. That session ran `npx -y openmerit@latest install --client claude-code`, opened the real device-approval page, and completed after approval of the matching code. This was a clean setup after removing and revoking this test's earlier source installation, not reuse of an existing key. Status then reported the MCP server and skill installed and connected; credentials had mode 600 and other MCP servers were preserved.
8
+
9
+ A fresh Desktop session received an application-model request that never mentioned Scout. It loaded `openmerit-scout` and called the real `scout_models` tool with the saved empty inventory, returning zero candidates. It asked before broadening; after separate approval, its second MCP call returned five candidates and it produced a provisional recommendation. No application code, providers, candidate inference or deployments were performed by that session. Anonymous MCP access and use of the device key on the JSON API both returned 401, while authenticated tool discovery succeeded.
10
+
11
+ These are integration observations, not a general guarantee that every agent follows the skill. Both search responses reported stale/degraded source data, including incomplete Parallel research. Their approximately 228,000- and 538,000-character payloads exceeded Desktop's inline tool limit; Claude recovered through saved tool-result files. The flow works, but these checks do not establish catalogue freshness, task accuracy or small response size. A separate recovery-path check exposed an obsolete preview install command in the MCP 401 message; its stable-command regression test failed before the source correction and passed afterward. Hosted correction is a separate deployment from the npm release.
12
+
3
13
  ## Published preview.5 acceptance — 17 October 2026
4
14
 
5
15
  Published `0.1.7-preview.5` to npm's `preview` channel after all **679 repository tests**, syntax/type checks, builds and documentation checks passed, including 336 Scout tests. Registry integrity matches the reviewed tarball and stable `latest` remains `0.1.6`. All 101 packaged files are accounted for; runtime bytes, public exports and legal metadata are unchanged from preview.4. Version metadata and public guides carry this release. The frozen package contains the preparation record; this post-publication result is maintained here after the registry and VM checks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmerit",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Evidence-driven model decisions for coding harnesses.",
5
5
  "type": "module",
6
6
  "bin": {