openmerit 0.3.1 → 0.3.3

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.3 — 2026-11-05
4
+
5
+ - Fix `openmerit install --client claude-code` exiting with `ENOENT … dist/terminal/src/updates.ts` (0.3.1 and 0.3.2). Setting up automatic Scout instruction updates copied the updater from its TypeScript source, which the package does not ship; it now copies the module that is running. The MCP server, skill and rule were already written, but automatic updates stayed off and the installer reported failure.
6
+ - `openmerit scout --help` and the installed Scout instructions describe the new optional `kind` field for reported results (`quality`, `usd` or `seconds`), so measured cost replaces Scout's estimate, and tell the agent to report each agent's own checks at the agent level because end-to-end results also measure the agents it did not swap. Terminal output shows measured cost per 1,000 agent runs or tasks. The matching Scout serving release is deployed.
7
+
8
+ ## 0.3.2 — 2026-11-05
9
+
10
+ - Fix Claude Code project-scope installation for a project inside a larger git repository, such as a monorepo package. Claude Code keeps its private MCP entry under the repository root, so the installer reported "Claude Code did not save the openmerit MCP server", `status` reported it missing and a rerun clashed with it. The installer now reads the entry where Claude Code keeps it and says that the MCP server applies to the whole repository; the skill and rule stay in the project.
11
+ - Make device approvals easier to relay. The installer prints the code's expiry in UTC with the minutes left instead of an unlabelled local time. When it returns with exit code 10, the message includes the approval link and expiry, `--json` adds `approvalUrl`, `userCode` and `expiresAt`, and the installed Scout instructions tell the agent to show the link and expiry to the person approving. The matching Scout serving release extends device codes from 10 to 30 minutes.
12
+
3
13
  ## 0.3.1 — 2026-11-05
4
14
 
5
15
  - `openmerit scout --conversation <id> --results <file.json>` reports aggregate results you measured with your own evals. Scout then ranks them above public benchmarks for your recommendations only. Terminal output shows each task's evaluation plan, its agent and calls per run, and marks quality that rests on measured results.
@@ -133,7 +133,11 @@ eval platform.
133
133
  Report what you measured with \`scout_models\`: the same \`conversationId\` and
134
134
  \`results\`, one aggregate per model and metric (\`level\`, \`task\` title, \`scope\`,
135
135
  \`model\` endpoint or model ID, \`metric\`, \`value\`, \`higherIsBetter\`,
136
- \`samples\`, \`source\`). Send aggregates only, never inputs, outputs or records.
136
+ \`samples\`, \`source\`). Set \`kind\` to \`usd\` or \`seconds\` for measured cost
137
+ or time (per call at the task level, per run at the agent level), so Scout uses
138
+ your cost instead of its estimate. Report each agent's own checks at the agent
139
+ level: an end-to-end result also measures the agents you did not swap. Send
140
+ aggregates only, never inputs, outputs or records.
137
141
  Scout ranks them above public benchmarks for this user only; models whose
138
142
  \`quality.measured\` is true rest on them.
139
143
 
@@ -147,7 +151,9 @@ own model.
147
151
  If Scout fails or has no suitable candidate/evidence for the required deployment,
148
152
  say so and pause the model decision; do not substitute a remembered recommendation
149
153
  or SDK default. Continue only model-independent work. Local/browser models are not
150
- exempt when the catalogue lacks coverage. If it reports that the device key is missing,
154
+ exempt when the catalogue lacks coverage. When an install or login waits for approval (exit code
155
+ 10), show the user the approval link and its UTC expiry in your reply, not only in
156
+ command output, then rerun the same command once they approve. If it reports that the device key is missing,
151
157
  expired or revoked, run \`npx -y openmerit@latest login\` and then
152
158
  \`npx -y openmerit@latest install\` again. Never ask the user to paste a key.
153
159
  `;
@@ -1 +1 @@
1
- {"format":1,"contract":"scout-recommend-v1","version":"0.3.1","skill":"---\nname: openmerit-scout\ndescription: 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.\n---\n\n# OpenMerit · Scout\n\nBefore introducing, recommending, replacing or configuring an application\ninference model, call the `scout_models` tool on the `openmerit` MCP server.\nThis applies even when the user never mentions models or Scout. Downloading local\nweights, using a pretrained browser model or accepting an SDK default is a model\ndecision: consult Scout before model-specific code, installs or downloads.\nPreserve the user's explicit model choice; assess it rather than silently replacing\nit. Leave unchanged, approved application selections in place.\n\n## Project policy\n\nRead `.openmerit/scout.json` before searching. If it is absent, ask once whether this\nproject uses **existing-only** (only endpoints it can already reach) or\n**existing-first** (prefer them, ask before searching the whole catalogue), and\nwhich public catalogue endpoint IDs it can access. Save the answer:\n\n```sh\nnpx -y openmerit@latest project --policy existing-only --endpoint-id <id> ...\n```\n\nNever infer access from a model name, SDK or environment-variable name. Never\nrecord credentials, private URLs, prompts or customer data. An empty inventory\nis not permission to search all models.\n\n## Searching\n\nCall `scout_models` with a non-confidential task description as `prompt` and\n`endpointIds` set to the saved inventory, including `[]` when empty. Send\nonly task requirements, never source files, secrets or real customer records.\nIf the MCP tool is unavailable, run\n`npx -y openmerit@latest scout \"<task>\" --json`; it applies the saved inventory.\n\nScout is a conversation with the agent behind openmerit.ai/scout. When\n`status` is `needs_answers`, fill in what the user or project already states,\nshow the user Scout's brief and remaining questions, and wait for their answers.\nThen call again with the returned `conversationId` and `brief` set to the\nrewritten components, or `skip: true` if the user wants Scout to assume. Never\nanswer Scout's questions with guesses. Send later refinements to the same\n`conversationId`. A call with only a `conversationId` reads an interrupted turn.\n\nWhen `status` is `recommended`, Scout has split the work into `tasks`, one\nper model, each with up to five ranked `models`; cover every task. Each\nmodel's `cost` names the `endpointId` and `providerModelId` to call.\nExisting-only never broadens. For existing-first, if no existing candidate fits a\ntask, explain the gap and ask before a new conversation without `endpointIds`.\nCompare quality evidence, reference cost/time and watch-outs. Prefer the cheapest\nusable option the evidence supports, without claiming benchmarks prove\napplication accuracy. Use the project's actual SDK and protocol.\n\n## Measuring candidates\n\nEach task also has an `evaluation`: the checks (per model call, agent run or\nend-to-end outcome), labelled examples per candidate and when to stop a losing\ncandidate. Offer to run it before the user commits to a model. Use the evals the\nproject already has, such as datasets and experiments in its eval or tracing\nplatform, or its own eval scripts and fixtures: add the shortlisted candidates as\nextra runs of those evals. Do not build a second eval or copy their datasets.\nIf the project has no eval for the task, propose a minimal one built from its\nexisting traces and the decisions people already make, and create it only where\nand how the user agrees. Ask before spending on model calls or writing to an\neval platform.\n\nReport what you measured with `scout_models`: the same `conversationId` and\n`results`, one aggregate per model and metric (`level`, `task` title, `scope`,\n`model` endpoint or model ID, `metric`, `value`, `higherIsBetter`,\n`samples`, `source`). Send aggregates only, never inputs, outputs or records.\nScout ranks them above public benchmarks for this user only; models whose\n`quality.measured` is true rest on them.\n\n## Boundaries\n\nAsk before adding providers or credentials, spending, deploying or external\nwrites. Report recommendation, endpoint connectivity and application checks as\nseparate results, each only after it succeeds. Do not switch this coding agent's\nown model.\n\nIf Scout fails or has no suitable candidate/evidence for the required deployment,\nsay so and pause the model decision; do not substitute a remembered recommendation\nor SDK default. Continue only model-independent work. Local/browser models are not\nexempt when the catalogue lacks coverage. If it reports that the device key is missing,\nexpired or revoked, run `npx -y openmerit@latest login` and then\n`npx -y openmerit@latest install` again. Never ask the user to paste a key.\n","rule":"# Application model decisions · OpenMerit Scout\n\nBefore choosing, recommending, adding, downloading, replacing or configuring\nANY application inference model, load the `openmerit-scout` skill and consult\n`scout_models` on the `openmerit` MCP server before committing to a model.\nThis applies even when the user only asks you to build a feature, never mentions\nmodels or Scout, or names a model themselves (assess it; do not silently replace it).\n\nA pretrained model, local weights, browser inference, an SDK's default model,\nembeddings, OCR, speech, image classification, detection and segmentation all\ncount as model choices, not implementation details. Identify this decision before\nwriting model-specific code, installing a model package or downloading weights.\n\nFollow the skill's project access policy. If it is missing, ask and save it first.\nScout may answer with clarifying questions: relay them to the user and continue\nthe same Scout conversation with their answers before choosing.\nIf Scout is unavailable, fails or has no suitable evidence/candidate, disclose the\ngap and pause the model decision; do not fall back to a remembered or SDK-default\nmodel. You may continue work that does not commit to a model.\n\nDo not consult Scout for unrelated edits, normal inference calls using an unchanged\napproved model, or this coding agent's own model. Do not reselect approved models\nunless the task introduces or changes a model decision.\n\nWhen the user says \"update openmerit\", run\n`npx -y openmerit@latest update --scope user` (use `--scope project` if this\nrule is in the project's .claude/rules directory). Report the result, including\nany conflict or manual-upgrade requirement. Do not delete edited files, bypass\npermissions or rerun login automatically. A new session loads updated instructions.\n"}
1
+ {"format":1,"contract":"scout-recommend-v1","version":"0.3.3","skill":"---\nname: openmerit-scout\ndescription: 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.\n---\n\n# OpenMerit · Scout\n\nBefore introducing, recommending, replacing or configuring an application\ninference model, call the `scout_models` tool on the `openmerit` MCP server.\nThis applies even when the user never mentions models or Scout. Downloading local\nweights, using a pretrained browser model or accepting an SDK default is a model\ndecision: consult Scout before model-specific code, installs or downloads.\nPreserve the user's explicit model choice; assess it rather than silently replacing\nit. Leave unchanged, approved application selections in place.\n\n## Project policy\n\nRead `.openmerit/scout.json` before searching. If it is absent, ask once whether this\nproject uses **existing-only** (only endpoints it can already reach) or\n**existing-first** (prefer them, ask before searching the whole catalogue), and\nwhich public catalogue endpoint IDs it can access. Save the answer:\n\n```sh\nnpx -y openmerit@latest project --policy existing-only --endpoint-id <id> ...\n```\n\nNever infer access from a model name, SDK or environment-variable name. Never\nrecord credentials, private URLs, prompts or customer data. An empty inventory\nis not permission to search all models.\n\n## Searching\n\nCall `scout_models` with a non-confidential task description as `prompt` and\n`endpointIds` set to the saved inventory, including `[]` when empty. Send\nonly task requirements, never source files, secrets or real customer records.\nIf the MCP tool is unavailable, run\n`npx -y openmerit@latest scout \"<task>\" --json`; it applies the saved inventory.\n\nScout is a conversation with the agent behind openmerit.ai/scout. When\n`status` is `needs_answers`, fill in what the user or project already states,\nshow the user Scout's brief and remaining questions, and wait for their answers.\nThen call again with the returned `conversationId` and `brief` set to the\nrewritten components, or `skip: true` if the user wants Scout to assume. Never\nanswer Scout's questions with guesses. Send later refinements to the same\n`conversationId`. A call with only a `conversationId` reads an interrupted turn.\n\nWhen `status` is `recommended`, Scout has split the work into `tasks`, one\nper model, each with up to five ranked `models`; cover every task. Each\nmodel's `cost` names the `endpointId` and `providerModelId` to call.\nExisting-only never broadens. For existing-first, if no existing candidate fits a\ntask, explain the gap and ask before a new conversation without `endpointIds`.\nCompare quality evidence, reference cost/time and watch-outs. Prefer the cheapest\nusable option the evidence supports, without claiming benchmarks prove\napplication accuracy. Use the project's actual SDK and protocol.\n\n## Measuring candidates\n\nEach task also has an `evaluation`: the checks (per model call, agent run or\nend-to-end outcome), labelled examples per candidate and when to stop a losing\ncandidate. Offer to run it before the user commits to a model. Use the evals the\nproject already has, such as datasets and experiments in its eval or tracing\nplatform, or its own eval scripts and fixtures: add the shortlisted candidates as\nextra runs of those evals. Do not build a second eval or copy their datasets.\nIf the project has no eval for the task, propose a minimal one built from its\nexisting traces and the decisions people already make, and create it only where\nand how the user agrees. Ask before spending on model calls or writing to an\neval platform.\n\nReport what you measured with `scout_models`: the same `conversationId` and\n`results`, one aggregate per model and metric (`level`, `task` title, `scope`,\n`model` endpoint or model ID, `metric`, `value`, `higherIsBetter`,\n`samples`, `source`). Set `kind` to `usd` or `seconds` for measured cost\nor time (per call at the task level, per run at the agent level), so Scout uses\nyour cost instead of its estimate. Report each agent's own checks at the agent\nlevel: an end-to-end result also measures the agents you did not swap. Send\naggregates only, never inputs, outputs or records.\nScout ranks them above public benchmarks for this user only; models whose\n`quality.measured` is true rest on them.\n\n## Boundaries\n\nAsk before adding providers or credentials, spending, deploying or external\nwrites. Report recommendation, endpoint connectivity and application checks as\nseparate results, each only after it succeeds. Do not switch this coding agent's\nown model.\n\nIf Scout fails or has no suitable candidate/evidence for the required deployment,\nsay so and pause the model decision; do not substitute a remembered recommendation\nor SDK default. Continue only model-independent work. Local/browser models are not\nexempt when the catalogue lacks coverage. When an install or login waits for approval (exit code\n10), show the user the approval link and its UTC expiry in your reply, not only in\ncommand output, then rerun the same command once they approve. If it reports that the device key is missing,\nexpired or revoked, run `npx -y openmerit@latest login` and then\n`npx -y openmerit@latest install` again. Never ask the user to paste a key.\n","rule":"# Application model decisions · OpenMerit Scout\n\nBefore choosing, recommending, adding, downloading, replacing or configuring\nANY application inference model, load the `openmerit-scout` skill and consult\n`scout_models` on the `openmerit` MCP server before committing to a model.\nThis applies even when the user only asks you to build a feature, never mentions\nmodels or Scout, or names a model themselves (assess it; do not silently replace it).\n\nA pretrained model, local weights, browser inference, an SDK's default model,\nembeddings, OCR, speech, image classification, detection and segmentation all\ncount as model choices, not implementation details. Identify this decision before\nwriting model-specific code, installing a model package or downloading weights.\n\nFollow the skill's project access policy. If it is missing, ask and save it first.\nScout may answer with clarifying questions: relay them to the user and continue\nthe same Scout conversation with their answers before choosing.\nIf Scout is unavailable, fails or has no suitable evidence/candidate, disclose the\ngap and pause the model decision; do not fall back to a remembered or SDK-default\nmodel. You may continue work that does not commit to a model.\n\nDo not consult Scout for unrelated edits, normal inference calls using an unchanged\napproved model, or this coding agent's own model. Do not reselect approved models\nunless the task introduces or changes a model decision.\n\nWhen the user says \"update openmerit\", run\n`npx -y openmerit@latest update --scope user` (use `--scope project` if this\nrule is in the project's .claude/rules directory). Report the result, including\nany conflict or manual-upgrade requirement. Do not delete edited files, bypass\npermissions or rerun login automatically. A new session loads updated instructions.\n"}
@@ -50,7 +50,9 @@ Exit codes: 0 done, 10 approval pending, 11 denied or expired, 20 conflict,
50
50
  30 OpenMerit unreachable, 1 other error.
51
51
  `;
52
52
 
53
- class Stop extends Error { constructor(message, code, next) { super(message); this.exit = code; this.next = next; } }
53
+ class Stop extends Error { constructor(message, code, next, details) { super(message); this.exit = code; this.next = next; this.details = details; } }
54
+ // An approval deadline people in any timezone, and agents relaying it, read the same way.
55
+ const deadlineText = at => `${new Date(at).toISOString().slice(11, 16)} UTC, in ${Math.max(0, Math.round((at - Date.now()) / 60_000))} min`;
54
56
 
55
57
  const server = () => (process.env.OPENMERIT_URL ?? 'https://openmerit.ai').replace(/\/+$/, '');
56
58
  const credentialsPath = telemetry => deviceCredentialsPath(telemetry.properties.agent, telemetry.properties.scope, telemetry.root);
@@ -105,7 +107,13 @@ function claudePaths(root, scope) {
105
107
  mcpScope: scope === 'user' ? 'user' : 'local',
106
108
  };
107
109
  }
108
- const configuredServer = (config, root, scope) => scope === 'user' ? config.mcpServers?.openmerit : config.projects?.[root]?.mcpServers?.openmerit;
110
+ // Claude Code keys its private local scope by the project's git repository root, so a
111
+ // project in a subdirectory of a repository shares the entry of the whole repository.
112
+ async function claudeProject(root) {
113
+ try { return (await exec('git', ['rev-parse', '--show-toplevel'], { cwd: root, timeout: 10_000, windowsHide: true })).stdout.trim() || root; }
114
+ catch { return root; }
115
+ }
116
+ const configuredServer = (config, project, scope) => scope === 'user' ? config.mcpServers?.openmerit : config.projects?.[project]?.mcpServers?.openmerit;
109
117
  const serverConfig = (key, telemetry) => ({ type: 'http', url: `${server()}/scout/mcp`, headers: { Authorization: `Bearer ${key}`, ...setupHeaders(telemetry.properties, 'agent') } });
110
118
  const canonical = value => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map(k => [k, canonical(value[k])])) : value;
111
119
  const serverHash = value => value ? hash(JSON.stringify(canonical(value))) : null;
@@ -123,12 +131,12 @@ async function claude(args, root, key) {
123
131
  // Inspect the current Claude Code installation without changing anything.
124
132
  async function inspect(root, scope, client = 'claude-code') {
125
133
  if (client === 'pi') return { ...await inspectPi(root, scope), client };
126
- const paths = claudePaths(root, scope);
134
+ const paths = { ...claudePaths(root, scope), project: await claudeProject(root) };
127
135
  const [record, config, installedSkill, installedRule, legacyRule] = await Promise.all([object(paths.record), object(paths.mcpFile), optional(paths.skill), optional(paths.rule), optional(paths.legacyRule)]);
128
136
  const updates = await inspectUpdates(dirname(paths.record), scope, record);
129
137
  const owned = [2, 3].includes(record.version) && record.scope === scope;
130
- const current = configuredServer(config, root, scope);
131
- const otherScope = configuredServer(config, root, scope === 'user' ? 'project' : 'user');
138
+ const current = configuredServer(config, paths.project, scope);
139
+ const otherScope = configuredServer(config, paths.project, scope === 'user' ? 'project' : 'user');
132
140
  const projectFile = await object(join(root, '.mcp.json'));
133
141
  return {
134
142
  paths, record, owned, current, installedSkill, installedRule, updates, client,
@@ -189,7 +197,7 @@ async function login({ client, scope, open, log, wait, telemetry }) {
189
197
  if (process.env.OPENMERIT_ANALYTICS_ACCOUNT === 'off' || !telemetryEnabled(true)) approvalURL.searchParams.set('analytics_account', 'off');
190
198
  pending = { ...pending, url: approvalURL.href, setup_id: telemetry.properties.setup_id, installation_id: telemetry.properties.installation_id };
191
199
  await save(pendingPath(telemetry), pending);
192
- log(`Approve this device in your browser (opening it now):\n\n ${pending.url}\n\n Code: ${pending.userCode} (expires ${new Date(pending.expiresAt).toLocaleTimeString()})\n`);
200
+ log(`Approve this device in your browser (opening it now):\n\n ${pending.url}\n\n Code: ${pending.userCode} (expires ${deadlineText(pending.expiresAt)})\n`);
193
201
  await telemetry.step('browser_open', 'started');
194
202
  if (open && !pending.opened && !pending.openFailed) {
195
203
  // Only the approval page on the configured OpenMerit server may be opened.
@@ -226,7 +234,9 @@ async function login({ client, scope, open, log, wait, telemetry }) {
226
234
  if (data.error === 'access_denied') throw new Stop('The sign-in request was denied. Nothing was installed.', exitCodes.denied, 'Rerun the command to start a new request.');
227
235
  throw new Stop('The sign-in code expired or was already used. Nothing was installed.', exitCodes.denied, 'Rerun the command to get a new code.');
228
236
  }
229
- if (Date.now() + interval > deadline) throw new Stop(`Waiting for approval of code ${pending.userCode}.`, exitCodes.pending, `Open ${pending.url}, click Approve, then rerun the same command. It resumes this request.`);
237
+ if (Date.now() + interval > deadline) throw new Stop(`Waiting for approval of code ${pending.userCode}: ${pending.url} (expires ${deadlineText(pending.expiresAt)}).`, exitCodes.pending,
238
+ `Show the person approving this link and its expiry in your reply. Once they click Approve, rerun the same command: it resumes this request.`,
239
+ { approvalUrl: pending.url, userCode: pending.userCode, expiresAt: new Date(pending.expiresAt).toISOString() });
230
240
  await new Promise(resolve => setTimeout(resolve, interval));
231
241
  }
232
242
  }
@@ -251,7 +261,7 @@ async function install(root, { client, scope, plan, open, log, wait, telemetry,
251
261
  const found = conflicts(state);
252
262
  if (client === 'pi' && autoUpdate === 'on') throw new Stop('Pi updates are explicit: rerun install --client pi. Automatic hooks are Claude Code only.', exitCodes.error);
253
263
  const changes = [
254
- `${state.current ? 'Update' : 'Add'} ${client === 'pi' ? 'native Pi MCP extension' : 'MCP server "openmerit" in Claude Code'} (${scope} scope)`,
264
+ `${state.current ? 'Update' : 'Add'} ${client === 'pi' ? 'native Pi MCP extension' : 'MCP server "openmerit" in Claude Code'} (${scope} scope)${client !== 'pi' && scope === 'project' && state.paths.project !== root ? `; Claude Code applies it to the whole git repository at ${state.paths.project}` : ''}`,
255
265
  `${state.installedSkill === null ? 'Create' : 'Update'} ${state.paths.skill}`,
256
266
  `${state.installedRule === null ? 'Create' : 'Update'} ${state.paths.rule} (loaded at session startup)`,
257
267
  ...(client === 'pi' ? [] : [
@@ -293,7 +303,7 @@ async function install(root, { client, scope, plan, open, log, wait, telemetry,
293
303
  await save(state.paths.rule, rule);
294
304
  if (state.legacyRule === 'remove') await rm(state.paths.legacyRule, { force: true });
295
305
  // Record what Claude actually wrote, so later edits are detected precisely.
296
- const written = configuredServer(await object(state.paths.mcpFile), root, scope);
306
+ const written = configuredServer(await object(state.paths.mcpFile), state.paths.project, scope);
297
307
  if (!written) throw new Stop('Claude Code did not save the "openmerit" MCP server.', exitCodes.error, 'Run "openmerit doctor".');
298
308
  const updates = await configureUpdates(dirname(state.paths.record), scope, state.record, clientVersion, enabled);
299
309
  await save(state.paths.record, { version: 3, scope, client, ...ids, serverHash: serverHash(written), skillHash: hash(skill), ruleHash: hash(rule), updates, installedAt: new Date().toISOString() });
@@ -509,7 +519,7 @@ async function runMain(args, { root = process.cwd(), print = console.log, printE
509
519
  } catch (error) {
510
520
  if (!(error instanceof Stop)) throw error;
511
521
  await telemetry.fail(error);
512
- if (options.json) print(JSON.stringify({ ok: false, action, code: Object.keys(exitCodes).find(k => exitCodes[k] === error.exit), message: error.message, next: error.next }, null, 2));
522
+ if (options.json) print(JSON.stringify({ ok: false, action, code: Object.keys(exitCodes).find(k => exitCodes[k] === error.exit), message: error.message, next: error.next, ...error.details }, null, 2));
513
523
  else printError([error.message, error.next].filter(Boolean).join('\n'));
514
524
  return error.exit;
515
525
  }
@@ -99,7 +99,11 @@ eval platform.
99
99
  Report what you measured with \`scout_models\`: the same \`conversationId\` and
100
100
  \`results\`, one aggregate per model and metric (\`level\`, \`task\` title, \`scope\`,
101
101
  \`model\` endpoint or model ID, \`metric\`, \`value\`, \`higherIsBetter\`,
102
- \`samples\`, \`source\`). Send aggregates only, never inputs, outputs or records.
102
+ \`samples\`, \`source\`). Set \`kind\` to \`usd\` or \`seconds\` for measured cost
103
+ or time (per call at the task level, per run at the agent level), so Scout uses
104
+ your cost instead of its estimate. Report each agent's own checks at the agent
105
+ level: an end-to-end result also measures the agents you did not swap. Send
106
+ aggregates only, never inputs, outputs or records.
103
107
  Scout ranks them above public benchmarks for this user only; models whose
104
108
  \`quality.measured\` is true rest on them.
105
109
 
@@ -113,7 +117,9 @@ own model.
113
117
  If Scout fails or has no suitable candidate/evidence for the required deployment,
114
118
  say so and pause the model decision; do not substitute a remembered recommendation
115
119
  or SDK default. Continue only model-independent work. Local/browser models are not
116
- exempt when the catalogue lacks coverage. If it reports that the device key is missing,
120
+ exempt when the catalogue lacks coverage. When an install or login waits for approval (exit code
121
+ 10), show the user the approval link and its UTC expiry in your reply, not only in
122
+ command output, then rerun the same command once they approve. If it reports that the device key is missing,
117
123
  expired or revoked, run \`npx -y openmerit@latest login\` and then
118
124
  \`npx -y openmerit@latest install\` again. Never ask the user to paste a key.
119
125
  `;
@@ -19,9 +19,10 @@ work, output, criteria, constraints) in the same conversation, or --skip to let
19
19
  Scout assume. It then splits the work into tasks and recommends up to five
20
20
  models for each, with an evaluation: how to measure the candidates with your
21
21
  own evals. Report what you measured with --results: a JSON list of aggregate
22
- results ({level, task?, scope, model, metric, value, higherIsBetter, samples,
23
- source}), never inputs or outputs. Scout ranks them above public benchmarks, for
24
- your recommendations only. A turn usually takes one to three minutes.
22
+ results ({level, kind?, task?, scope, model, metric, value, higherIsBetter,
23
+ samples, source}), never inputs or outputs. kind is quality (the default), usd
24
+ or seconds; measured usd replaces Scout's cost estimate. Scout ranks them above
25
+ public benchmarks, for your recommendations only. A turn usually takes one to three minutes.
25
26
 
26
27
  A new conversation is limited to the endpoint IDs in ${policyFile}
27
28
  (set with openmerit project). --all-endpoints searches the whole catalogue and is
@@ -125,7 +126,8 @@ export function renderScout(result) {
125
126
  if (priority !== 'cost')
126
127
  lines.push(` ${rankings[priority]}`);
127
128
  for (const m of task.models) {
128
- const price = m.cost ? `${money(m.cost.inputPerMillion)} in / ${money(m.cost.outputPerMillion)} out per 1M via ${m.cost.route} (${m.cost.endpointId})` : 'no priced route';
129
+ const measuredCost = m.cost?.measured ? `measured ${money(m.cost.measured.usdPer1kAgentRuns ?? m.cost.measured.usdPer1kTasks)} per 1k ${m.cost.measured.usdPer1kAgentRuns === undefined ? 'tasks' : 'agent runs'}, ` : '';
130
+ const price = m.cost ? `${measuredCost}${money(m.cost.inputPerMillion)} in / ${money(m.cost.outputPerMillion)} out per 1M via ${m.cost.route} (${m.cost.endpointId})` : 'no priced route';
129
131
  const metric = priority === 'latency' ? ` · ${m.latency ? `${m.latency.seconds.toFixed(1)}s per task` : 'time unknown'}`
130
132
  : priority === 'recency' ? ` · ${m.recency ? `released ${m.recency.released}` : 'release date unknown'}` : '';
131
133
  lines.push(` ${m.rank}. ${m.name}${metric} · quality ${m.quality.score}/100${m.quality.measured ? ' (measured)' : ''} · ${price}`, ` ${m.rationale}`);
@@ -166,7 +166,8 @@ export async function configureUpdates(base, scope, record, version, enabled) {
166
166
  const state = await inspectUpdates(base, scope, record);
167
167
  if (state.conflicts.length)
168
168
  throw new Error(state.conflicts.join('\n'));
169
- const source = await readFile(new URL('./updates.ts', import.meta.url), 'utf8');
169
+ // This module is the runner: updates.ts in source, the compiled updates.js in the npm package.
170
+ const source = await readFile(new URL(import.meta.url), 'utf8');
170
171
  const hook = { hooks: [{ type: 'command', command: `${quote(process.execPath)} ${quote(state.paths.runner)} --base ${quote(base)} --scope ${scope} --hook`, async: true, timeout: 150 }] };
171
172
  const hooks = { ...state.hooks };
172
173
  for (const event of ['SessionStart', 'Stop']) {
package/docs/scout.md CHANGED
@@ -26,7 +26,12 @@ OpenMerit does not run evaluations or host an eval platform. Scout tells you wha
26
26
 
27
27
  - **Agents and calls.** A task is still one model call. When the work is an agent that calls models in a loop, a task can name its `agent` and `callsPerRun`; each model's `cost` then also has `usdPer1kAgentRuns`, the same route's cost of 1,000 runs of that agent.
28
28
  - **Evaluation plan.** Every task in a recommendation has an `evaluation`: `checks` at the `task` (one model call), `agent` (one agent run) or `environment` (end-to-end outcome) level, labelled `samples` per candidate, and a `stopEarly` rule for a candidate that is clearly losing. The installed instructions tell the coding agent to add the candidates as extra runs of evals the project already has, such as datasets and experiments in its eval or tracing platform, or its own eval scripts, and not to build a second eval. If none exists, it proposes a minimal one and creates it only where and how you agree.
29
- - **Measured results.** Continue the conversation with `results` (`scout_models`, `POST /scout/api/agent`) or `openmerit scout --conversation <id> --results <file.json>`. Each result is one aggregate: `level`, optional `task` title, `scope`, `model` (catalogue model or endpoint ID), `metric`, `value`, `higherIsBetter`, `samples` and `source`, at most 50 per call and 200 per conversation. Inputs, outputs and records are not accepted. Scout recommends again with measured results ranked above public benchmarks: a candidate you measured on a task must cite that result, cited results appear as evidence named "Your eval", ranked among your results on the same metric, and `quality.measured` is true for models whose estimate rests on them.
29
+ - **Measured results.** Continue the conversation with `results` (`scout_models`, `POST /scout/api/agent`) or `openmerit scout --conversation <id> --results <file.json>`. Each result is one aggregate: `level`, optional `task` title, `scope`, `model` (catalogue model or endpoint ID), `metric`, `value`, `higherIsBetter`, `samples` and `source`, at most 50 per call and 200 per conversation. Inputs, outputs and records are not accepted. Scout recommends again with measured results ranked above public benchmarks: a candidate you measured on a task must cite that result, cited results appear as evidence named "Your eval", ranked among your results on the same metric, and `quality.measured` is true for models whose estimate rests on them. Since the Scout serving deploy of 5 November 2026 (version `8782d345-a63c-4560-bb08-1a6a95e8c0ae`), models whose estimate rests on your results also rank ahead of unmeasured ones, in the conversation's priority order within each group (cheapest first by default), so a model estimated only from benchmarks no longer leads a shortlist you measured. Before it, measured results could only move a model below the bar.
30
+
31
+ **Since the Scout serving deploy of 5 November 2026 (version `ecd0bd6b-83a9-4e8d-9fd8-d5ca6de88271`).** npm 0.3.3 documents the field in `openmerit scout --help` and the installed instructions and shows measured cost in terminal output; `--results` in 0.3.2 already forwards it.
32
+
33
+ - **Measured cost.** A result can set `kind` to `usd` or `seconds` (default `quality`): cost or time per model call at the task level, per agent run at the agent level, per outcome at the environment level. Cost and time results are never quality evidence. A model's agent- or task-level `usd` result on a task appears as `cost.measured` (`usdPer1kTasks`, `usdPer1kAgentRuns`, and the `result` it came from), and when every model in a group (measured or unmeasured) has one, the group is ranked by measured cost instead of Scout's catalogue estimate, which can be far off for agent runs.
34
+ - **Environment results and attribution.** An environment-level result also measures every other agent and control. Scout no longer drops a candidate below the bar when its own task- and agent-level quality results on that task match a candidate that reaches it; an environment miss alone does not show the swapped model caused it.
30
35
  - **Scope.** Results are stored with the conversation for your account and inform only your recommendations. They are not shared with other accounts or used to change Scout's catalogue.
31
36
 
32
37
  The quality bar now considers what an error costs once your environment's controls, such as a person approving every payment, have acted. Cost remains the default ranking among models that reach the bar.
@@ -60,12 +65,12 @@ Once the brief is settled, Scout splits the work into tasks, one per model it ne
60
65
  Coding agents now use the same Scout agent as the page, instead of the older single-call search. Claude Code and Pi reach it through the `scout_models` MCP tool, which runs one conversation turn per call and returns `status`:
61
66
 
62
67
  - `needs_answers`: Scout's `brief` and `questions`. The agent shows them to the user, then calls again with the `conversationId` and `brief` set to the rewritten components, or `skip: true`.
63
- - `recommended`: `tasks[]`, each with `title`, `work`, `input`, `output`, `summary`, `workload`, `priority` (`cost` by default, or `quality`, `latency` or `recency`), `qualityBar` and up to five ranked `models`. Coding-agent conversations have no task limit. Each model's `cost` names the cheapest eligible route's `endpointId` and `providerModelId`.
68
+ - `recommended`: `tasks[]`, each with `title`, `work`, `input`, `output`, `summary`, `workload`, `priority` (`cost` by default, or `quality` or `recency`), `qualityBar` and up to five ranked `models`. Coding-agent conversations have no task limit. Each model's `cost` names the cheapest eligible route's `endpointId` and `providerModelId`.
64
69
  - `working`: the call ended before the turn did. Calling again with only the `conversationId` reads the outcome.
65
70
 
66
71
  The tool takes `prompt`, `conversationId`, `brief`, `skip` and, on the first call, `endpointIds`. The inventory limits every search and shortlist in that conversation, whatever filters the agent's model chooses; an empty list allows none, and later turns cannot change it. `POST /scout/api/agent` accepts the same `endpointIds`. `modelIds` is no longer accepted, and the response no longer has `tasks[].slots`. A turn usually takes one to three minutes, much longer than a search. Conversations are keyed by the device key's account. The local `npm run scout` preview has no agent and reports MCP calls as unavailable.
67
72
 
68
- npm 0.2.0 adds `openmerit scout` for agents without MCP and for people at a terminal. It calls the same tool with this device's key and applies the project's saved endpoint inventory. Without `--json` it prints each task's models in ranked order with their quality estimate and list prices. As on the page, the default cheapest-first order is unlabelled; when the user asks for the best quality, the fastest or the newest model, a line names that order, and fastest and newest lists also show each model's time per task or release date (unknown when missing). The installed instructions tell the agent to relay Scout's questions rather than answer them itself.
73
+ npm 0.2.0 adds `openmerit scout` for agents without MCP and for people at a terminal. It calls the same tool with this device's key and applies the project's saved endpoint inventory. Without `--json` it prints each task's models in ranked order with their quality estimate and list prices. As on the page, the default cheapest-first order is unlabelled; when the user asks for the best quality, the fastest or the newest model, a line names that order, and fastest and newest lists also show each model's time per task or release date (unknown when missing). Scout no longer ranks by speed, so the fastest order no longer appears. The installed instructions tell the agent to relay Scout's questions rather than answer them itself.
69
74
 
70
75
  ## Every model kind in the agent — source only, not deployed
71
76
 
@@ -78,7 +83,7 @@ The agent's tools change as follows:
78
83
  - **search_catalogue** (new) searches models, benchmarks and leaderboards by free text. By default it combines keyword (BM25) and embedding results by reciprocal rank fusion; it can also use either alone, and filter by result type and model kind. Each result names the field that matched. Without embeddings (no OpenAI key, an embedding error, or a catalogue version published without them) it returns keyword results and says so. Its queries, written by the agent from your task, are embedded with OpenAI, as benchmark search already does.
79
84
  - **search_models** filters by model kind and reports the cheapest eligible price in each unit a model is billed in: per million tokens, per image, per second of audio or video, per million characters, per page or per request. Sorting by price compares one unit only (by default the usual unit for the requested kind, otherwise tokens); models not billed in that unit follow, unsorted. A route that lists only zero prices, such as a local runtime or a free tier, is not counted as a price.
80
85
  - **get_model_details** and **benchmark_leaderboard** include leaderboard results, and leaderboard IDs work wherever benchmark IDs do.
81
- - **submit_recommendation** accepts models of any kind. Fewer than five are accepted only when fewer models meet the stated hard constraints, which can now include the model kind. The per-task workload is given in billing units (input and output tokens, images, seconds of audio or video, characters, pages, requests) and cost per 1,000 tasks is computed from catalogue prices on the eligible route that prices most of the workload, then the cheapest. Quantities the route does not price are listed, and such a partial cost never leads on cost. Citations must be the model's own benchmark or leaderboard results.
86
+ - **submit_recommendation** accepts models of any kind. It must assess at least five candidates, fewer only when fewer models meet the stated hard constraints, which can now include the model kind. Fewer than five may reach the quality bar, so the shortlist can be shorter than five instead of the bar dropping to fill it; at least one must reach it. For a task that is an agent's call, every candidate at or above the bar must cite a benchmark tagged tools or agents, or the user's measured result, so domain benchmarks alone cannot qualify a model that calls tools. The per-task workload is given in billing units (input and output tokens, images, seconds of audio or video, characters, pages, requests) and cost per 1,000 tasks is computed from catalogue prices on the eligible route that prices most of the workload, then the cheapest. Quantities the route does not price are listed, and such a partial cost never leads on cost. Citations must be the model's own benchmark or leaderboard results.
82
87
 
83
88
  In the shortlist, the Input and Output columns keep per-million-token prices; a model billed in another unit shows that unit's price there instead, marked with its unit, and the expanded model shows the estimated cost per 1,000 tasks. Known limitations: per-second audio prices apply to audio seconds, but audio billed in tokens must be estimated as tokens; image prices are per generated image; prices are base-tier list prices from catalogue sources and can be wrong at the source.
84
89
 
@@ -98,7 +103,7 @@ In the shortlist, the Input and Output columns keep per-million-token prices; a
98
103
 
99
104
  The conversational agent is the Scout page at `/scout/`, replacing the search box and its results table. The page stays open to everyone; sending a message requires sign-in, as searching did, and a message typed while signed out is kept while you sign in. The JSON search API (`/scout/api/recommend`), the MCP server (`/scout/mcp`), coding-agent setup at `/connect/` and the catalogue refresh are unchanged, so the CLI and installed coding agents keep their current behaviour. Sections below that describe the search page's prompt examples, results table and loading messages are historical; the Claude Code setup handoff remains available in the new page.
100
105
 
101
- You describe the work; Scout writes a task brief with five components (input, work, output, criteria and constraints), each marked stated, assumed or missing. A component holds only what is known, without listing unknowns. Criteria and constraints are filled only from what you explicitly say, such as "best quality output" or "open weights only"; otherwise they stay empty and missing. Each component is editable in place: correct anything Scout got wrong, or fill in a missing one. All five fields share the same editable background, including Work; the page does not show per-component question text. Once sent, the brief collapses. **Continue** sends only the components you changed and requests recommendations immediately, using stated assumptions for missing details. It also works without edits; there is no separate Skip questions button. Pressing Enter in a brief field does the same; Shift+Enter adds a line break. The message box stays at the bottom of the page. Each turn ends with an editable brief or a shortlist of five models, followed by Scout's summary. While a turn runs, its steps are listed with the current one last; afterwards they stay as a collapsed record of the work. By default each shortlist is the five cheapest models that maintain the task's output quality. Scout sets a 0–100 quality bar for the task and estimates each candidate it assessed (up to ten) on the same scale. Candidates below the bar are dropped, the rest are ranked by estimated cost per task (Scout's estimate of the task's workload at the route's list prices) and the cheapest five are shown, so a costlier model appears only when fewer than five cheaper ones reach the bar. A cost that leaves part of the workload unpriced ranks after complete ones. Only when the user explicitly asks for the best quality, the fastest or the newest model are the qualifying candidates ranked by quality estimate, reference time or release date instead. The default ranking is not labelled on the page; an explicit priority is named in a line above the table. The bar and the estimates are Scout's judgement from benchmark standings, not measured accuracy. The shortlist reports Scout's 0–100 quality estimate with the cited benchmark ranks, input and output prices per million tokens on its cheapest eligible route and release date; a latency ranking adds a time per task column. Expanding a model shows its rationale, benchmark ranks, route and reference latency. A missing release date is shown as unknown. Reply in the same conversation to change constraints or trade-offs; **New** starts over. When Scout is updated while the page is open, a notice at the bottom of the sidebar asks you to refresh, and clicking anywhere on it reloads the page; an open page notices on its next turn or when you return to its tab, and checks `GET /scout/api/version`, which returns the deployed version ID.
106
+ You describe the work; Scout writes a task brief with five components (input, work, output, criteria and constraints), each marked stated, assumed or missing. A component holds only what is known, without listing unknowns. Criteria and constraints are filled only from what you explicitly say, such as "best quality output" or "open weights only"; otherwise they stay empty and missing. Each component is editable in place: correct anything Scout got wrong, or fill in a missing one. All five fields share the same editable background, including Work; the page does not show per-component question text. Once sent, the brief collapses. **Continue** sends only the components you changed and requests recommendations immediately, using stated assumptions for missing details. It also works without edits; there is no separate Skip questions button. Pressing Enter in a brief field does the same; Shift+Enter adds a line break. The message box stays at the bottom of the page. Each turn ends with an editable brief or a shortlist of five models, followed by Scout's summary. While a turn runs, its steps are listed with the current one last; afterwards they stay as a collapsed record of the work. By default each shortlist is the five cheapest models that maintain the task's output quality, or fewer when fewer reach the bar. Scout sets a 0–100 quality bar for the task and estimates each candidate it assessed (up to ten) on the same scale. Candidates below the bar are dropped, the rest are ranked by estimated cost per task (Scout's estimate of the task's workload at the route's list prices) and the cheapest five are shown, so a costlier model appears only when fewer than five cheaper ones reach the bar. A cost that leaves part of the workload unpriced ranks after complete ones. Only when the user explicitly asks for the best quality or the newest model are the qualifying candidates ranked by quality estimate or release date instead. Speed never ranks: few benchmarks measure it, and it depends on the endpoint and the reasoning setting, so reference latency is shown for information only. The default ranking is not labelled on the page; an explicit priority is named in a line above the table. The bar and the estimates are Scout's judgement from benchmark standings, not measured accuracy. The shortlist reports Scout's 0–100 quality estimate with the cited benchmark ranks, input and output prices per million tokens on its cheapest eligible route and release date. Expanding a model shows its rationale, benchmark ranks, route and reference latency. A missing release date is shown as unknown. Reply in the same conversation to change constraints or trade-offs; **New** starts over. When Scout is updated while the page is open, a notice at the bottom of the sidebar asks you to refresh, and clicking anywhere on it reloads the page; an open page notices on its next turn or when you return to its tab, and checks `GET /scout/api/version`, which returns the deployed version ID.
102
107
 
103
108
  The agent runs in a Cloudflare Durable Object, one per conversation, using Pi Durable and the Agents SDK. Its model is set by `SCOUT_AGENT_MODEL`: `openai/<model>` calls OpenAI's Responses API directly with the Worker's `OPENAI_API_KEY` (default `openai/gpt-6-sol`), and any other value runs on Workers AI, such as `@cf/zai-org/glm-5.3`. Its tools search benchmark descriptions by embedding (OpenAI embeddings, as in search), read leaderboards and model records from the published catalogue, and use Workers AI web search when the catalogue cannot answer something decisive. Cost, latency and recency figures come from catalogue data, not from the model's text, and a shortlist that cites models or benchmarks outside the catalogue, or breaks a stated hard constraint, is rejected and resubmitted. A turn usually takes one to three minutes.
104
109
 
@@ -352,9 +357,9 @@ The prompt contains no key, account data or task draft. Its 10-minute timeout is
352
357
 
353
358
  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).
354
359
 
355
- 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.
360
+ 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 30 minutes (ten minutes before the Scout serving deploy of 5 November 2026) and can be used once. From npm 0.3.2 the installer prints the expiry in UTC with the minutes left, the pending message and `--json` result (`approvalUrl`, `userCode`, `expiresAt`) carry the link, and the installed skill tells the agent to show both. `OPENMERIT_LOGIN_WAIT` sets the wait in seconds.
356
361
 
357
- `--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.
362
+ `--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. Claude Code keys that configuration by the git repository root, so for a project inside a larger repository (such as a monorepo package) the MCP server applies to every Claude Code session in that repository, while the skill and rule stay in the project; npm 0.3.2 and later say so, and earlier versions report a failed install in this case. `--plan` lists the changes without making them. `--json` prints one result object for agents.
358
363
 
359
364
  | Exit code | Meaning |
360
365
  |---|---|
package/docs/security.md CHANGED
@@ -30,7 +30,7 @@ Hosted Scout uses Clerk Google/GitHub sign-in. The page and status are public; J
30
30
 
31
31
  ### Coding-agent device keys
32
32
 
33
- The [coding-agent setup](scout.md#connect-a-coding-agent) in npm 0.1.7 uses hosted device sign-in instead of OAuth or tokens in prompts. The copied prompt contains no secret. A signed-in person approves an eight-character code on `/connect`; codes expire after ten minutes and are single-use. Approval mints one key per device and client, scoped to `scout:recommend` and accepted only on `/scout/mcp`. The server stores only SHA-256 hashes. Keys expire after 90 days without use and can be revoked on `/connect` or with `openmerit remove`/`logout`. Locally the key is in `~/.config/openmerit/credentials.json` (mode 600) and in Claude Code's plain-text MCP configuration; the CLI never prints it. As with any device-code flow, only approve a code you started.
33
+ The [coding-agent setup](scout.md#connect-a-coding-agent) in npm 0.1.7 uses hosted device sign-in instead of OAuth or tokens in prompts. The copied prompt contains no secret. A signed-in person approves an eight-character code on `/connect`; codes expire after 30 minutes (ten before the Scout serving deploy of 5 November 2026) and are single-use. Approval mints one key per device and client, scoped to `scout:recommend` and accepted only on `/scout/mcp`. The server stores only SHA-256 hashes. Keys expire after 90 days without use and can be revoked on `/connect` or with `openmerit remove`/`logout`. Locally the key is in `~/.config/openmerit/credentials.json` (mode 600) and in Claude Code's plain-text MCP configuration; the CLI never prints it. As with any device-code flow, only approve a code you started.
34
34
 
35
35
  **Automatic instruction updates — version 0.1.10:** updater-enabled setup defaults on with an opt-out, installing asynchronous Claude Code startup/response-end hooks. Checks use npm's public stable registry without credentials, task content or installation IDs. SHA-512 verification checks package bytes against registry metadata; it does not defend against a compromised publisher/registry. The updater reads only a bounded instruction bundle, never executes downloaded code or package scripts, and leaves MCP configuration, keys, privacy settings and project policy unchanged. A supported release contract permits instruction updates; maintainers must change it when permissions or data handling change. This is a publisher assertion, not semantic validation of instructions. Edited files are retained, concurrent writes serialize, and journaled recovery stops on intervening edits. New sessions reliably load new instructions. See [automatic updates](scout.md#automatic-instruction-updates-version-0110) for cadence, offline retries, manual commands and the one-time bootstrap required for older installations. Availability follows npm publication independently of hosted releases.
36
36
 
@@ -11,7 +11,7 @@ npx -y openmerit@latest status --client pi --scope user --check
11
11
  npx -y openmerit@latest doctor --client pi --scope user
12
12
  ```
13
13
 
14
- Use `--scope project` from the original project root for project setup. Check Pi and Node.js versions against [setup](getting-started.md), complete browser device approval, then start a **new session**. Installing the npm package or reconnecting an old session does not load new extension instructions. For SDK embedding, load Pi's `createMcpExtension()` explicitly alongside `createScoutPiExtension`; the Scout factory alone is not an MCP runtime.
14
+ Use `--scope project` from the original project root for project setup. In a project inside a larger git repository, Claude Code project setup up to npm 0.3.1 ends with "Claude Code did not save the openmerit MCP server" although Claude Code saved it under the repository root; Scout still works in that repository, and npm 0.3.2 reads the entry correctly; rerun the install with it. npm 0.3.1 and 0.3.2 also end every Claude Code install with `ENOENT … dist/terminal/src/updates.ts`: the MCP server, skill and rule are installed, but automatic instruction updates stay off. Rerun the install with npm 0.3.3 or later. Check Pi and Node.js versions against [setup](getting-started.md), complete browser device approval, then start a **new session**. Installing the npm package or reconnecting an old session does not load new extension instructions. For SDK embedding, load Pi's `createMcpExtension()` explicitly alongside `createScoutPiExtension`; the Scout factory alone is not an MCP runtime.
15
15
 
16
16
  ## The connection configuration is invalid
17
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmerit",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Evidence-driven model decisions for coding harnesses.",
5
5
  "type": "module",
6
6
  "bin": {