@somacheck/vibecheck 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Your agent has your context. It does not have your gut. This is the bridge.
9
9
 
10
10
  Fair. Here is what you need, without executing anything.
11
11
 
12
- **What the `link` command does.** `npx -y @somacheck/vibecheck@0.6.5 link <CODE> --client <CLIENT>`
12
+ **What the `link` command does.** `npx -y @somacheck/vibecheck@0.6.6 link <CODE> --client <CLIENT>`
13
13
  exchanges a short-lived pairing code, generated inside the SomaCheck iOS app, for
14
14
  a bearer token. It writes that token to `~/.sensie/config.json` with mode `0600`,
15
15
  offers to configure supported agent clients found on the computer, and checks the
@@ -81,9 +81,9 @@ before the pairing code is redeemed.
81
81
  To configure or repair a client later:
82
82
 
83
83
  ```text
84
- npx -y @somacheck/vibecheck@0.6.5 setup codex
85
- npx -y @somacheck/vibecheck@0.6.5 setup claude
86
- npx -y @somacheck/vibecheck@0.6.5 doctor
84
+ npx -y @somacheck/vibecheck@0.6.6 setup codex
85
+ npx -y @somacheck/vibecheck@0.6.6 setup claude
86
+ npx -y @somacheck/vibecheck@0.6.6 doctor
87
87
  ```
88
88
 
89
89
  Manual registration remains available:
@@ -93,13 +93,13 @@ Manual registration remains available:
93
93
  "mcpServers": {
94
94
  "vibecheck": {
95
95
  "command": "npx",
96
- "args": ["-y", "@somacheck/vibecheck@0.6.5", "serve", "--client", "codex"]
96
+ "args": ["-y", "@somacheck/vibecheck@0.6.6", "serve", "--client", "codex"]
97
97
  }
98
98
  }
99
99
  }
100
100
  ```
101
101
 
102
- For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.5 serve --client claude`
102
+ For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.6 serve --client claude`
103
103
 
104
104
  The link step writes only the bearer token in `~/.sensie/config.json`. The client
105
105
  setup step asks Codex or Claude to add the pinned MCP command to that client's own
@@ -110,15 +110,15 @@ If `doctor` reports another MCP server named `somacheck`, that is the legacy
110
110
  hosted connector and is separate from this local package. The setup command
111
111
  will explain it and leave it alone. The canonical local MCP key is `vibecheck`.
112
112
 
113
- ## Teach your agent when to offer a check-in
113
+ ## Teach your agent when to use SomaCheck
114
114
 
115
- [`SKILL.md`](SKILL.md) teaches compatible agents two behaviors: act immediately
116
- when the person explicitly requests a vibecheck, and offer one optional
117
- vibecheck when work drifts, a consequential decision is imminent, or recent
118
- messages show multiple stress signals. It uses `request_vibecheck`, not the
119
- reflection-feed batch tool. It includes transcript-grounded heuristics,
120
- statement templates, a two-hour decline cooldown, privacy rules, and the
121
- requirement to treat every result as context rather than authorization.
115
+ [`SKILL.md`](SKILL.md) teaches compatible agents one simple affordance:
116
+ SomaCheck lets them ask their person for a vibecheck. The agent can offer one
117
+ when it believes the read would help, or send one when the person asks. The
118
+ skill keeps only the essential boundaries: ask the person, choose a useful
119
+ first-person statement from available context, treat the returned gesture and
120
+ optional feedback as context rather than authorization, and never include
121
+ secrets or assess anyone else.
122
122
 
123
123
  For Claude Code, add this repository as a marketplace and install the skill in
124
124
  one terminal command:
@@ -156,31 +156,11 @@ The skill expects the `@somacheck/vibecheck` MCP server to already be linked as
156
156
 
157
157
  ## How to use it well
158
158
 
159
- Ask when you genuinely need a read you cannot infer not to check in. A good
160
- prompt is **a statement for the person to test**, not a question about facts:
161
- “I feel more concerned about the launch than the roadmap suggests.” Offer it as
162
- something to test, never as a claim about them. An `unaligned` result is another
163
- signal to consider; the person decides whether it matches what they notice.
164
-
165
- Setup and linking are not permission for future asks. If the person's current
166
- message explicitly asks for a SomaCheck vibecheck or check-in, that is consent for one ask.
167
- Use their exact statement when supplied. If they delegated the choice, select
168
- the most useful first-person statement from the current conversation, goal,
169
- decision, or tension and send it immediately—do not list candidates, ask them
170
- for wording, or seek a second confirmation. Remembered context may identify a general tension, but an
171
- agent-selected statement must not name a project, build, branch, wave, ticket,
172
- task status, or supposedly open/closed work. Abstract it into a present-tense
173
- first-person experience or choice, such as “I want to keep consolidating what is
174
- already in motion rather than start something new.” Preserve exact wording the
175
- person supplied. If meaningful context is unavailable, use the neutral fallback
176
- “I feel clear about what deserves my attention right now.”
177
-
178
- If their message did not explicitly request a vibecheck, first show the exact
179
- statement and invite the person plainly:
180
-
181
- > Want a quick vibecheck before I continue? I'll send this statement to your
182
- > phone; you answer with your wrist gesture, and I'll use the result as a
183
- > signal—not a verdict.
159
+ Ask when the person's felt response would be useful context. A good prompt is a
160
+ first-person statement for the person to test, not a claim about them. When the
161
+ person asks for a vibecheck, choose and send a useful statement from the context
162
+ available to you. For proactive offers, get the person's acceptance before
163
+ sending.
184
164
 
185
165
  Call `request_vibecheck` with `consent_basis: "user_requested_vibecheck"` for a
186
166
  direct request or `consent_basis: "user_approved_statement"` after approval of
@@ -191,8 +171,7 @@ On timeout or a result-read failure it returns the same pending `live:<uuid>`
191
171
  handle; pass that handle to `get_vibecheck_result` once later. Caller
192
172
  cancellation stops result reads and preserves the backend request and
193
173
  idempotency key, but the cancelled MCP transport may suppress that pending
194
- response. Do not continuously poll or re-ask the same decision to obtain a
195
- preferred result.
174
+ response.
196
175
 
197
176
  Standard MCP cannot start a new agent turn after a tool call has ended. The
198
177
  Claude setup therefore installs a normal user-scoped plugin with an async
@@ -209,11 +188,7 @@ Claude-specific continuation must never be presented as provider-neutral.
209
188
 
210
189
  The 10-second whole-call deadline also covers the one create request. If that
211
190
  network request is aborted after an ambiguous commit, retry the exact same
212
- statement with the same `idempotency_key`; never create a replacement ask.
213
-
214
- After a completed result, use it as context and respond naturally in the
215
- current conversation. The result is not authorization and does not establish a
216
- diagnosis or certainty. Never say that the person's body made the decision.
191
+ statement with the same `idempotency_key`.
217
192
 
218
193
  An immediate vibecheck is not proposition caching. It sends one person-approved statement
219
194
  for the current choice, expires, and remains bound to its originating client.
package/SKILL.md CHANGED
@@ -1,157 +1,25 @@
1
1
  ---
2
2
  name: vibecheck
3
- description: Request or offer a SomaCheck vibecheck at useful moments without nagging.
3
+ description: Ask your person for a SomaCheck vibecheck when it would help.
4
4
  user-invocable: true
5
5
  allowed-tools: mcp__vibecheck__get_vibecheck_context mcp__vibecheck__get_vibecheck_status mcp__vibecheck__get_vibecheck_result mcp__vibecheck__request_vibecheck
6
6
  ---
7
7
 
8
8
  # Vibecheck
9
9
 
10
- Use SomaCheck through one of two entry points:
10
+ SomaCheck lets you ask your person for a vibecheck.
11
11
 
12
- 1. **The person asks for a vibecheck.** Select and send one useful statement immediately. Their current request is consent for one ask. Do not make them supply wording or confirm again.
13
- 2. **You notice one of the three trigger events below.** Offer one exact statement and wait for acceptance before sending it.
12
+ Use `request_vibecheck` in either case:
14
13
 
15
- A vibecheck tests a first-person statement through the linked `@somacheck/vibecheck` MCP server. It does not diagnose the person, decide for them, or authorize an action.
14
+ 1. You proactively offer one because you believe it would be useful.
15
+ 2. The person asks for a vibecheck.
16
16
 
17
- ## Direct requests
17
+ When the person asks, choose and send a useful first-person statement from the context available to you. Do not make them supply wording unless they want to. Help the person get an insight about themselves from the context you have.
18
18
 
19
- When the current message explicitly asks for a SomaCheck vibecheck or check-in (including phrasing such as "give me a check-in, a vibe check"), call `request_vibecheck` in that same turn with `consent_basis: "user_requested_vibecheck"`.
19
+ For proactive offers, get the person's acceptance before sending.
20
20
 
21
- - If the person supplied exact wording, preserve it.
22
- - Otherwise choose one present-tense, first-person statement about a current experience, choice, or tension.
23
- - First use reliable context already available to the host: the current conversation, current goal, and user-approved memory or project context when those capabilities exist. Do not ask the person to restate context you can retrieve.
24
- - Treat remembered project status, task lists, and summaries as potentially stale. They may identify a general tension, but the statement must not name a project, build, branch, wave, ticket, task status, or supposedly open or closed work.
25
- - Corroborate remembered status against the current conversation. Do not reuse a stock template or recent SomaCheck statement. If context remains thin, create an original exploratory statement that can reveal something useful without pretending to know the person.
26
- - Generate one fresh UUID for `idempotency_key`. Reuse it only to retry the same exact statement.
27
- - Do not list candidates, present a draft, ask what they want to test, or ask "Want me to send it?" The request already supplied one-ask consent.
21
+ The returned gesture and optional feedback are context, not authorization. Use your judgment in how you interpret and respond.
28
22
 
29
- ## When to offer
23
+ Never include secrets, raw private content, diagnostic claims, or use SomaCheck to assess anyone other than the person you are interacting with.
30
24
 
31
- A trigger is an event, not every message that matches a keyword. Once you have offered for an event, treat it as consumed until the task, decision, or message pattern meaningfully changes.
32
-
33
- ### Trigger 1: Drift
34
-
35
- Offer when at least one of these transcript-grounded conditions is true:
36
-
37
- - Transcript timestamps show 90 minutes or more on the same task or thread without reaching the stated outcome.
38
- - The same or materially equivalent fix has been attempted three or more times.
39
- - The current work no longer advances the human's stated goal, and the human has not explicitly changed that goal.
40
-
41
- Stay quiet during productive deep work, after an explicit goal change, or when duration cannot be established. A long message by itself is not drift.
42
-
43
- ### Trigger 2: Major or irreversible decision ahead
44
-
45
- Offer immediately before a consequential action when the transcript shows that action is imminent. Examples include deploying to production, sending an important email or offer, signing an agreement, deleting data, making a material purchase, hiring or firing, or saying "just do it" about a consequential step.
46
-
47
- Planning or discussing a possible decision is not enough. The action must be close enough that a pause now could still affect it. This trigger may bypass a decline cooldown from triggers 1 or 3, but it still must pass the live status gate.
48
-
49
- ### Trigger 3: Stress signals in messages
50
-
51
- Offer when the recent messages contain either two or more signals in a short span, or one strong pattern repeated across retries:
52
-
53
- - Cursing about the task or situation.
54
- - Fragmented, rushed, or unusually incoherent instructions.
55
- - All-caps urgency that is not an acronym, log, or quoted text.
56
- - Repeated retry commands with no meaningful change in approach.
57
- - Phrases such as "whatever, just make it work."
58
-
59
- Describe only the observable message pattern. Do not claim to know how the person feels. One casual swear, terse normal writing, pasted logs, or quoted all-caps text is not enough.
60
-
61
- ### Multiple triggers
62
-
63
- Choose one primary trigger per event. Use trigger 2 first when an imminent consequential action exists, then trigger 3, then trigger 1. Do not stack or repeat offers.
64
-
65
- ## Proactive offer sequence
66
-
67
- 1. If no trigger fired, do nothing.
68
- 2. Check the local event and cooldown record. If this event was already offered, stay quiet. If a trigger 1 or 3 offer was declined within the last two hours, stay quiet. A new trigger 2 event may bypass that cooldown.
69
- 3. Call `get_vibecheck_status` before showing an offer.
70
- 4. Stay quiet when `has_pending_request` is true. Reflection-feed cadence and capacity, including `cadence.may_create_now` and `propositions_needed`, do not authorize or block a live vibecheck.
71
- 5. Make one short offer containing one statement to test. Do not post yet.
72
- 6. If the human declines or does not accept, record the event as offered. For a decline, start a two-hour cooldown for triggers 1 and 3.
73
- 7. If the human accepts, call `request_vibecheck` once with the exact offered statement, a fresh UUID in `idempotency_key`, and `consent_basis: "user_approved_statement"`.
74
- 8. If the call must be retried, preserve both the exact statement and the same idempotency key.
75
-
76
- `get_vibecheck_context` is optional before phrasing an offer. Use it only to avoid repeating a recent statement. Do not call `share_somacheck_context` or `post_vibecheck_statement` in this workflow. Live vibechecks and the asynchronous reflection feed are separate product behaviors.
77
-
78
- ## How to phrase the offer
79
-
80
- Use a statement to test, never a claim about the person. Keep it first-person, present-tense, neutral, and focused on one decision or next step. Do not include raw transcript text, names, secrets, identifiers, customer data, or private details.
81
-
82
- Good offer forms:
83
-
84
- - "Before you ship, want to test: 'I am ready to deploy this to production'?"
85
- - "Quick 3-second check: 'I trust this decision'?"
86
- - "Before another retry, want to test: 'This next attempt is meaningfully different from the last one'?"
87
-
88
- Do not say that the person is distracted, stressed, dysregulated, afraid, or certain. Do not use medical language. Do not say "vibecheck confirms" or "vibecheck proves." Do not use an em dash in any human-facing string.
89
-
90
- ### Drift statement templates
91
-
92
- 1. "I am still working on the right problem."
93
- 2. "This next attempt is meaningfully different from the last one."
94
- 3. "I know what outcome I am trying to reach."
95
- 4. "Continuing this task now is the best use of my attention."
96
- 5. "I am ready to pause and restate the goal."
97
- 6. "The current approach is moving me closer to done."
98
- 7. "I have enough context to keep going."
99
- 8. "I want to keep working on this thread."
100
- 9. "A short reset would help me choose the next step."
101
-
102
- ### Major or irreversible decision statement templates
103
-
104
- 1. "I am ready to deploy this to production."
105
- 2. "I trust this decision."
106
- 3. "I am ready to send this message as written."
107
- 4. "I understand the consequences of this action."
108
- 5. "I have the information I need to commit."
109
- 6. "This purchase is the right choice right now."
110
- 7. "I am ready to make this offer."
111
- 8. "I am comfortable making this change irreversible."
112
- 9. "I want to act on this now."
113
-
114
- ### Stress-signal statement templates
115
-
116
- 1. "I want to keep pushing on this right now."
117
- 2. "I trust the next step I am about to take."
118
- 3. "I have enough clarity to choose the next step."
119
- 4. "I am ready to slow down for one minute."
120
- 5. "This approach still feels right to me."
121
- 6. "I want to send this message in this tone."
122
- 7. "I am comfortable continuing without another review."
123
- 8. "I know what I need from this task."
124
- 9. "I want to make this decision now."
125
-
126
- Adapt a template only enough to make it relevant. Keep the statement free of sensitive specifics.
127
-
128
- ## Cooldown rules
129
-
130
- - One offer per trigger event, whether accepted, declined, or ignored.
131
- - After a decline, suppress trigger 1 and trigger 3 offers for two hours.
132
- - A new imminent trigger 2 event may bypass that two-hour cooldown.
133
- - The one-pending gate always wins. A completed, expired, or cancelled request has no server cooldown; a new direct user request may be sent immediately.
134
- - A continued conversation about the same task or decision is not a new event. A materially new goal, a different consequential action, or a new message-pattern episode can be.
135
-
136
- ## What to do with the result
137
-
138
- `request_vibecheck` waits boundedly for the exact result. If it returns `completed`, use the result as context and respond naturally in the current conversation. The result is not authorization and does not establish a diagnosis or certainty. If the tool returns `pending`, retain the exact `live:<uuid>` handle. Do not promise that you will check back automatically unless the current host supports automatic result delivery. Use `get_vibecheck_result` once when the person asks or at a natural follow-up. Never create a replacement request and never poll in a tight loop.
139
-
140
- For `expired` or `cancelled`, do not infer an answer and do not immediately repost.
141
-
142
- A result never grants permission to deploy, send, sign, delete, spend, hire, fire, publish, or take any other consequential action.
143
-
144
- ## Do not
145
-
146
- - Do not offer without one of the three transcript-grounded triggers.
147
- - Do not ignore or reconfirm an explicit request for one vibecheck.
148
- - Do not claim to know the person's internal state.
149
- - Do not nag, stack offers, or re-offer for the same event.
150
- - Do not proactively offer when a request is pending.
151
- - Do not send an unsolicited offer before the human accepts the exact statement.
152
- - Do not use `post_vibecheck_statement` for a live ask.
153
- - Do not send raw conversation text, secrets, identifiers, or private details to the server.
154
- - Do not call `share_somacheck_context` in this workflow.
155
- - Do not treat any outcome as fact, diagnosis, proof, or authorization.
156
- - Do not continuously poll for a result.
157
- - Do not use medical language or an em dash in a human-facing string.
25
+ If a request is pending, keep the returned `live:<uuid>` handle instead of creating a replacement.
@@ -10,7 +10,7 @@
10
10
  "name": "vibecheck",
11
11
  "source": "./plugins/vibecheck",
12
12
  "description": "Let Claude offer a vibecheck and continue when the phone result arrives.",
13
- "version": "0.6.5",
13
+ "version": "0.6.6",
14
14
  "author": {
15
15
  "name": "Sensie",
16
16
  "email": "agents@joinsensie.com"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vibecheck",
3
3
  "description": "Let Claude offer a SomaCheck vibecheck and continue when the phone result arrives.",
4
- "version": "0.6.5",
4
+ "version": "0.6.6",
5
5
  "author": {
6
6
  "name": "Sensie",
7
7
  "email": "agents@joinsensie.com"
@@ -7,7 +7,7 @@
7
7
  "hooks": [
8
8
  {
9
9
  "type": "command",
10
- "command": "npx -y @somacheck/vibecheck@0.6.5 claude-hook await-result",
10
+ "command": "npx -y @somacheck/vibecheck@0.6.6 claude-hook await-result",
11
11
  "asyncRewake": true,
12
12
  "timeout": 900
13
13
  }
package/dist/api.js CHANGED
@@ -8,6 +8,7 @@ const LIVE_ASK_CLIENT_REQUIRED_MARKER = "SC_VC_VIBECHECK_CLIENT_REQUIRED";
8
8
  const LIVE_ASK_CLIENT_NOT_READY_MARKER = "SC_VC_VIBECHECK_CLIENT_NOT_READY";
9
9
  const STATUSES = new Set(["queued", "pending", "answered", "expired", "cancelled"]);
10
10
  const VERDICTS = new Set(["aligned", "unaligned"]);
11
+ const USER_FEEDBACKS = new Set(["agreed", "disagreed", "not_provided"]);
11
12
  const HANDSHAKE_READINESS_STATUSES = new Set([
12
13
  "ready",
13
14
  "upgrade_required",
@@ -222,6 +223,7 @@ export function decodePollResponse(value) {
222
223
  const verdict = row.verdict;
223
224
  const confidence = row.confidence;
224
225
  const latency = row.latency_s;
226
+ const userFeedback = row.user_feedback ?? null;
225
227
  if (verdict !== null && (typeof verdict !== "string" || !VERDICTS.has(verdict))) {
226
228
  throw new Error("SomaCheck returned an invalid verdict.");
227
229
  }
@@ -231,6 +233,9 @@ export function decodePollResponse(value) {
231
233
  if (latency !== null && (typeof latency !== "number" || !Number.isFinite(latency) || latency < 0)) {
232
234
  throw new Error("SomaCheck returned an invalid latency.");
233
235
  }
236
+ if (userFeedback !== null && (typeof userFeedback !== "string" || !USER_FEEDBACKS.has(userFeedback))) {
237
+ throw new Error("SomaCheck returned invalid user feedback.");
238
+ }
234
239
  if (status === "answered") {
235
240
  if (verdict === null || confidence === null || confidence < 0 || confidence > 1 || latency === null) {
236
241
  throw new Error("SomaCheck returned an incomplete answered result.");
@@ -239,12 +244,21 @@ export function decodePollResponse(value) {
239
244
  else if (verdict !== null || confidence !== null || latency !== null) {
240
245
  throw new Error("SomaCheck returned an inconsistent pending result.");
241
246
  }
242
- return {
247
+ if (status !== "answered" && userFeedback !== null) {
248
+ throw new Error("SomaCheck returned inconsistent feedback for an unanswered result.");
249
+ }
250
+ const decoded = {
243
251
  status: status,
244
252
  verdict: verdict,
245
253
  confidence: confidence,
246
254
  latency_s: latency,
247
255
  };
256
+ if (userFeedback === null)
257
+ return decoded;
258
+ return {
259
+ ...decoded,
260
+ user_feedback: userFeedback,
261
+ };
248
262
  }
249
263
  export function decodeLiveVibecheckResult(value, expectedRequestId) {
250
264
  const row = firstRow(value);
@@ -347,7 +361,7 @@ export class SupabaseAgentApi {
347
361
  const lifecycle = created.status === "answered"
348
362
  ? await this.liveVibecheckResult(token, identity, created.request_id, signal)
349
363
  : { status: created.status, verdict: null, confidence: null, latency_s: null };
350
- return {
364
+ const liveRequest = {
351
365
  request_id: `live:${created.request_id}`,
352
366
  state: lifecycle.status === "answered" ? "completed" : lifecycle.status,
353
367
  verdict: lifecycle.verdict,
@@ -357,6 +371,9 @@ export class SupabaseAgentApi {
357
371
  delivery_state: created.push_status,
358
372
  cooldown_until: created.cooldown_until,
359
373
  };
374
+ if (lifecycle.user_feedback === undefined || lifecycle.user_feedback === null)
375
+ return liveRequest;
376
+ return { ...liveRequest, user_feedback: lifecycle.user_feedback };
360
377
  }
361
378
  async createRequests(token, statements) {
362
379
  return decodeCreatedRequests(await this.#rpcJson("agent_proposition_batch_create", { token, statements }));
@@ -17,8 +17,8 @@ export function parsePendingClaudeAsk(input) {
17
17
  if (typeof envelope.tool_name !== "string"
18
18
  || !envelope.tool_name.endsWith("__request_vibecheck"))
19
19
  return null;
20
- const response = record(envelope.tool_response);
21
- const structured = record(response.structuredContent ?? response.structured_content);
20
+ const response = parseToolResponse(envelope.tool_response);
21
+ const structured = record(response.structuredContent ?? response.structured_content ?? response);
22
22
  if (structured.state !== "pending" || typeof structured.request_id !== "string")
23
23
  return null;
24
24
  if (structured.delivery_state === "failed" || structured.delivery_state === "skipped")
@@ -78,8 +78,8 @@ export async function awaitClaudeVibecheckResult(input, dependencies) {
78
78
  continue;
79
79
  const handle = `live:${pending.requestId}`;
80
80
  const message = result.status === "answered"
81
- ? `SomaCheck result for ${handle}: ${result.verdict} (confidence ${formatConfidence(result.confidence)}). Use it as context in the current conversation. It is not authorization and does not establish a diagnosis or certainty.`
82
- : `SomaCheck request ${handle} is ${result.status}. Do not infer an answer or create a replacement request for the same decision.`;
81
+ ? `SomaCheck vibecheck ${handle} completed: ${result.verdict}, confidence ${formatConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
82
+ : `SomaCheck vibecheck ${handle} is ${result.status}.`;
83
83
  await writeTerminalReceipt(donePath, result);
84
84
  return { exitCode: 2, message };
85
85
  }
@@ -122,9 +122,21 @@ async function writeTerminalReceipt(path, result) {
122
122
  status: result.status,
123
123
  verdict: result.verdict,
124
124
  confidence: result.confidence,
125
+ user_feedback: result.user_feedback ?? null,
125
126
  })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
126
127
  await rename(temporary, path);
127
128
  }
129
+ function parseToolResponse(value) {
130
+ if (typeof value === "string") {
131
+ try {
132
+ return record(JSON.parse(value));
133
+ }
134
+ catch {
135
+ return {};
136
+ }
137
+ }
138
+ return record(value);
139
+ }
128
140
  function record(value) {
129
141
  return value !== null && typeof value === "object" ? value : {};
130
142
  }
@@ -137,6 +149,9 @@ function parseHandle(handle) {
137
149
  function formatConfidence(value) {
138
150
  return value === null ? "unavailable" : value.toFixed(2);
139
151
  }
152
+ function formatUserFeedback(feedback) {
153
+ return feedback === undefined || feedback === null ? "" : `, user feedback ${feedback}`;
154
+ }
140
155
  async function exists(path) {
141
156
  try {
142
157
  await readFile(path);
@@ -144,7 +144,8 @@ function isManagedSomaCheckRegistration(client, stdout) {
144
144
  || packageArg === "@somacheck/vibecheck@0.6.1"
145
145
  || packageArg === "@somacheck/vibecheck@0.6.2"
146
146
  || packageArg === "@somacheck/vibecheck@0.6.3"
147
- || packageArg === "@somacheck/vibecheck@0.6.4")
147
+ || packageArg === "@somacheck/vibecheck@0.6.4"
148
+ || packageArg === "@somacheck/vibecheck@0.6.5")
148
149
  && args.length === 5
149
150
  && args[0] === "-y"
150
151
  && args[1] === packageArg
package/dist/constants.js CHANGED
@@ -2,7 +2,7 @@
2
2
  export const SUPABASE_URL = "https://pbldcmniommltbdwuykk.supabase.co";
3
3
  export const SUPABASE_PUBLISHABLE_KEY = "sb_publishable_af-lUNI2FqEcb-oGy-4uxQ_cnm6kY85";
4
4
  export const PACKAGE_NAME = "@somacheck/vibecheck";
5
- export const PACKAGE_VERSION = "0.6.5";
5
+ export const PACKAGE_VERSION = "0.6.6";
6
6
  export const PACKAGE_SPEC = `${PACKAGE_NAME}@${PACKAGE_VERSION}`;
7
7
  export const MCP_SERVER_NAME = "vibecheck";
8
8
  export const LEGACY_HOSTED_MCP_SERVER_NAME = "somacheck";
package/dist/server.js CHANGED
@@ -61,6 +61,7 @@ const resultSchema = {
61
61
  verdict: z.enum(["aligned", "unaligned"]).nullable(),
62
62
  confidence: z.number().nullable(),
63
63
  latency_s: z.number().nullable(),
64
+ user_feedback: z.enum(["agreed", "disagreed", "not_provided"]).nullable().optional(),
64
65
  };
65
66
  const contextItemSchema = z.object({
66
67
  request_id: z.string(),
@@ -86,6 +87,7 @@ const liveAskSchema = {
86
87
  request_id: z.string().nullable(),
87
88
  verdict: z.enum(["aligned", "unaligned"]).nullable(),
88
89
  confidence: z.number().min(0).max(1).nullable(),
90
+ user_feedback: z.enum(["agreed", "disagreed", "not_provided"]).nullable().optional(),
89
91
  expires_at: z.string().datetime({ offset: true }).nullable(),
90
92
  idempotent_replay: z.boolean().nullable(),
91
93
  delivery_state: z.enum(["queued", "processing", "sent", "failed", "skipped"]).nullable(),
@@ -110,34 +112,18 @@ const contextShareSchema = {
110
112
  captured_at: z.string().datetime({ offset: true }),
111
113
  };
112
114
  const SERVER_INSTRUCTIONS = [
113
- "SomaCheck lets you offer the person a statement to test through a quick phone check-in.",
114
- "MANDATORY COMMAND RULE: when the current user message says 'Give me a SomaCheck vibecheck', 'Give me a check-in, a vibe check', 'Request a SomaCheck vibecheck', or otherwise explicitly asks for one, you must call request_vibecheck in that same turn after any read-only context/status calls. Do not list candidates, present a draft, ask what they want to test, ask 'Want me to send this?', or wait for another yes. The request itself is one-ask consent for you to select and send the statement.",
115
- "Call get_vibecheck_context and get_vibecheck_status at the start of a session or background run, and call status again after a completed check-in.",
116
- "Treat gesture outcomes as contextual signals, never fixed facts or blanket authorization.",
117
- "Use request_vibecheck when a current choice would benefit from the person's own felt response; never use it to evaluate someone else.",
118
- "A setup or link is not blanket consent for future vibechecks.",
119
- "If the person's current message explicitly requests a SomaCheck vibecheck, that message is consent for one ask. If they supplied exact wording, use it. Otherwise, choose one useful statement from the current conversation, goal, decision, or tension and send it immediately; do not ask them to supply wording or confirm twice.",
120
- "DELEGATED SELECTION RULE: before choosing wording, use the reliable context already available to your host: the current conversation, current working goal, and user-approved memory or project context when those capabilities exist. Do not make the person restate context you can already retrieve. Remembered task status may identify a general tension, but the statement itself must not name a project, build, branch, wave, ticket, task status, or supposedly open/closed work. Abstract it into an original present-tense first-person experience or choice. This restriction does not alter exact wording supplied by the person.",
121
- "When choosing the statement, prefer the most consequential unresolved first-person decision or tension you can support with current evidence. Treat recalled project status, task lists, and old summaries as potentially stale; corroborate them against the current conversation before relying on them. Do not reuse a stock example or recent SomaCheck statement. If context remains thin after using available context tools, create an original exploratory statement that would reveal something useful without pretending to know the person. Never diagnose, judge another person, or claim certainty about the user.",
122
- "Otherwise, first show the exact statement and ask: Want a quick vibecheck before I continue? I'll send this statement to your phone; you answer with your wrist gesture, and I'll use the result as a signal—not a verdict. Call only after the person accepts.",
123
- "Use at most one vibecheck per decision. Never re-ask to obtain a preferred answer.",
124
- "An immediate vibecheck is separate from the asynchronous reflection feed. Do not use it to replenish cached propositions.",
125
- "Use a fresh idempotency_key for each new vibecheck, and reuse that exact key only when retrying the same statement.",
126
- "request_vibecheck waits boundedly for the exact answer. If it returns completed, use the result as context in the current conversation. If it returns pending, retain that exact handle and read it once later; never create a replacement ask.",
127
- "If request_vibecheck returns pending, never promise that you will check back on your own unless the host has automatic result delivery. Continue automatically only after an actual result event arrives for that exact request.",
128
- "After a completed result, use it as context in the current conversation and respond naturally. It is not authorization and does not establish a diagnosis or certainty. Do not claim the person's body decided anything.",
129
- "When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
130
- "Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
131
- "Maintain three distinct propositions drawn from your own context about this person.",
132
- "When propositions_needed is greater than zero, post exactly that many in one call.",
133
- "Offer each statement as something to test, never as a claim of fact about the user.",
134
- "Retain every request_id returned by post_vibecheck_statement and call get_vibecheck_result later.",
135
- "Do not poll continuously. Queued means the proposition is cached until the person advances their feed.",
115
+ "SomaCheck lets you ask your person for a vibecheck.",
116
+ "You can proactively offer one when you believe it would be useful, or send one when the person asks.",
117
+ "When the person asks, choose and send a useful first-person statement from the context available to you; do not make them supply wording unless they want to.",
118
+ "Help the person get an insight about themselves from the context you have.",
119
+ "The returned gesture and optional feedback are context, not authorization.",
120
+ "Use your judgment in how you interpret and respond.",
121
+ "Never include secrets, raw private content, diagnostic claims, or use SomaCheck to assess anyone other than the person you are interacting with.",
136
122
  ].join(" ");
137
- const DIRECT_REQUEST_NEXT_ACTION = "DIRECT REQUEST RULE: If the person's current message asks for a SomaCheck vibecheck or check-in, first use reliable context already available to the host, including current conversation and user-approved memory when supported. Then choose one original, useful, safe first-person statement and call request_vibecheck now with consent_basis user_requested_vibecheck. Do not list choices, ask for wording, or seek another confirmation. Ignore reflection-feed propositions_needed for this direct request. Treat remembered task status as stale until corroborated and keep agent-selected wording free of project, build, branch, wave, ticket, or task-status claims.";
138
123
  export function createVibecheckServer(dependencies) {
139
124
  const supportsClaudeChannel = dependencies.identity?.kind === "local"
140
- && dependencies.identity.client_key === "claude";
125
+ && dependencies.identity.client_key === "claude"
126
+ && typeof dependencies.claudeChannelWatch === "object";
141
127
  const server = new McpServer({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, {
142
128
  instructions: SERVER_INSTRUCTIONS,
143
129
  ...(supportsClaudeChannel
@@ -180,12 +166,10 @@ export function createVibecheckServer(dependencies) {
180
166
  const token = await dependencies.loadToken();
181
167
  const checkins = await dependencies.api.contextRequest(token);
182
168
  const text = checkins.length === 0
183
- ? `No completed SomaCheck gestures yet.\n\n${DIRECT_REQUEST_NEXT_ACTION}`
169
+ ? "No completed SomaCheck gestures yet."
184
170
  : [
185
- `Loaded ${checkins.length} completed gesture${checkins.length === 1 ? "" : "s"}. Treat them as contextual signals, not fixed conclusions:`,
171
+ `Loaded ${checkins.length} completed gesture${checkins.length === 1 ? "" : "s"}:`,
186
172
  ...checkins.map((item) => `${Math.round(item.confidence * 100)}% ${item.verdict} — ${JSON.stringify(item.statement)}`),
187
- "",
188
- DIRECT_REQUEST_NEXT_ACTION,
189
173
  ].join("\n");
190
174
  return { content: [{ type: "text", text }], structuredContent: { checkins } };
191
175
  }
@@ -211,7 +195,7 @@ export function createVibecheckServer(dependencies) {
211
195
  return {
212
196
  content: [{
213
197
  type: "text",
214
- text: `${summarise(status)}\n\n${DIRECT_REQUEST_NEXT_ACTION}`,
198
+ text: summarise(status),
215
199
  }],
216
200
  structuredContent: { ...status },
217
201
  };
@@ -305,7 +289,7 @@ export function createVibecheckServer(dependencies) {
305
289
  const text = result.status === "answered"
306
290
  ? liveRequestId === null
307
291
  ? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
308
- : `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Use it as context in the current conversation. It is not authorization and does not establish a diagnosis or certainty.`
292
+ : `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
309
293
  : result.status === "queued"
310
294
  ? "Queued. The proposition is cached until the person advances their feed."
311
295
  : result.status === "pending"
@@ -319,14 +303,14 @@ export function createVibecheckServer(dependencies) {
319
303
  });
320
304
  server.registerTool("request_vibecheck", {
321
305
  title: "Request a Vibecheck",
322
- description: "Send one plain-language statement to the person's phone for a vibecheck answered with a wrist gesture. A current user message that explicitly requests a SomaCheck vibecheck is confirmation for one ask. Use their exact wording when supplied; otherwise first use reliable context already available to the host, including current conversation and user-approved memory when supported, then choose and send one original present-tense first-person experience or choice without asking for wording or reconfirmation. Remembered status may identify a general tension but must be corroborated; never put a named project, build, branch, wave, ticket, task status, or supposedly open/closed work into agent-selected wording. Do not reuse a stock example or recent SomaCheck statement. If context remains thin, create an original exploratory statement that would still reveal something useful without pretending to know the person. Otherwise, first show the exact statement and call only after the person accepts. Setup or linking is not blanket consent. Use this only when their own felt response would help with the current choice, not for routine feed content, diagnosis, or judging another person. The call waits boundedly for the exact result and returns completed when available. A timeout returns the same stable pending request_id for one later read. Caller cancellation stops result reads without creating another request; the cancelled transport may suppress the pending response.",
306
+ description: "Send one statement to the person's phone for a SomaCheck vibecheck. If the person asks for a vibecheck, choose a useful first-person statement from your available context and send it. For proactive offers, call only after the person accepts. The result is context, not authorization.",
323
307
  inputSchema: {
324
308
  statement: z.string().trim().min(1).max(1000)
325
- .describe("One plain-language statement for the person to test; offer a signal, not a fact, diagnosis, or instruction. If the agent selected the wording, it must be a present-tense first-person experience or choice and must not name remembered projects, builds, branches, waves, tickets, task status, or supposedly open/closed work. Preserve exact wording supplied by the person."),
309
+ .describe("One plain-language first-person statement for the person to test. Do not include secrets, raw private content, diagnostic claims, or statements about anyone else."),
326
310
  idempotency_key: z.string().uuid()
327
311
  .describe("A new UUID for this logical ask. Reuse the same UUID only to retry the exact same statement; retries will not create another phone request."),
328
312
  consent_basis: z.enum(["user_requested_vibecheck", "user_approved_statement"])
329
- .describe("Required one-ask consent basis. Use user_requested_vibecheck when the current message asks for a SomaCheck vibecheck, including when the agent chooses the statement; call immediately without another confirmation. Use user_approved_statement only after the agent proposed exact wording and the person accepted it."),
313
+ .describe("Use user_requested_vibecheck when the person asks for a vibecheck. Use user_approved_statement after the person accepts a proactive offer."),
330
314
  },
331
315
  outputSchema: liveAskSchema,
332
316
  annotations: {
@@ -375,12 +359,12 @@ export function createVibecheckServer(dependencies) {
375
359
  startClaudeChannelWatch(result, token, identity);
376
360
  const structuredContent = { ...result, error_code: null };
377
361
  const text = result.state === "completed"
378
- ? `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Use it as context in the current conversation. It is not authorization and does not establish a diagnosis or certainty.`
362
+ ? `Vibecheck completed: ${result.verdict}, confidence ${formatChannelConfidence(result.confidence)}${formatUserFeedback(result.user_feedback)}.`
379
363
  : result.state === "pending"
380
364
  ? result.delivery_state === "failed" || result.delivery_state === "skipped"
381
- ? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. It expires at ${result.expires_at}. Do not repost it or change the idempotency_key; check the phone setup before a later decision.`
382
- : `Vibecheck ${result.request_id} is still pending after a bounded wait. Keep this exact handle and check it once later with get_vibecheck_result. It expires at ${result.expires_at}; do not poll continuously, re-ask for this decision, or promise an automatic follow-up unless the host delivers an actual result event.`
383
- : `This vibecheck is ${result.state}. Do not create another ask for the same decision.`;
365
+ ? `Vibecheck ${result.request_id} was stored, but phone delivery is ${result.delivery_state}. It expires at ${result.expires_at}.`
366
+ : `Vibecheck ${result.request_id} is pending. It expires at ${result.expires_at}.`
367
+ : `This vibecheck is ${result.state}.`;
384
368
  return { content: [{ type: "text", text }], structuredContent };
385
369
  }
386
370
  catch (error) {
@@ -430,8 +414,8 @@ async function watchForClaudeChannelResult(created, token, identity, dependencie
430
414
  continue;
431
415
  const handle = `live:${requestId}`;
432
416
  const content = lifecycle.status === "answered"
433
- ? `A SomaCheck vibecheck requested by this Claude connection has completed. The exact result is ${lifecycle.verdict} with confidence ${formatChannelConfidence(lifecycle.confidence)}. Use it as context in the current conversation. It is not authorization and does not establish a diagnosis or certainty. The exact opaque request handle is ${handle}. Do not request another vibecheck for this decision.`
434
- : `A SomaCheck vibecheck requested by this Claude connection is now ${lifecycle.status}. The exact opaque request handle is ${handle}. Do not create a replacement request for the same decision.`;
417
+ ? `SomaCheck vibecheck ${handle} completed: ${lifecycle.verdict}, confidence ${formatChannelConfidence(lifecycle.confidence)}${formatUserFeedback(lifecycle.user_feedback)}.`
418
+ : `SomaCheck vibecheck ${handle} is ${lifecycle.status}.`;
435
419
  try {
436
420
  await notify({
437
421
  method: "notifications/claude/channel",
@@ -463,6 +447,9 @@ function unrefSleep(milliseconds) {
463
447
  function formatChannelConfidence(confidence) {
464
448
  return confidence === null ? "unavailable" : confidence.toFixed(2);
465
449
  }
450
+ function formatUserFeedback(feedback) {
451
+ return feedback === undefined || feedback === null ? "" : `, user feedback ${feedback}`;
452
+ }
466
453
  async function waitForLiveVibecheck(created, token, identity, dependencies, signal, deadline, progress) {
467
454
  if (created.state !== "pending"
468
455
  || created.delivery_state === "failed"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somacheck/vibecheck",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Send a consented vibecheck to SomaCheck and use the result as a signal.",
5
5
  "repository": {
6
6
  "type": "git",