prism-mcp-server 20.0.3 → 20.0.4

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
@@ -18,19 +18,35 @@ A paid subscription adds cloud sync, higher model tiers, and team features throu
18
18
 
19
19
  ---
20
20
 
21
- ## What's New in v20.0.2
21
+ ## What's New in v20.0.4
22
22
 
23
- ### Reserved-Category Safety Gate — All Tiers
24
- The Layer 1 semantic classifier now runs for **every** user, not just paid tiers. Reserved clinical content (restraint, seclusion, crisis protocols) is refused on free tier when cloud is unavailable fail-closed, never silently served locally. This closes a real gap where free-tier users bypassed classification entirely.
23
+ ### Think-Only Retry
24
+ Qwen 3.5 models (9B/27B) with thinking enabled could burn all tokens on `<think>` blocks and return empty content, causing a cascade to 4B. Now detects think-only responses and retries the same tier with thinking disabled preserving model quality instead of falling to a smaller model.
25
25
 
26
- ### Compressed Safety Banner (500 → 120 tokens/call)
27
- The per-call operating boundaries text was split: safety declaration stays in every `session_load_context` response (~120 tokens); architecture/routing docs moved to the MCP server description (loaded once at connection). Saves ~350 tokens per call with no loss of defense-in-depth.
26
+ ### Local-First Skill Rewrite
27
+ The `local-inference-first` skill now uses a hard falsifiable trigger list (format conversion, regex, i18n, boilerplate CRUD, deterministic tests) with no self-assessment escape. Clinical content removed from delegation it conflicts with the Layer 1 reserved-category safety floor. Honest target: 15-25% of prompts delegated at equal quality.
28
28
 
29
- ### Ledger Dedup
30
- `session_save_ledger` now deduplicates identical entries within a 5-minute window, preventing the retry-storm pattern that produced duplicate rows.
29
+ ---
30
+
31
+ ## What's New in v20.0.3
32
+
33
+ ### Layer 1 Cold-Model Resilience
34
+ The reserved-category classifier now retries once with a longer timeout on cold-model failure, then falls back to a deterministic keyword backstop before refusing. Over-length prompts (>4K chars) are classified as UNCERTAIN before reaching the classifier — prompt padding can no longer force the ERROR branch. This eliminates the cold-start refusal problem without weakening the safety gate.
35
+
36
+ ### Keyword Backstop for Reserved Content
37
+ When the LLM classifier fails (timeout, injection, resource pressure), a deterministic regex floor catches reserved vocabulary (restraint, seclusion, self-harm, suicide, overdose, crisis de-escalation, etc.) including inflected and verb forms. Blocks prompt-padding and classifier-injection attacks on the ERROR path.
38
+
39
+ ### Single-Source Safety Text
40
+ The safety statement in the MCP server `instructions` field now imports from `boundaries.ts` — one source of truth instead of two hand-maintained copies. Boundaries version bumped to v3 with an explicit delivery decision documented in code.
41
+
42
+ ### Reserved-Category Safety Gate — All Tiers (v20.0.2)
43
+ The Layer 1 semantic classifier now runs for **every** user, not just paid tiers. Reserved clinical content is refused on free tier when cloud is unavailable — fail-closed.
44
+
45
+ ### Ledger Dedup (v20.0.2)
46
+ `session_save_ledger` deduplicates identical entries within a 5-minute window.
31
47
 
32
- ### Evidence Script
33
- `scripts/generate-evidence.sh` regenerates all 5 evidence files with built-in assertions (skill counts, tier gating, version consistency, inference correctness). Run `bash scripts/generate-evidence.sh` to verify the full pipeline.
48
+ ### Evidence Script (v20.0.2)
49
+ `scripts/generate-evidence.sh` regenerates all 5 evidence files with built-in assertions. Run `bash scripts/generate-evidence.sh` to verify the full pipeline.
34
50
 
35
51
  ---
36
52
 
@@ -250,6 +250,21 @@ describe("Layer 1 handler integration", () => {
250
250
  await expect(runInfer({ prompt: "de-escalation plan for violent behavior", mode: "code", cloud_fallback: true, max_tokens: 512 }, makeBaseDeps({ callLocal, callCloud, callLayer1: callLayer1Mock }))).rejects.toThrow("Layer 1 verdict=OBVIOUS_RESERVED");
251
251
  expect(callLocal).not.toHaveBeenCalled();
252
252
  });
253
+ it("think_only → retries same tier with think=false, succeeds", async () => {
254
+ let callCount = 0;
255
+ const callLocal = vi.fn().mockImplementation(async (_url, _model, _prompt, _sys, _max, _temp, _timeout, think) => {
256
+ callCount++;
257
+ if (think)
258
+ return { ok: false, reason: "think_only" };
259
+ return { ok: true, text: "answer without thinking", doneReason: "stop" };
260
+ });
261
+ const result = await runInfer({ prompt: "write a regex for emails", mode: "code", cloud_fallback: false, max_tokens: 256 }, makeBaseDeps({ callLocal }));
262
+ expect(callCount).toBe(2);
263
+ expect(callLocal).toHaveBeenNthCalledWith(1, expect.anything(), expect.anything(), expect.anything(), undefined, expect.anything(), expect.anything(), expect.anything(), true);
264
+ expect(callLocal).toHaveBeenNthCalledWith(2, expect.anything(), expect.anything(), expect.anything(), undefined, expect.anything(), expect.anything(), expect.anything(), false);
265
+ expect(result.output).toBe("answer without thinking");
266
+ expect(result.used_cloud).toBe(false);
267
+ });
253
268
  });
254
269
  // ── Auto-evict unit tests ─────────────────────────────────────────────────────
255
270
  describe("Auto-evict warm smaller models (F1/F2/F3/F4 regression suite)", () => {
@@ -449,15 +449,14 @@ describe("ledgerHandlers", () => {
449
449
  expect(text).toContain('<prism_memory context="historical">');
450
450
  expect(text).toContain("</prism_memory>");
451
451
  });
452
- it("includes ABA Precision Protocol in every response", async () => {
452
+ it("does not contain inline ABA protocol (delivered via skill routing)", async () => {
453
453
  storage.loadContext.mockResolvedValue({
454
454
  last_summary: "Summary",
455
455
  version: 1,
456
456
  });
457
457
  const result = await sessionLoadContextHandler(validArgs);
458
458
  const text = result.content[0].text;
459
- expect(text).toContain("ABA PRECISION PROTOCOL");
460
- expect(text).toContain("Observable Goals");
459
+ expect(text).not.toContain("ABA PRECISION PROTOCOL");
461
460
  });
462
461
  it("passes role to loadContext when provided", async () => {
463
462
  storage.loadContext.mockResolvedValue(null);
@@ -1010,21 +1010,13 @@ export async function sessionLoadContextHandler(args) {
1010
1010
  behavWarnings.map(w => `- ${w.summary} (importance: ${w.importance})`).join("\n");
1011
1011
  behavBlock = [...rawBlock].slice(0, 2000).join('');
1012
1012
  }
1013
- // ─── v9.4.7: ABA Precision Protocol (foundational) ────────
1014
- const abaProtocol = `\n\n[🧠 ABA PRECISION PROTOCOL]\n` +
1015
- `Rule 1 Observable Goals: Every task must have a measurable, verifiable outcome. State the specific result.\n` +
1016
- `Rule 2 — Precise Execution: One step at a time. Verify each step. If it fails → STOP → fix → verify → then continue.\n` +
1017
- `Rule 3 — No Reinforcement of Errors: Never repeat the same mistake twice. When the user says something is wrong, read the actual code/data FIRST before forming an opinion.\n` +
1018
- `Rule 4 — No Negation/Filler/Hedging Lead: NEVER start with "I can't", "I'm unable to", "Unfortunately", "I cannot", "I apologize", "While I cannot", "Sorry", "Regrettably", "I'm afraid", "As an AI", "Sure,", "Certainly,", "I can certainly", "Let me be transparent", "I should note", "Just to clarify", "It's worth noting". You may use "Yes" or "Absolutely" ONLY as a 1-word direct answer to a binary Yes/No question. UNCERTAINTY ESCAPE: Use ONLY for required database fields/API params (e.g., "Missing: patient_id"). Do NOT use as generic refusal.\n` +
1019
- `Rule 5 — Fix Without Asking: When you see a bug, fix it immediately. Do NOT ask "would you like me to fix that?" — just fix it.\n` +
1020
- `Rule 6 — Action Intent: When user says "fix/run/open/deploy", they want ACTION not a tutorial. Ask for specific info needed in 1-2 sentences, or act directly.\n` +
1021
- `Rule 7 — Tool Redirect: When user asks to "open browser"/"run terminal"/"git push" — output ONLY the URL or command. No follow-up. No explanations. Example: "open browser" → "https://synalux.ai/dashboard"`;
1013
+ // ABA Precision Protocol is now delivered via skill routing
1014
+ // (aba-precision-protocol, protected universal at priority -1).
1015
+ // The inline literal was deleted in R25 to eliminate the mirror pair.
1022
1016
  // T5 structural truncation: assemble sections in priority order so budget truncation
1023
1017
  // drops the LEAST critical sections first (session history), never skills.
1024
- // Priority high→low: ABA | skills | behavioral warnings | version | drift | briefing | SDM | history
1025
- // The header + critical rules are always at the top; history is appended last.
1018
+ // Priority high→low: skills | behavioral warnings | version | drift | briefing | SDM | history
1026
1019
  const criticalPrefix = `${MEMORY_BOUNDARY_PREFIX}📋 Session context for "${project}" (${level}):\n\n` +
1027
- abaProtocol +
1028
1020
  behavBlock +
1029
1021
  skillBlock +
1030
1022
  versionNote +
@@ -245,8 +245,13 @@ async function callOllamaGenerate(url, model, prompt, system, maxTokens, tempera
245
245
  if (data.error)
246
246
  return { ok: false, reason: `ollama_err:${data.error}` };
247
247
  const text = (data.message?.content ?? "").trim();
248
- if (!text)
249
- return { ok: false, reason: "empty_response" };
248
+ if (!text) {
249
+ // When think=true, the model may burn all tokens on <think> and
250
+ // produce empty content. Report this distinctly so the tier loop
251
+ // can retry the same model with think=false rather than skipping.
252
+ const hadThinking = !!(data.message?.thinking);
253
+ return { ok: false, reason: hadThinking ? "think_only" : "empty_response" };
254
+ }
250
255
  return { ok: true, text, doneReason: data.done_reason, promptTokens: data.prompt_eval_count, completionTokens: data.eval_count };
251
256
  }
252
257
  catch (err) {
@@ -561,7 +566,14 @@ export async function runInfer(args, deps) {
561
566
  anyViable = true;
562
567
  const timeout = args.timeout_ms ?? DEFAULT_TIMEOUTS[tier.tag] ?? 60_000;
563
568
  const enableThink = args.think ?? (mode !== "route");
564
- const result = await deps.callLocal(deps.ollamaUrl, ollamaName, args.prompt, args.system, maxTokens, temperature, timeout, enableThink);
569
+ let result = await deps.callLocal(deps.ollamaUrl, ollamaName, args.prompt, args.system, maxTokens, temperature, timeout, enableThink);
570
+ // Think-only retry: model burned all tokens on <think>, empty content.
571
+ // Retry same model with think=false rather than falling to a smaller tier.
572
+ // One-shot: think=false cannot re-trigger think_only (no thinking to burn).
573
+ if (!result.ok && result.reason === "think_only" && enableThink) {
574
+ debugLog(`[prism_infer] ${tier.tag} returned think-only — retrying with think=false`);
575
+ result = await deps.callLocal(deps.ollamaUrl, ollamaName, args.prompt, args.system, maxTokens, temperature, timeout, false);
576
+ }
565
577
  if (result.ok) {
566
578
  const { stripped, thinkOnly } = stripThink(result.text);
567
579
  const output = stripped;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.0.3",
3
+ "version": "20.0.4",
4
4
  "mcpName": "io.github.dcostenco/prism-coder",
5
5
  "description": "Prism Coder — Cognitive memory + tool-calling intelligence for AI agents. Mind Palace persistent memory (BFCL Gold Certified, 100% Tool-Call Accuracy, 114 Agent Skills, PHI Guard, Tier Enforcement, Prompt-Based Skill Routing, Zero-Search HDC/HRR retrieval, HRR Semantic Drift Detection across BCBA/Coding/AAC domains, HIPAA-hardened local-first storage, SLERP-optimized GRPO alignment) plus the prism-coder 1.7B–32B open-weights LLM fleet.",
6
6
  "module": "index.ts",