prism-mcp-server 20.21.17 → 20.21.18

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
@@ -158,6 +158,14 @@ or by re-enabling after each run.
158
158
  <details>
159
159
  <summary>Release history (optional)</summary>
160
160
 
161
+ ## What's New in v20.21.18
162
+
163
+ - Fixed: when a save was refused with `context_not_loaded`, the instructions
164
+ Prism installs for Claude Code, Codex and Gemini CLI forbade the one call
165
+ that recovers. The refusal now prints that call, `session_load_context`
166
+ with the same project and conversation_id, and the installed instructions
167
+ allow it. First-turn startup is unchanged.
168
+
161
169
  ## What's New in v20.21.17
162
170
 
163
171
  - Fixed: the on-device screen refused some ordinary coding requests. With a
@@ -1396,7 +1404,7 @@ Prism exposes 40+ MCP tools. The core memory loop:
1396
1404
  | Tool | What it does |
1397
1405
  |---|---|
1398
1406
  | `session_bootstrap` | Hook-free first-turn greeting and dashboard-configured context |
1399
- | `session_load_context` | Explicit project reload or older-server startup fallback |
1407
+ | `session_load_context` | Explicit project reload, recovery after a `context_not_loaded` save refusal, or older-server startup fallback |
1400
1408
  | `session_save_ledger` | Append an immutable session log entry |
1401
1409
  | `session_save_handoff` | Save live state for the next session |
1402
1410
  | `knowledge_search` | Semantic + keyword search over all memories |
package/dist/connect.js CHANGED
@@ -4,6 +4,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep, win32 as w
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { isDeepStrictEqual } from "node:util";
6
6
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7
+ import { CONTEXT_RECOVERY_POLICY_LINES } from "./contextRecoveryPolicy.js";
7
8
  import { EVIDENCE_WORKFLOW_POLICY_LINES } from "./evidenceWorkflowPolicy.js";
8
9
  import { LOCAL_FIRST_POLICY_ID, LOCAL_FIRST_POLICY_LINES } from "./localFirstPolicy.js";
9
10
  export const CONNECT_HOSTS = [
@@ -44,11 +45,13 @@ const CODEX_STARTUP_BODY = [
44
45
  "reformat, or omit any returned section. Preserve its order and line content. For a greeting-only prompt, stop",
45
46
  "after the verbatim startup display. If `session_bootstrap` is deferred, use native tool discovery to load that",
46
47
  "exact tool, then invoke it. Do not use shell commands, file reads, subagents, or unrelated tool inspection as",
47
- "a substitute. Do not call `session_load_context`. If discovery or invocation fails, report",
48
+ "a substitute, and do not use `session_load_context` in place of it. If discovery or invocation fails, report",
48
49
  "`Prism startup failure` and stop. Reuse the `conversation_id` returned on the `<prism_session />` line for every",
49
50
  "session_save_ledger, session_save_handoff, and session_detect_drift call in this conversation. This hook-free",
50
51
  "block is managed by `prism connect`; do not edit it manually.",
51
52
  "",
53
+ ...CONTEXT_RECOVERY_POLICY_LINES,
54
+ "",
52
55
  ...LOCAL_FIRST_POLICY_LINES,
53
56
  "",
54
57
  ...EVIDENCE_WORKFLOW_POLICY_LINES,
@@ -578,11 +581,13 @@ function serializeClaudeStartupBlock(newline) {
578
581
  "content. For a greeting-only prompt, stop after the verbatim startup display. If `session_bootstrap` is",
579
582
  "deferred, use native tool discovery/ToolSearch to load that",
580
583
  "exact tool, then invoke it. Do not use shell commands, file reads, subagents, or unrelated tool inspection",
581
- "as a substitute. Do not call `session_load_context`. If discovery or invocation fails, report",
584
+ "as a substitute, and do not use `session_load_context` in place of it. If discovery or invocation fails, report",
582
585
  "`Prism startup failure` and stop. Reuse the `conversation_id` returned on the `<prism_session />` line for every",
583
586
  "session_save_ledger, session_save_handoff, and session_detect_drift call in this conversation. This block is",
584
587
  "managed by `prism connect`; do not edit it manually.",
585
588
  "",
589
+ ...CONTEXT_RECOVERY_POLICY_LINES,
590
+ "",
586
591
  ...LOCAL_FIRST_POLICY_LINES,
587
592
  "",
588
593
  ...EVIDENCE_WORKFLOW_POLICY_LINES,
@@ -671,10 +676,12 @@ function serializeGeminiStartupBlock(newline) {
671
676
  "reformat, or omit any returned section. Preserve its order and line content. For a greeting-only prompt, stop",
672
677
  "after the verbatim startup display. If `session_bootstrap` is deferred, use native tool discovery/ToolSearch",
673
678
  "to load that exact tool, then invoke it.",
674
- "Do not use shell commands, file reads, subagents, or unrelated tool inspection as a substitute. Do not call",
675
- "`session_load_context`. If discovery or invocation fails, report `Prism startup failure` and stop. Reuse the",
676
- "`conversation_id` returned on the `<prism_session />` line for session_save_ledger, session_save_handoff, and",
677
- "session_detect_drift calls. This block is managed by `prism connect`; do not edit it manually.",
679
+ "Do not use shell commands, file reads, subagents, or unrelated tool inspection as a substitute, and do not use",
680
+ "`session_load_context` in place of it. If discovery or invocation fails, report `Prism startup failure` and stop.",
681
+ "Reuse the `conversation_id` returned on the `<prism_session />` line for session_save_ledger, session_save_handoff,",
682
+ "and session_detect_drift calls. This block is managed by `prism connect`; do not edit it manually.",
683
+ "",
684
+ ...CONTEXT_RECOVERY_POLICY_LINES,
678
685
  "",
679
686
  ...LOCAL_FIRST_POLICY_LINES,
680
687
  "",
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canonical context-recovery contract shared by every host instruction file.
3
+ *
4
+ * Startup is one `session_bootstrap` call. A later save can still be refused
5
+ * with `context_not_loaded`, and the refusal names one remedy; the host block
6
+ * must say the same thing instead of forbidding it. Keep this host-neutral: the
7
+ * Claude Code, Gemini CLI and Codex blocks splice these lines unchanged, and
8
+ * the server instructions, tool descriptions and refusal text state the same
9
+ * rule in their own words (pinned by tests/startup-recovery-contract.test.ts).
10
+ */
11
+ export const CONTEXT_RECOVERY_POLICY_LINES = [
12
+ "## Prism context recovery",
13
+ "Startup is the one turn-one `session_bootstrap` call. Prism can still refuse `session_save_ledger` or",
14
+ "`session_save_handoff` with `context_not_loaded`: it has no record that this conversation loaded that project",
15
+ "(after a restart, a long idle gap, or for a project startup did not load). That is a recovery step, not a second",
16
+ "startup. Call `session_load_context` with the same `project` and the same `conversation_id` as the refused save",
17
+ "(the refusal prints the exact call; discover the tool the same way if it is deferred), then retry the save once.",
18
+ "Do not repeat `session_bootstrap` or print a startup display for it. If the reload or the retry fails, tell the",
19
+ "user; a local note is a fallback after that, never a substitute for the reload.",
20
+ ];
21
+ export const CONTEXT_RECOVERY_POLICY_TEXT = CONTEXT_RECOVERY_POLICY_LINES.join(" ");
package/dist/server.js CHANGED
@@ -361,8 +361,10 @@ export const PRISM_SERVER_INSTRUCTIONS = `Prism MCP — The Mind Palace for AI A
361
361
  `Emit no preamble. Print the complete tool result verbatim as the entire first-turn startup display, before any optional ` +
362
362
  `answer. Do not summarize, paraphrase, rename headings, reformat, or omit any returned section. Preserve its order and ` +
363
363
  `line content. For a greeting-only prompt, stop after the verbatim startup display. ` +
364
- `Do not substitute session_load_context while session_bootstrap is available; use session_load_context ` +
365
- `only for an explicit project reload or as an older-server fallback. ` +
364
+ `Do not substitute session_load_context for the startup call while session_bootstrap is available. Use ` +
365
+ `session_load_context to recover when a save is refused with context_not_loaded (pass that save's project ` +
366
+ `and conversation_id, then retry the save once; recovery is not a second startup), for an explicit project ` +
367
+ `reload, or as an older-server fallback. ` +
366
368
  `Use session_save_ledger to log completed work and session_save_handoff to preserve state for the next session. ` +
367
369
  `Reuse the conversation_id from session_bootstrap's <prism_session /> line for those saves and for ` +
368
370
  `session_detect_drift, the 60-minute goal-alignment drift check. Do not add the id to the visible greeting.\n\n` +
@@ -84,24 +84,75 @@ export function markContextLoaded(conversationId, project, boundariesVersion) {
84
84
  s.boundariesVersion = boundariesVersion;
85
85
  lastSeenConversationId = conversationId;
86
86
  }
87
+ const ENFORCED = " (Enforced server-side — applies to every host.)";
88
+ /**
89
+ * The one remedy every refusal names, so the three variants cannot drift apart
90
+ * and the host instruction blocks (src/contextRecoveryPolicy.ts), the server
91
+ * instructions and the tool descriptions can say the same thing. A block that
92
+ * forbids the call a refusal asks for leaves an agent with no way forward
93
+ * (tests/startup-recovery-contract.test.ts pins every surface).
94
+ */
95
+ const CONTEXT_RECOVERY = " To recover, call session_load_context with the same project and the same conversation_id you " +
96
+ "passed to this save, then retry the save once. You do not need to repeat session_bootstrap (it " +
97
+ "reloads only the dashboard Auto-Load projects and reprints the startup display). A recovery load " +
98
+ "is not a second startup. If the retry is refused too, stop and tell the user.";
87
99
  function contextNotLoadedError(project) {
88
100
  const projectNote = project
89
101
  ? " the requested project was not loaded for this conversation."
90
- : "";
102
+ : " no context is registered for this conversation.";
103
+ return {
104
+ blocked: true,
105
+ error: "context_not_loaded:" + projectNote + CONTEXT_RECOVERY +
106
+ " This project-scoped tool needs confirmed working context to act correctly." + ENFORCED,
107
+ };
108
+ }
109
+ /**
110
+ * A save whose conversation_id is the empty string can never be recovered by
111
+ * "the same conversation_id you passed": the load handler registers nothing for
112
+ * an empty id. Say what is actually wrong and where the real id comes from.
113
+ */
114
+ function emptyConversationIdError() {
91
115
  return {
92
116
  blocked: true,
93
- error: "context_not_loaded:" + projectNote + " Call session_bootstrap(conversation_id) or " +
94
- "session_load_context(project, conversation_id) " +
95
- "before this action. This project-scoped tool needs confirmed working context " +
96
- "to act correctly. (Enforced server-side — applies to every host.)",
117
+ error: "context_not_loaded: conversation_id is empty, so no context can be registered for it." +
118
+ " Pass this conversation's conversation_id (the value on session_bootstrap's <prism_session /> line)," +
119
+ " call session_load_context with that conversation_id and this project, then retry the save once." +
120
+ " If the retry is refused too, stop and tell the user." + ENFORCED,
97
121
  };
98
122
  }
99
123
  function contextExpiredError() {
100
124
  return {
101
125
  blocked: true,
102
- error: "context_not_loaded: session expired (6 h TTL). Call " +
103
- "session_bootstrap(conversation_id) or session_load_context(project, conversation_id) again. " +
104
- "(Enforced server-side — applies to every host.)",
126
+ error: "context_not_loaded: session expired (6 h TTL)." + CONTEXT_RECOVERY + ENFORCED,
127
+ };
128
+ }
129
+ /**
130
+ * Longest project or conversation_id a refusal echoes back. A longer value gets
131
+ * the remedy without a literal call: a clipped value would register the wrong
132
+ * project, and the retry would be refused again.
133
+ */
134
+ const MAX_ECHOED_VALUE = 200;
135
+ /**
136
+ * Print the literal recovery call, built from the arguments the refused save
137
+ * used, so "the same project and conversation_id" cannot be mistyped or
138
+ * replaced by a host session id. Values are JSON-escaped and echoed whole, only
139
+ * to the caller that just sent them; a value too long to echo gets no call.
140
+ */
141
+ function withExactCall(gate, conversationId, project) {
142
+ if (!gate || !gate.blocked || !conversationId || !project.trim() || !gate.error.endsWith(ENFORCED)) {
143
+ return gate;
144
+ }
145
+ if (project.length > MAX_ECHOED_VALUE || conversationId.length > MAX_ECHOED_VALUE)
146
+ return gate;
147
+ const call = JSON.stringify({
148
+ project,
149
+ conversation_id: conversationId,
150
+ toolAction: "Reload context",
151
+ toolSummary: "Recover from context_not_loaded",
152
+ });
153
+ return {
154
+ blocked: true,
155
+ error: gate.error.slice(0, -ENFORCED.length) + ` Exact call: session_load_context(${call}).` + ENFORCED,
105
156
  };
106
157
  }
107
158
  function hashReceiptScope(kind, value) {
@@ -196,9 +247,15 @@ export function requireContextLoaded(conversationId) {
196
247
  * cross-project lookups remain fail-closed.
197
248
  */
198
249
  export async function requireContextLoadedForProject(conversationId, project) {
250
+ const gate = await evaluateContextGateForProject(conversationId, project);
251
+ return conversationId === undefined ? gate : withExactCall(gate, conversationId, project);
252
+ }
253
+ async function evaluateContextGateForProject(conversationId, project) {
199
254
  if (conversationId === undefined)
200
255
  return null;
201
- if (!conversationId || !project.trim())
256
+ if (!conversationId)
257
+ return emptyConversationIdError();
258
+ if (!project.trim())
202
259
  return contextNotLoadedError(project || undefined);
203
260
  const memoryGate = requireContextLoaded(conversationId);
204
261
  const memoryState = sessions.get(conversationId);
@@ -256,7 +256,7 @@ export function _resetLayer1HistoryCacheForTest() { layer1HistoryCache.clear();
256
256
  * probe and read races the time left, and no clearance is accepted after it. */
257
257
  export const LAYER1_SECOND_READ_MAX_CALLS = 48;
258
258
  export const LAYER1_SECOND_READ_DEADLINE_MS = 30_000;
259
- /** Reads in flight at once: ONE (owner decision 2026-09-26, after review).
259
+ /** Reads in flight at once: ONE.
260
260
  * Two at once made each classifier read about twice as slow and some reads
261
261
  * abort at the classifier's first-attempt timeout (an aborted read keeps the
262
262
  * hedge: refusal or cloud), for little clearance time gained. The per-read
@@ -1498,8 +1498,8 @@ function makeReservedRefusal(verdict, attempts, category = null, cloudWasAllowed
1498
1498
  });
1499
1499
  return new ReservedRefusalError(verdict, attempts, category, cloudWasAllowed);
1500
1500
  }
1501
- /** Portal cap on the flattened conversation (`ROLE: content` lines) — see
1502
- * portal/src/app/api/v1/prism/inference/route.ts MAX_PROMPT_BYTES. */
1501
+ /** Server cap on the flattened conversation (`ROLE: content` lines): the
1502
+ * Synalux inference endpoint enforces the same limit. */
1503
1503
  export const CLOUD_HISTORY_CAP_BYTES = 32 * 1024;
1504
1504
  /** Exported for tests: the cap check must be provable without a portal. */
1505
1505
  export async function callSynaluxInference(prompt, maxTokens, timeoutMs, opts) {
@@ -4,7 +4,10 @@ export const SESSION_SAVE_LEDGER_TOOL = {
4
4
  description: "Save an immutable session log entry to the session ledger. " +
5
5
  "Use this at the END of each work session to record what was accomplished. " +
6
6
  "The ledger is append-only — entries cannot be updated or deleted. " +
7
- "This creates a permanent audit trail of all agent work sessions.",
7
+ "This creates a permanent audit trail of all agent work sessions. " +
8
+ "The save is refused with context_not_loaded until session_bootstrap or session_load_context has loaded " +
9
+ "this exact project for this conversation_id; on that refusal call session_load_context with the same " +
10
+ "project and conversation_id, then retry the save once.",
8
11
  inputSchema: {
9
12
  type: "object",
10
13
  properties: {
@@ -60,7 +63,11 @@ export const SESSION_SAVE_HANDOFF_TOOL = {
60
63
  "Pass expected_version to enable concurrency control.\n\n" +
61
64
  "**v0.4.0 OCC**: If you received a version number from session_load_context, " +
62
65
  "/resume_session prompt, or memory resource attachment, you MUST pass it as " +
63
- "expected_version to prevent overwriting another session's changes.",
66
+ "expected_version to prevent overwriting another session's changes.\n\n" +
67
+ "Pass the same conversation_id as session_save_ledger: the save is refused with context_not_loaded until " +
68
+ "session_bootstrap or session_load_context has loaded this exact project for it; on that refusal call " +
69
+ "session_load_context with the same project and conversation_id, then retry the save once, passing the " +
70
+ "version it shows as expected_version.",
64
71
  inputSchema: {
65
72
  type: "object",
66
73
  properties: {
@@ -101,7 +108,7 @@ export const SESSION_SAVE_HANDOFF_TOOL = {
101
108
  },
102
109
  conversation_id: {
103
110
  type: "string",
104
- description: "Optional. Session key for this conversation (same id used in session_load_context). When provided, the server verifies that session_load_context was called for this conversation before accepting the write.",
111
+ description: "Optional. Session key for this conversation (same id used in session_load_context). When provided, the server verifies that session_bootstrap or session_load_context loaded this exact project for this conversation before accepting the write.",
105
112
  },
106
113
  },
107
114
  required: ["project"],
@@ -111,7 +118,9 @@ export const SESSION_SAVE_HANDOFF_TOOL = {
111
118
  export const SESSION_LOAD_CONTEXT_TOOL = {
112
119
  name: "session_load_context",
113
120
  description: "Load session context for a project using progressive context loading. " +
114
- "Use this for an explicit project reload, or as a startup fallback only when session_bootstrap is unavailable. " +
121
+ "Use this to recover when session_save_ledger or session_save_handoff is refused with context_not_loaded " +
122
+ "(pass that save's project and the same conversation_id, then retry the save once; a recovery load is not a " +
123
+ "second startup), for an explicit project reload, or as a startup fallback only when session_bootstrap is unavailable. " +
115
124
  "When session_bootstrap is available, do not substitute this tool for the first-turn bootstrap. " +
116
125
  "Three levels available:\n" +
117
126
  "- **quick**: Just the latest project state — keywords and open TODOs (~50 tokens)\n" +
@@ -148,7 +157,7 @@ export const SESSION_LOAD_CONTEXT_TOOL = {
148
157
  },
149
158
  conversation_id: {
150
159
  type: "string",
151
- description: "Optional. Session key for this conversation (same id used in session_save_ledger). When provided, marks the session as context-loaded server-side so project-scoped tools can verify working context without relying on hook-based enforcement. Required on non-Claude hosts.",
160
+ description: "Optional for a plain read. Pass it whenever this load is meant to unlock saves (startup fallback, or recovery from context_not_loaded), using the same conversation_id as the refused or upcoming save. When provided, marks this project as context-loaded server-side so project-scoped saves are accepted; without it nothing is registered.",
152
161
  },
153
162
  prompt: {
154
163
  type: "string",
@@ -209,7 +218,7 @@ export const SESSION_BOOTSTRAP_TOOL = {
209
218
  "before any user-facing response, passing the user's verbatim first message as {prompt: \"<first user message>\"}. " +
210
219
  "The prompt is matched against prompt_keywords ON-DEVICE to load symptom-triggered skills on turn one; it is used " +
211
220
  "for routing only and never leaves the machine. Pass {} only when there is no user message. " +
212
- "Do not substitute session_load_context when this tool is available. " +
221
+ "Do not substitute session_load_context for this startup call. " +
213
222
  "This starts a Prism-backed conversation without host hooks. " +
214
223
  "Prism reads the dashboard's Auto-Load Projects, Context Depth (quick/standard/deep), developer name, and default role, " +
215
224
  "then returns the greeting and correctly scoped prior-session context. Emit no preamble. Print the complete tool result " +
@@ -217,7 +226,10 @@ export const SESSION_BOOTSTRAP_TOOL = {
217
226
  "headings, reformat, or omit any returned section. Preserve its order and line content. For a greeting-only prompt, " +
218
227
  "stop after the verbatim startup display. Do not guess or pass a project or depth. Prism returns a stable " +
219
228
  "conversation_id on the trailing <prism_session /> line; reuse it for session_save_ledger, session_save_handoff, and " +
220
- "session_detect_drift throughout this conversation without adding it to the visible greeting.",
229
+ "session_detect_drift throughout this conversation without adding it to the visible greeting. " +
230
+ "This is the first-turn startup call only: if a later save is refused with context_not_loaded, recover with " +
231
+ "session_load_context for that save's project and conversation_id, then retry the save once, rather than " +
232
+ "repeating this startup display.",
221
233
  annotations: {
222
234
  readOnlyHint: true,
223
235
  destructiveHint: false,
@@ -13,8 +13,8 @@
13
13
  * regexes match, and the regexes are already public. Sending the raw first
14
14
  * message bought nothing a local match could not compute — and free-tier
15
15
  * callers paid that privacy cost for literally zero routing benefit, since
16
- * the portal gates them to an empty set (resolve/route.ts: `tier === 'paid' ?
17
- * resolved : []`). Paid skill CONTENT stays gated server-side at
16
+ * the server returns no prompt-matched skills to the free tier. Paid skill
17
+ * CONTENT stays gated server-side at
18
18
  * /api/v1/prism/skill-manifest, which this change does not touch.
19
19
  *
20
20
  * Cache: portal keyed on (project,role) — no longer per-prompt, which never
@@ -464,8 +464,8 @@ export function stripQuotedEvidenceForRouting(prompt, promptKeywords = {}) {
464
464
  return units.join('');
465
465
  }
466
466
  /**
467
- * Verbatim port of portal resolve/route.ts prompt-matching block + the sort
468
- * that follows it. Parity is the whole point: any divergence silently changes
467
+ * Port of the server resolver's prompt-matching step and the sort that
468
+ * follows it. Parity is the whole point: any divergence silently changes
469
469
  * which skills load. Do not "improve" this — the reference implementation and
470
470
  * a scenario-level parity test both pin it.
471
471
  *
@@ -14,9 +14,9 @@ import { debugLog } from "./logger.js";
14
14
  import { resolvePortalBaseUrl, usablePortalKey } from "./synaluxSearch.js";
15
15
  /** What a host with NO portal (unconfigured), a portal that says nothing
16
16
  * (older deployment), or an assumed-free fallback gets: OFF. Multi-turn
17
- * needs a Synalux account, and a free one is enough (owner decision
18
- * 2026-09-26): the answer check it depends on is served per account. The
19
- * caps here are what an account gets when the portal omits them. */
17
+ * needs a Synalux account, and a free one is enough: the answer check it
18
+ * depends on is served per account. The caps here are what an account gets
19
+ * when the portal omits them. */
20
20
  export const DEFAULT_MULTI_TURN = { enabled: false, max_turns: 12, max_chars: 32_000 };
21
21
  /** Structural ceiling no plan can exceed: the portal's own inference route
22
22
  * takes at most 50 messages INCLUDING the current turn appended on
@@ -38,9 +38,8 @@ export function multiTurnPolicy(ent) {
38
38
  }
39
39
  // ── Free-tier defaults (no auth) ──────────────────────────────────
40
40
  /** No account: everything local, with no cap on the model the user's own
41
- * machine can run (owner decision 2026-09-26). Anything that needs Synalux
42
- * (multi-turn with the answer check, cloud answers) needs an account; a free
43
- * one is enough. */
41
+ * machine can run. Anything that needs Synalux (multi-turn with the answer
42
+ * check, cloud answers) needs an account; a free one is enough. */
44
43
  export const FREE_ENTITLEMENTS = {
45
44
  plan: "free",
46
45
  model_ceiling: "27b",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.21.17",
3
+ "version": "20.21.18",
4
4
  "mcpName": "io.github.dcostenco/prism-coder",
5
5
  "description": "Persistent session memory for AI coding agents that never leaves your machine — including the on-device model that reasons over it. Restores your prior decisions, open TODOs, and changed files across sessions; adds associative recall of related past work, semantic drift detection, and local inference. Local-first by default. Works with Claude Code, Cursor, and Codex.",
6
6
  "module": "index.ts",
@@ -22,7 +22,7 @@
22
22
  "prebuild": "npm run clean",
23
23
  "build": "tsc && npm run chmod-bins",
24
24
  "chmod-bins": "node -e \"['dist/cli.js','dist/server.js','dist/utils/universalImporter.js'].forEach(f => { try { require('fs').chmodSync(f, 0o755); } catch (e) { console.warn('chmod skipped', f, e.message); } })\"",
25
- "prepublishOnly": "node scripts/check-no-private-content.mjs && node scripts/check-publish-clean.mjs && npm run build",
25
+ "prepublishOnly": "node scripts/check-no-private-content.mjs && node scripts/private-identifier-scan.mjs && node scripts/check-publish-clean.mjs && npm run build",
26
26
  "lint:dashboard": "node scripts/lint-dashboard-es5.cjs",
27
27
  "check:lockfile": "node scripts/check-lockfile-drift.mjs",
28
28
  "start": "node dist/server.js",
@@ -79,7 +79,7 @@
79
79
  "overrides": {
80
80
  "@hono/node-server": "^2.0.5",
81
81
  "body-parser": "^2.3.0",
82
- "fast-uri": "^3.1.6",
82
+ "fast-uri": "^3.1.8",
83
83
  "hono": "^4.13.5",
84
84
  "ip-address": "^10.2.0",
85
85
  "protobufjs": "^7.6.5",