@somacheck/vibecheck 0.6.4 → 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 +28 -33
- package/SKILL.md +157 -0
- package/claude-marketplace/.claude-plugin/marketplace.json +22 -0
- package/claude-marketplace/plugins/vibecheck/.claude-plugin/plugin.json +13 -0
- package/claude-marketplace/plugins/vibecheck/hooks/hooks.json +18 -0
- package/dist/claude-hook.js +152 -0
- package/dist/claude-plugin-setup.js +114 -0
- package/dist/cli.js +50 -24
- package/dist/client-setup.js +2 -6
- package/dist/constants.js +1 -1
- package/dist/server.js +9 -10
- package/package.json +4 -2
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.
|
|
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.
|
|
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.
|
|
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.
|
|
82
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
83
|
-
npx -y @somacheck/vibecheck@0.6.
|
|
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.
|
|
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.
|
|
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,34 +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
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Claude
|
|
206
|
-
|
|
207
|
-
|
|
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,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
generic signal language.
|
|
220
|
-
Never treat it as authorization or say that the person's body made the
|
|
221
|
-
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.
|
|
222
217
|
|
|
223
218
|
An immediate vibecheck is not proposition caching. It sends one person-approved statement
|
|
224
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
|
@@ -3,8 +3,10 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { SupabaseAgentApi } from "./api.js";
|
|
6
|
-
import {
|
|
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,11 +95,12 @@ async function configureClients(clients) {
|
|
|
93
95
|
else {
|
|
94
96
|
output(`✗ Could not configure ${clientDisplayName(client)} automatically.`);
|
|
95
97
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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"}.`);
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
}
|
|
@@ -142,28 +145,38 @@ async function preflightLinkClients(clients, interactive) {
|
|
|
142
145
|
const preflight = await preflightClientPersistence(client, runner);
|
|
143
146
|
if (preflight.status === "ready") {
|
|
144
147
|
output(`✓ ${clientDisplayName(client)} already has the exact SomaCheck MCP registration.`);
|
|
145
|
-
continue;
|
|
146
148
|
}
|
|
147
|
-
if (preflight.status === "registered" || preflight.status === "upgraded") {
|
|
149
|
+
else if (preflight.status === "registered" || preflight.status === "upgraded") {
|
|
148
150
|
const verb = preflight.status === "upgraded" ? "was safely upgraded" : "was configured";
|
|
149
151
|
output(`✓ ${clientDisplayName(client)} ${verb} before redeeming the pairing code.`);
|
|
150
|
-
continue;
|
|
151
152
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
+
}
|
|
167
180
|
}
|
|
168
181
|
return selectedClients;
|
|
169
182
|
}
|
|
@@ -200,8 +213,21 @@ async function startServer(runtimeClient) {
|
|
|
200
213
|
void refreshRuntimeHealth(runtimeClient).catch(() => undefined);
|
|
201
214
|
}
|
|
202
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
|
+
}
|
|
203
226
|
async function main() {
|
|
204
227
|
const args = process.argv.slice(2);
|
|
228
|
+
if (args.length === 2 && args[0] === "claude-hook" && args[1] === "await-result") {
|
|
229
|
+
return runClaudeResultHook();
|
|
230
|
+
}
|
|
205
231
|
if (args[0] === "link") {
|
|
206
232
|
if (args.length < 2)
|
|
207
233
|
throw new Error(usage());
|
package/dist/client-setup.js
CHANGED
|
@@ -34,11 +34,6 @@ export function manualSetupCommand(client) {
|
|
|
34
34
|
}
|
|
35
35
|
return `claude mcp add --scope user ${MCP_SERVER_NAME} -- npx ${mcpServerArgs(client).join(" ")}`;
|
|
36
36
|
}
|
|
37
|
-
export function automaticContinuationCommand(client) {
|
|
38
|
-
return client === "claude"
|
|
39
|
-
? "claude --dangerously-load-development-channels server:vibecheck"
|
|
40
|
-
: null;
|
|
41
|
-
}
|
|
42
37
|
export function manualRemoveCommand(client) {
|
|
43
38
|
return client === "codex"
|
|
44
39
|
? `codex mcp remove ${MCP_SERVER_NAME}`
|
|
@@ -148,7 +143,8 @@ function isManagedSomaCheckRegistration(client, stdout) {
|
|
|
148
143
|
|| packageArg === "@somacheck/vibecheck@0.6.0"
|
|
149
144
|
|| packageArg === "@somacheck/vibecheck@0.6.1"
|
|
150
145
|
|| packageArg === "@somacheck/vibecheck@0.6.2"
|
|
151
|
-
|| packageArg === "@somacheck/vibecheck@0.6.3"
|
|
146
|
+
|| packageArg === "@somacheck/vibecheck@0.6.3"
|
|
147
|
+
|| packageArg === "@somacheck/vibecheck@0.6.4")
|
|
152
148
|
&& args.length === 5
|
|
153
149
|
&& args[0] === "-y"
|
|
154
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.
|
|
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 =
|
|
6
|
+
const LIVE_ASK_WAIT_BUDGET_MS = 10_000;
|
|
7
7
|
const LIVE_ASK_POLL_DELAYS_MS = [
|
|
8
|
-
1_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;
|
|
@@ -124,9 +123,9 @@ const SERVER_INSTRUCTIONS = [
|
|
|
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,
|
|
128
|
-
"If request_vibecheck returns pending, never promise that you will check back on your own.
|
|
129
|
-
"After a completed result,
|
|
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.",
|
|
@@ -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}.
|
|
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"
|
|
@@ -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}.
|
|
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
|
|
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)}.
|
|
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
|
+
"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",
|