@somacheck/vibecheck 0.6.3 → 0.6.5

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,11 +9,13 @@ 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.3 link <CODE> --client <CLIENT>`
12
+ **What the `link` command does.** `npx -y @somacheck/vibecheck@0.6.5 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
16
- connection without printing the token. Setup is not reported ready until the
16
+ connection without printing the token. For Claude Code it also installs an
17
+ inspectable user-scoped SomaCheck plugin that can wake the same open session when
18
+ the phone result arrives. Setup is not reported ready until the
17
19
  selected client has the exact pinned `vibecheck` MCP registration, the backend
18
20
  accepts protocol `5` / toolset `vibecheck-0.6`, and both status and context
19
21
  probes succeed. It contacts exactly one network host: the Supabase project below.
@@ -28,7 +30,8 @@ with. The safe paths are:
28
30
  `--client claude` or `--client codex`.
29
31
 
30
32
  For the second path, this package first proves that exact client CLI is installed
31
- and that its persistent MCP config can be inspected and written. If that preflight
33
+ and that its persistent MCP config can be inspected and written. Claude setup
34
+ also verifies the user-scoped continuation plugin before redemption. If that preflight
32
35
  fails, the pairing code is not used. Non-interactive implicit selection,
33
36
  `--client all`, and `--client none` are rejected before redemption.
34
37
 
@@ -78,9 +81,9 @@ before the pairing code is redeemed.
78
81
  To configure or repair a client later:
79
82
 
80
83
  ```text
81
- npx -y @somacheck/vibecheck@0.6.3 setup codex
82
- npx -y @somacheck/vibecheck@0.6.3 setup claude
83
- npx -y @somacheck/vibecheck@0.6.3 doctor
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
87
  ```
85
88
 
86
89
  Manual registration remains available:
@@ -90,13 +93,13 @@ Manual registration remains available:
90
93
  "mcpServers": {
91
94
  "vibecheck": {
92
95
  "command": "npx",
93
- "args": ["-y", "@somacheck/vibecheck@0.6.3", "serve", "--client", "codex"]
96
+ "args": ["-y", "@somacheck/vibecheck@0.6.5", "serve", "--client", "codex"]
94
97
  }
95
98
  }
96
99
  }
97
100
  ```
98
101
 
99
- For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.3 serve --client claude`
102
+ For Claude Code: `claude mcp add --scope user vibecheck -- npx -y @somacheck/vibecheck@0.6.5 serve --client claude`
100
103
 
101
104
  The link step writes only the bearer token in `~/.sensie/config.json`. The client
102
105
  setup step asks Codex or Claude to add the pinned MCP command to that client's own
@@ -191,31 +194,26 @@ idempotency key, but the cancelled MCP transport may suppress that pending
191
194
  response. Do not continuously poll or re-ask the same decision to obtain a
192
195
  preferred result.
193
196
 
194
- Standard MCP cannot start a new agent turn after that bounded tool call has
195
- ended. The local Claude Code runtime therefore also advertises Anthropic's
196
- opt-in `claude/channel` capability. When Claude Code is launched with the
197
- SomaCheck server enabled as a channel, one local background watcher follows
198
- only the exact pending request. It emits one hardcoded, secret-free channel
199
- event when that request becomes answered, expired, or cancelled. The event
200
- contains the constrained lifecycle result and opaque handle, never the private
201
- statement or link token, and it cannot create another phone request.
202
-
203
- Claude Code channels are currently a research preview and only receive events
204
- while the opted-in session remains open. For local QA of this candidate, launch
205
- Claude Code with `--dangerously-load-development-channels server:vibecheck`.
206
- Production distribution requires the SomaCheck channel plugin to be accepted by
207
- Anthropic's channel allowlist (or explicitly allowed by an organization).
208
- Claude Desktop, hosted connectors, Codex, and other standard MCP hosts retain
209
- the bounded tool response and stable-handle fallback; this Claude-specific
210
- channel must never be presented as provider-neutral behavior.
211
-
212
- The 45-second whole-call deadline also covers the one create request. If that
197
+ Standard MCP cannot start a new agent turn after a tool call has ended. The
198
+ Claude setup therefore installs a normal user-scoped plugin with an async
199
+ `PostToolUse` hook. When `request_vibecheck` returns pending, that hook follows
200
+ only the exact request and wakes the same open Claude Code session when the
201
+ answer, expiry, or cancellation arrives. It passes the constrained result and
202
+ opaque handle, never the statement or link token, and cannot create another
203
+ phone request. No experimental launch flag is required. Restart Claude Code
204
+ once after setup so it loads the plugin.
205
+
206
+ Other standard MCP hosts retain the bounded tool response and stable-handle
207
+ fallback until they provide an equivalent supported wake mechanism. This
208
+ Claude-specific continuation must never be presented as provider-neutral.
209
+
210
+ The 10-second whole-call deadline also covers the one create request. If that
213
211
  network request is aborted after an ambiguous commit, retry the exact same
214
212
  statement with the same `idempotency_key`; never create a replacement ask.
215
213
 
216
- After a completed result, explain what the signal changes in your next action.
217
- Never treat it as authorization or say that the person's body made the
218
- decision. A typed response from the person always overrides the signal.
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.
219
217
 
220
218
  An immediate vibecheck is not proposition caching. It sends one person-approved statement
221
219
  for the current choice, expires, and remains bound to its originating client.
package/SKILL.md ADDED
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: vibecheck
3
+ description: Request or offer a SomaCheck vibecheck at useful moments without nagging.
4
+ user-invocable: true
5
+ allowed-tools: mcp__vibecheck__get_vibecheck_context mcp__vibecheck__get_vibecheck_status mcp__vibecheck__get_vibecheck_result mcp__vibecheck__request_vibecheck
6
+ ---
7
+
8
+ # Vibecheck
9
+
10
+ Use SomaCheck through one of two entry points:
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.
14
+
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.
16
+
17
+ ## Direct requests
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"`.
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.
28
+
29
+ ## When to offer
30
+
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.
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "somacheck-local",
3
+ "owner": {
4
+ "name": "Sensie",
5
+ "email": "agents@joinsensie.com"
6
+ },
7
+ "description": "SomaCheck integration installed by the official vibecheck package.",
8
+ "plugins": [
9
+ {
10
+ "name": "vibecheck",
11
+ "source": "./plugins/vibecheck",
12
+ "description": "Let Claude offer a vibecheck and continue when the phone result arrives.",
13
+ "version": "0.6.5",
14
+ "author": {
15
+ "name": "Sensie",
16
+ "email": "agents@joinsensie.com"
17
+ },
18
+ "category": "productivity",
19
+ "tags": ["vibecheck", "somacheck", "decision-support", "mcp"]
20
+ }
21
+ ]
22
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "vibecheck",
3
+ "description": "Let Claude offer a SomaCheck vibecheck and continue when the phone result arrives.",
4
+ "version": "0.6.5",
5
+ "author": {
6
+ "name": "Sensie",
7
+ "email": "agents@joinsensie.com"
8
+ },
9
+ "homepage": "https://github.com/sensie-app/Somacheck/tree/main/packages/vibecheck",
10
+ "repository": "https://github.com/sensie-app/Somacheck",
11
+ "license": "UNLICENSED",
12
+ "keywords": ["vibecheck", "somacheck", "decision-support", "mcp"]
13
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "description": "Continue an ordinary Claude Code session when a SomaCheck result arrives.",
3
+ "hooks": {
4
+ "PostToolUse": [
5
+ {
6
+ "matcher": "mcp__vibecheck__request_vibecheck",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "npx -y @somacheck/vibecheck@0.6.5 claude-hook await-result",
11
+ "asyncRewake": true,
12
+ "timeout": 900
13
+ }
14
+ ]
15
+ }
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,152 @@
1
+ import { mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { readConfig } from "./config.js";
4
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
5
+ const POLL_INTERVAL_MS = 2_000;
6
+ const MAX_WATCH_MS = 15 * 60 * 1_000;
7
+ const READ_TIMEOUT_MS = 10_000;
8
+ const STALE_LEASE_MS = 20 * 60 * 1_000;
9
+ export function parsePendingClaudeAsk(input) {
10
+ let envelope;
11
+ try {
12
+ envelope = JSON.parse(input);
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ if (typeof envelope.tool_name !== "string"
18
+ || !envelope.tool_name.endsWith("__request_vibecheck"))
19
+ return null;
20
+ const response = record(envelope.tool_response);
21
+ const structured = record(response.structuredContent ?? response.structured_content);
22
+ if (structured.state !== "pending" || typeof structured.request_id !== "string")
23
+ return null;
24
+ if (structured.delivery_state === "failed" || structured.delivery_state === "skipped")
25
+ return null;
26
+ const requestId = parseHandle(structured.request_id);
27
+ if (requestId === null)
28
+ return null;
29
+ return {
30
+ requestId,
31
+ expiresAt: typeof structured.expires_at === "string" ? structured.expires_at : null,
32
+ };
33
+ }
34
+ export async function awaitClaudeVibecheckResult(input, dependencies) {
35
+ const pending = parsePendingClaudeAsk(input);
36
+ if (pending === null)
37
+ return { exitCode: 0, message: null };
38
+ const receipts = join(dependencies.home, ".sensie", "claude-result-hooks");
39
+ await mkdir(receipts, { recursive: true, mode: 0o700 });
40
+ const donePath = join(receipts, `${pending.requestId}.done.json`);
41
+ const leasePath = join(receipts, `${pending.requestId}.lease`);
42
+ if (await exists(donePath))
43
+ return { exitCode: 0, message: null };
44
+ if (!(await claimLease(leasePath, dependencies.now ?? Date.now))) {
45
+ return { exitCode: 0, message: null };
46
+ }
47
+ try {
48
+ const token = (await readConfig(dependencies.home, "claude")).token;
49
+ const now = dependencies.now ?? Date.now;
50
+ const sleep = dependencies.sleep ?? delay;
51
+ const maxWatchMs = dependencies.maxWatchMs ?? MAX_WATCH_MS;
52
+ const pollIntervalMs = dependencies.pollIntervalMs ?? POLL_INTERVAL_MS;
53
+ const parsedExpiry = pending.expiresAt === null ? Number.NaN : Date.parse(pending.expiresAt);
54
+ const deadline = Math.min(Number.isFinite(parsedExpiry) ? parsedExpiry : now() + maxWatchMs, now() + maxWatchMs);
55
+ let consecutiveFailures = 0;
56
+ while (now() < deadline) {
57
+ await sleep(Math.min(pollIntervalMs, Math.max(0, deadline - now())));
58
+ if (now() >= deadline)
59
+ break;
60
+ let result;
61
+ const abort = new AbortController();
62
+ const timeout = setTimeout(() => abort.abort(new Error("SomaCheck result read timed out.")), READ_TIMEOUT_MS);
63
+ timeout.unref?.();
64
+ try {
65
+ result = await dependencies.api.liveVibecheckResult(token, { kind: "local", client_key: "claude" }, pending.requestId, abort.signal);
66
+ consecutiveFailures = 0;
67
+ }
68
+ catch {
69
+ consecutiveFailures += 1;
70
+ if (consecutiveFailures >= 3)
71
+ return { exitCode: 0, message: null };
72
+ continue;
73
+ }
74
+ finally {
75
+ clearTimeout(timeout);
76
+ }
77
+ if (result.status === "pending")
78
+ continue;
79
+ const handle = `live:${pending.requestId}`;
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.`;
83
+ await writeTerminalReceipt(donePath, result);
84
+ return { exitCode: 2, message };
85
+ }
86
+ return { exitCode: 0, message: null };
87
+ }
88
+ finally {
89
+ await rm(leasePath, { force: true });
90
+ }
91
+ }
92
+ async function claimLease(path, now) {
93
+ try {
94
+ const handle = await open(path, "wx", 0o600);
95
+ await handle.writeFile(`${JSON.stringify({ started_at_ms: now() })}\n`, "utf8");
96
+ await handle.close();
97
+ return true;
98
+ }
99
+ catch (error) {
100
+ if (error.code !== "EEXIST")
101
+ throw error;
102
+ }
103
+ try {
104
+ const metadata = await stat(path);
105
+ if (now() - metadata.mtimeMs <= STALE_LEASE_MS)
106
+ return false;
107
+ await rm(path, { force: true });
108
+ const handle = await open(path, "wx", 0o600);
109
+ await handle.writeFile(`${JSON.stringify({ started_at_ms: now() })}\n`, "utf8");
110
+ await handle.close();
111
+ return true;
112
+ }
113
+ catch (error) {
114
+ if (error.code === "EEXIST")
115
+ return false;
116
+ throw error;
117
+ }
118
+ }
119
+ async function writeTerminalReceipt(path, result) {
120
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
121
+ await writeFile(temporary, `${JSON.stringify({
122
+ status: result.status,
123
+ verdict: result.verdict,
124
+ confidence: result.confidence,
125
+ })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
126
+ await rename(temporary, path);
127
+ }
128
+ function record(value) {
129
+ return value !== null && typeof value === "object" ? value : {};
130
+ }
131
+ function parseHandle(handle) {
132
+ if (!handle.startsWith("live:"))
133
+ return null;
134
+ const requestId = handle.slice(5);
135
+ return UUID_PATTERN.test(requestId) ? requestId : null;
136
+ }
137
+ function formatConfidence(value) {
138
+ return value === null ? "unavailable" : value.toFixed(2);
139
+ }
140
+ async function exists(path) {
141
+ try {
142
+ await readFile(path);
143
+ return true;
144
+ }
145
+ catch {
146
+ return false;
147
+ }
148
+ }
149
+ function delay(milliseconds) {
150
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
151
+ }
152
+ //# sourceMappingURL=claude-hook.js.map
@@ -0,0 +1,114 @@
1
+ import { cp, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ export const CLAUDE_PLUGIN_MARKETPLACE = "somacheck-local";
5
+ export const CLAUDE_PLUGIN_ID = `vibecheck@${CLAUDE_PLUGIN_MARKETPLACE}`;
6
+ const MANAGED_MARKER = ".somacheck-managed-plugin-marketplace";
7
+ export async function configureClaudeContinuationPlugin(home, runner, packageRoot = fileURLToPath(new URL("..", import.meta.url))) {
8
+ try {
9
+ const destination = join(home, ".sensie", "claude-plugin-marketplace");
10
+ await installManagedMarketplace(packageRoot, destination);
11
+ const marketplaces = await jsonCommand(runner, ["plugin", "marketplace", "list", "--json"]);
12
+ const marketplaceExists = Array.isArray(marketplaces)
13
+ && marketplaces.some((item) => record(item).name === CLAUDE_PLUGIN_MARKETPLACE);
14
+ if (marketplaceExists) {
15
+ if ((await runner.run("claude", ["plugin", "marketplace", "update", CLAUDE_PLUGIN_MARKETPLACE])).exitCode !== 0) {
16
+ return { status: "failed" };
17
+ }
18
+ }
19
+ else if ((await runner.run("claude", [
20
+ "plugin", "marketplace", "add", "--scope", "user", destination,
21
+ ])).exitCode !== 0) {
22
+ return { status: "failed" };
23
+ }
24
+ const plugins = await jsonCommand(runner, ["plugin", "list", "--json"]);
25
+ const installed = Array.isArray(plugins)
26
+ && plugins.some((item) => record(item).id === CLAUDE_PLUGIN_ID && record(item).scope === "user");
27
+ const action = installed ? "update" : "install";
28
+ const result = await runner.run("claude", ["plugin", action, "--scope", "user", "--yes", CLAUDE_PLUGIN_ID]);
29
+ if (result.exitCode !== 0)
30
+ return { status: "failed" };
31
+ const verified = await jsonCommand(runner, ["plugin", "list", "--json"]);
32
+ const current = Array.isArray(verified)
33
+ && verified.some((item) => {
34
+ const value = record(item);
35
+ return value.id === CLAUDE_PLUGIN_ID && value.scope === "user" && value.enabled !== false;
36
+ });
37
+ return current
38
+ ? { status: installed ? "updated" : "installed" }
39
+ : { status: "failed" };
40
+ }
41
+ catch {
42
+ return { status: "failed" };
43
+ }
44
+ }
45
+ async function installManagedMarketplace(packageRoot, destination) {
46
+ const template = join(packageRoot, "claude-marketplace");
47
+ const skill = join(packageRoot, "SKILL.md");
48
+ await stat(join(template, ".claude-plugin", "marketplace.json"));
49
+ await stat(skill);
50
+ try {
51
+ await stat(destination);
52
+ await stat(join(destination, MANAGED_MARKER));
53
+ }
54
+ catch (error) {
55
+ const missing = error.code === "ENOENT";
56
+ if (!missing || await pathExists(destination)) {
57
+ throw new Error("SomaCheck will not overwrite an unmanaged Claude plugin directory.");
58
+ }
59
+ }
60
+ await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
61
+ const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
62
+ const backup = `${destination}.${process.pid}.${Date.now()}.backup`;
63
+ try {
64
+ await cp(template, temporary, { recursive: true, errorOnExist: true, force: false });
65
+ const skillDestination = join(temporary, "plugins", "vibecheck", "skills", "vibecheck");
66
+ await mkdir(skillDestination, { recursive: true, mode: 0o700 });
67
+ await cp(skill, join(skillDestination, "SKILL.md"), { errorOnExist: true, force: false });
68
+ await writeFile(join(temporary, MANAGED_MARKER), "managed by @somacheck/vibecheck\n", {
69
+ encoding: "utf8", flag: "wx", mode: 0o600,
70
+ });
71
+ if (await pathExists(destination))
72
+ await rename(destination, backup);
73
+ await rename(temporary, destination);
74
+ await rm(backup, { recursive: true, force: true });
75
+ }
76
+ finally {
77
+ await rm(temporary, { recursive: true, force: true });
78
+ if (await pathExists(backup) && !(await pathExists(destination))) {
79
+ await rename(backup, destination);
80
+ }
81
+ }
82
+ }
83
+ async function jsonCommand(runner, args) {
84
+ const result = await runner.run("claude", args);
85
+ if (result.exitCode !== 0)
86
+ return null;
87
+ try {
88
+ return JSON.parse(result.stdout);
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ async function pathExists(path) {
95
+ try {
96
+ await readFile(path);
97
+ return true;
98
+ }
99
+ catch (error) {
100
+ if (error.code === "EISDIR")
101
+ return true;
102
+ try {
103
+ await stat(path);
104
+ return true;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ }
111
+ function record(value) {
112
+ return value !== null && typeof value === "object" ? value : {};
113
+ }
114
+ //# sourceMappingURL=claude-plugin-setup.js.map
package/dist/cli.js CHANGED
@@ -4,7 +4,9 @@ import { createInterface } from "node:readline/promises";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import { SupabaseAgentApi } from "./api.js";
6
6
  import { clientDisplayName, detectInstalledClients, detectLegacyHostedRegistration, LocalCommandRunner, manualLegacyHostedRemoveCommand, manualRemoveCommand, manualSetupCommand, parseClientChoice, preflightClientPersistence, registerClient, singleNonInteractiveClientSelection, } from "./client-setup.js";
7
+ import { configureClaudeContinuationPlugin } from "./claude-plugin-setup.js";
7
8
  import { readConfig } from "./config.js";
9
+ import { awaitClaudeVibecheckResult } from "./claude-hook.js";
8
10
  import { PACKAGE_SPEC, SUPABASE_PUBLISHABLE_KEY, SUPABASE_URL } from "./constants.js";
9
11
  import { LinkPersistenceError, NonInteractiveLinkError, linkAgent } from "./link.js";
10
12
  import { checkReadiness } from "./readiness.js";
@@ -93,6 +95,13 @@ async function configureClients(clients) {
93
95
  else {
94
96
  output(`✗ Could not configure ${clientDisplayName(client)} automatically.`);
95
97
  }
98
+ if (client === "claude" && ["registered", "upgraded", "already_registered"].includes(result.status)) {
99
+ const plugin = await configureClaudeContinuationPlugin(homedir(), runner);
100
+ if (plugin.status === "failed") {
101
+ throw new ClientPreflightError("Could not configure automatic SomaCheck results for Claude Code. No pairing code was used.");
102
+ }
103
+ output(`✓ Automatic Claude result continuation ${plugin.status === "installed" ? "was configured" : "is ready"}.`);
104
+ }
96
105
  }
97
106
  }
98
107
  async function preflightLinkClients(clients, interactive) {
@@ -136,28 +145,38 @@ async function preflightLinkClients(clients, interactive) {
136
145
  const preflight = await preflightClientPersistence(client, runner);
137
146
  if (preflight.status === "ready") {
138
147
  output(`✓ ${clientDisplayName(client)} already has the exact SomaCheck MCP registration.`);
139
- continue;
140
148
  }
141
- if (preflight.status === "registered" || preflight.status === "upgraded") {
149
+ else if (preflight.status === "registered" || preflight.status === "upgraded") {
142
150
  const verb = preflight.status === "upgraded" ? "was safely upgraded" : "was configured";
143
151
  output(`✓ ${clientDisplayName(client)} ${verb} before redeeming the pairing code.`);
144
- continue;
145
152
  }
146
- const reason = preflight.status === "not_installed"
147
- ? `${clientDisplayName(client)} is not installed or its CLI is unavailable.`
148
- : preflight.status === "needs_update"
149
- ? `${clientDisplayName(client)} has an existing "vibecheck" MCP entry with a different command or version.`
150
- : `${clientDisplayName(client)} configuration could not be inspected and written.`;
151
- const repair = preflight.status === "needs_update"
152
- ? ["", `Remove the stale entry: ${manualRemoveCommand(client)}`, `Then configure the current version: ${manualSetupCommand(client)}`]
153
- : [];
154
- throw new ClientPreflightError([
155
- `Not linking here: ${reason}`,
156
- ...repair,
157
- "",
158
- "The pairing code has NOT been used. Fix the local client configuration, then",
159
- "run the link command again.",
160
- ].join("\n"));
153
+ else {
154
+ const reason = preflight.status === "not_installed"
155
+ ? `${clientDisplayName(client)} is not installed or its CLI is unavailable.`
156
+ : preflight.status === "needs_update"
157
+ ? `${clientDisplayName(client)} has an existing "vibecheck" MCP entry with a different command or version.`
158
+ : `${clientDisplayName(client)} configuration could not be inspected and written.`;
159
+ const repair = preflight.status === "needs_update"
160
+ ? ["", `Remove the stale entry: ${manualRemoveCommand(client)}`, `Then configure the current version: ${manualSetupCommand(client)}`]
161
+ : [];
162
+ throw new ClientPreflightError([
163
+ `Not linking here: ${reason}`,
164
+ ...repair,
165
+ "",
166
+ "The pairing code has NOT been used. Fix the local client configuration, then",
167
+ "run the link command again.",
168
+ ].join("\n"));
169
+ }
170
+ if (client === "claude") {
171
+ const plugin = await configureClaudeContinuationPlugin(homedir(), runner);
172
+ if (plugin.status === "failed") {
173
+ throw new ClientPreflightError([
174
+ "Not linking here: automatic SomaCheck result continuation could not be configured for Claude Code.",
175
+ "The pairing code has NOT been used. Repair Claude's plugin setup, then run the link command again.",
176
+ ].join("\n"));
177
+ }
178
+ output("✓ Automatic Claude result continuation is ready before redeeming the pairing code.");
179
+ }
161
180
  }
162
181
  return selectedClients;
163
182
  }
@@ -194,8 +213,21 @@ async function startServer(runtimeClient) {
194
213
  void refreshRuntimeHealth(runtimeClient).catch(() => undefined);
195
214
  }
196
215
  }
216
+ async function runClaudeResultHook() {
217
+ const chunks = [];
218
+ for await (const chunk of process.stdin) {
219
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
220
+ }
221
+ const result = await awaitClaudeVibecheckResult(Buffer.concat(chunks).toString("utf8"), { home: homedir(), api });
222
+ if (result.message !== null)
223
+ process.stderr.write(`${result.message}\n`);
224
+ return result.exitCode;
225
+ }
197
226
  async function main() {
198
227
  const args = process.argv.slice(2);
228
+ if (args.length === 2 && args[0] === "claude-hook" && args[1] === "await-result") {
229
+ return runClaudeResultHook();
230
+ }
199
231
  if (args[0] === "link") {
200
232
  if (args.length < 2)
201
233
  throw new Error(usage());
@@ -142,7 +142,9 @@ function isManagedSomaCheckRegistration(client, stdout) {
142
142
  const previousExactClient = (/^@somacheck\/vibecheck@0\.5\.\d+$/.test(packageArg)
143
143
  || packageArg === "@somacheck/vibecheck@0.6.0"
144
144
  || packageArg === "@somacheck/vibecheck@0.6.1"
145
- || packageArg === "@somacheck/vibecheck@0.6.2")
145
+ || packageArg === "@somacheck/vibecheck@0.6.2"
146
+ || packageArg === "@somacheck/vibecheck@0.6.3"
147
+ || packageArg === "@somacheck/vibecheck@0.6.4")
146
148
  && args.length === 5
147
149
  && args[0] === "-y"
148
150
  && 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.3";
5
+ export const PACKAGE_VERSION = "0.6.5";
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
@@ -3,10 +3,9 @@ import { z } from "zod";
3
3
  import { StatementPendingError, } from "./vibecheck.js";
4
4
  import { SomaCheckCompatibilityError, SomaCheckHttpError, SomaCheckLiveAskClientError, SomaCheckLiveAskConflictError, SomaCheckLiveAskPendingError, } from "./api.js";
5
5
  import { PACKAGE_NAME, PACKAGE_SPEC, PACKAGE_VERSION } from "./constants.js";
6
- const LIVE_ASK_WAIT_BUDGET_MS = 45_000;
6
+ const LIVE_ASK_WAIT_BUDGET_MS = 10_000;
7
7
  const LIVE_ASK_POLL_DELAYS_MS = [
8
- 1_000, 1_000, 2_000, 2_000, 3_000, 3_000,
9
- 4_000, 4_000, 5_000, 5_000, 5_000, 5_000,
8
+ 1_000, 1_500, 2_500, 4_000,
10
9
  ];
11
10
  const CLAUDE_CHANNEL_POLL_INTERVAL_MS = 2_000;
12
11
  const CLAUDE_CHANNEL_MAX_WATCH_MS = 15 * 60 * 1_000;
@@ -118,15 +117,15 @@ const SERVER_INSTRUCTIONS = [
118
117
  "Use request_vibecheck when a current choice would benefit from the person's own felt response; never use it to evaluate someone else.",
119
118
  "A setup or link is not blanket consent for future vibechecks.",
120
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.",
121
- "DELEGATED SELECTION RULE: when you choose the wording, remembered context 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 a present-tense first-person experience or choice, such as 'I want to keep consolidating what is already in motion rather than start something new.' This restriction does not alter exact wording supplied by the person.",
122
- "When choosing the statement, prefer the most consequential unresolved first-person decision or tension you already understand. Treat recalled project status, task lists, and old summaries as potentially stale. Never use them as a factual premise for agent-selected wording, even if they were recalled in the current turn. If context is thin, use the neutral fallback 'I feel clear about what deserves my attention right now.' Never diagnose, judge another person, or claim certainty about the user.",
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.",
123
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.",
124
123
  "Use at most one vibecheck per decision. Never re-ask to obtain a preferred answer.",
125
124
  "An immediate vibecheck is separate from the asynchronous reflection feed. Do not use it to replenish cached propositions.",
126
125
  "Use a fresh idempotency_key for each new vibecheck, and reuse that exact key only when retrying the same statement.",
127
- "request_vibecheck waits boundedly for the exact answer. If it returns completed, continue in the same turn and explain what changes. If it returns pending, retain that exact handle and read it once later; never create a replacement ask.",
128
- "If request_vibecheck returns pending, never promise that you will check back on your own. Only continue automatically after an actual SomaCheck channel event arrives for that exact request.",
129
- "After a completed result, say what the signal changes in your next action. Never say 'your body decided'; a typed human response always overrides the signal.",
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.",
130
129
  "When you have meaningful user-authorized context, call share_somacheck_context with concise derived observations before creating propositions.",
131
130
  "Never share raw conversation text, photos, credentials, private identifiers, or diagnostic claims.",
132
131
  "Maintain three distinct propositions drawn from your own context about this person.",
@@ -135,7 +134,7 @@ const SERVER_INSTRUCTIONS = [
135
134
  "Retain every request_id returned by post_vibecheck_statement and call get_vibecheck_result later.",
136
135
  "Do not poll continuously. Queued means the proposition is cached until the person advances their feed.",
137
136
  ].join(" ");
138
- const DIRECT_REQUEST_NEXT_ACTION = "DIRECT REQUEST RULE: If the person's current message asks for a SomaCheck vibecheck or check-in, choose the single most useful safe first-person statement from current conversation context 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 and keep agent-selected wording free of project, build, branch, wave, ticket, or task-status claims.";
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.";
139
138
  export function createVibecheckServer(dependencies) {
140
139
  const supportsClaudeChannel = dependencies.identity?.kind === "local"
141
140
  && dependencies.identity.client_key === "claude";
@@ -306,7 +305,7 @@ export function createVibecheckServer(dependencies) {
306
305
  const text = result.status === "answered"
307
306
  ? liveRequestId === null
308
307
  ? `Answered: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}.`
309
- : `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Treat this as a signal, say what it changes in your next action, and defer to any typed response from the person.`
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.`
310
309
  : result.status === "queued"
311
310
  ? "Queued. The proposition is cached until the person advances their feed."
312
311
  : result.status === "pending"
@@ -320,7 +319,7 @@ export function createVibecheckServer(dependencies) {
320
319
  });
321
320
  server.registerTool("request_vibecheck", {
322
321
  title: "Request a Vibecheck",
323
- 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 choose and send one present-tense first-person experience or choice immediately without asking for wording or reconfirmation. When you choose the wording, remembered context may identify only the general tension: never put a named project, build, branch, wave, ticket, task status, or supposedly open/closed work into the statement, even when recalled in the current turn. Abstract it, for example: 'I want to keep consolidating what is already in motion rather than start something new.' If context is thin, use the neutral fallback 'I feel clear about what deserves my attention right now.' 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.",
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.",
324
323
  inputSchema: {
325
324
  statement: z.string().trim().min(1).max(1000)
326
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."),
@@ -376,11 +375,11 @@ export function createVibecheckServer(dependencies) {
376
375
  startClaudeChannelWatch(result, token, identity);
377
376
  const structuredContent = { ...result, error_code: null };
378
377
  const text = result.state === "completed"
379
- ? `Vibecheck completed: ${Math.round((result.confidence ?? 0) * 100)}% ${result.verdict}. Treat this as a signal, say what it changes in your next action, and defer to any typed response from the person.`
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.`
380
379
  : result.state === "pending"
381
380
  ? result.delivery_state === "failed" || result.delivery_state === "skipped"
382
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.`
383
- : `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 an actual SomaCheck channel event arrives.`
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.`
384
383
  : `This vibecheck is ${result.state}. Do not create another ask for the same decision.`;
385
384
  return { content: [{ type: "text", text }], structuredContent };
386
385
  }
@@ -431,7 +430,7 @@ async function watchForClaudeChannelResult(created, token, identity, dependencie
431
430
  continue;
432
431
  const handle = `live:${requestId}`;
433
432
  const content = lifecycle.status === "answered"
434
- ? `A SomaCheck vibecheck requested by this Claude connection has completed. The exact result is ${lifecycle.verdict} with confidence ${formatChannelConfidence(lifecycle.confidence)}. Treat it as a signal, not a verdict. Continue the current conversation now: briefly acknowledge the signal and state what it changes in your next action. The exact opaque request handle is ${handle}. Do not request another vibecheck for this decision.`
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.`
435
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.`;
436
435
  try {
437
436
  await notify({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@somacheck/vibecheck",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Send a consented vibecheck to SomaCheck and use the result as a signal.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,7 +15,9 @@
15
15
  ".": "./dist/server.js"
16
16
  },
17
17
  "files": [
18
- "dist/*.js"
18
+ "dist/*.js",
19
+ "SKILL.md",
20
+ "claude-marketplace/**"
19
21
  ],
20
22
  "scripts": {
21
23
  "build": "rm -rf dist && tsc -p tsconfig.json",