prism-mcp-server 20.2.6 → 20.2.8

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.
@@ -7,11 +7,14 @@
7
7
  * This closes the gap where users have to learn tool syntax to query memories.
8
8
  * Instead, they ask plain English questions and get structured results.
9
9
  *
10
- * Pipeline:
10
+ * Parsing pipeline:
11
11
  * 1. Intent classification (decision, todo, file, general)
12
12
  * 2. Key phrase extraction
13
- * 3. Multi-strategy search (keyword + semantic)
14
- * 4. Answer synthesis (optional, uses local LLM)
13
+ * 3. Structured memory-query construction
14
+ *
15
+ * Execution, web fallback, and grounded prism_infer synthesis live in
16
+ * queryMemoryNaturalHandler.ts. Keeping network/model work out of this parser
17
+ * makes intent handling deterministic and independently testable.
15
18
  */
16
19
  import { debugLog } from "./logger.js";
17
20
  // ─── Intent Classification ───────────────────────────────────
@@ -151,34 +154,4 @@ export function parseNLQuery(query, project) {
151
154
  suggestedArgs: args,
152
155
  };
153
156
  }
154
- /**
155
- * Execute a natural language query end-to-end.
156
- *
157
- * Parses the query, executes the appropriate tool, and optionally
158
- * synthesizes an answer using the local LLM.
159
- */
160
- export async function executeNLQuery(query, project, synthesize = false) {
161
- const parsed = parseNLQuery(query, project);
162
- // Execute the search
163
- let sources = [];
164
- let answer = "";
165
- try {
166
- // Return the parsed query structure — the caller (handler) will
167
- // execute the actual tool call using the suggestedTool and suggestedArgs.
168
- answer =
169
- `Intent: ${parsed.intent} (confidence: ${(parsed.confidence * 100).toFixed(0)}%)\n` +
170
- `Keywords: ${parsed.extractedKeywords.join(", ")}\n` +
171
- `Suggested tool: ${parsed.suggestedTool}\n` +
172
- `Search query: "${parsed.searchQuery}"`;
173
- }
174
- catch (err) {
175
- debugLog(`NL query execution failed: ${err}`);
176
- answer = `Unable to parse query: ${err}`;
177
- }
178
- return {
179
- ...parsed,
180
- answer: answer || "No relevant memories found.",
181
- sources,
182
- };
183
- }
184
157
  debugLog("v12.2: Natural language query module loaded");
@@ -1,12 +1,4 @@
1
- /**
2
- * Quality Gate — deterministic check for obvious inference failures.
3
- *
4
- * NARROW by design: only high-precision signals that rarely false-positive.
5
- * Does NOT judge correctness — that's the grounding verifier's job.
6
- * Does NOT use refusal regex (too many false positives on legitimate output).
7
- *
8
- * Returns: { pass: boolean, reason?: string }
9
- */
1
+ import { parseRouteOutput } from "./routeContract.js";
10
2
  /**
11
3
  * Signal 5 — Tool-call bleed: pipe-delimited format leaking into non-tool turns.
12
4
  * Matches <|tool_call|> and <|tool_call_end|> only — NOT angle-bracket <tool_call> variants
@@ -37,11 +29,22 @@ export function passesQualityGate(stripped, thinkOnly, finishReason, mode) {
37
29
  if (finishReason === "length") {
38
30
  return { pass: false, reason: "hard_truncation" };
39
31
  }
40
- // Signal 5: Tool-call bleed — fine-tuned 4b emits <|tool_call|> format in non-tool turns.
41
- // Pipe-delimited format only; angle-bracket variants are handled by normalizeToolCallFormat.
42
- // False-positive guard: requires the literal pipe tokens, not the words "tool call".
32
+ // Signal 5: Tool-call bleed. The pipe envelope is invalid in chat/code,
33
+ // but it is the canonical trained output in route mode. Route mode parses
34
+ // the whole envelope and fails only when the contract is malformed.
43
35
  if (TOOL_CALL_BLEED_RE.test(stripped)) {
44
- return { pass: false, reason: "tool_call_bleed" };
36
+ if (mode === "route") {
37
+ const parsedRoute = parseRouteOutput(stripped);
38
+ if (parsedRoute.kind === "tool_call") {
39
+ // Continue through the remaining generic loop checks.
40
+ }
41
+ else {
42
+ return { pass: false, reason: "route_tool_call_malformed" };
43
+ }
44
+ }
45
+ else {
46
+ return { pass: false, reason: "tool_call_bleed" };
47
+ }
45
48
  }
46
49
  // Signal 4: Exact-loop detection (two passes).
47
50
  //
@@ -54,7 +57,11 @@ export function passesQualityGate(stripped, thinkOnly, finishReason, mode) {
54
57
  .replace(/^#{1,6}\s+.*$/gm, "")
55
58
  .replace(/^[\s*-]*\*{1,2}[^*]+\*{1,2}:?\s*$/gm, "");
56
59
  const proseSentences = proseOnly.split(/[.!?\n]+/).map(s => s.trim()).filter(s => s.length > 10);
57
- if (proseSentences.length >= 6) {
60
+ // Plain source code often repeats structural lines across methods. In code
61
+ // mode those are not prose loops, and splitting on "." corrupts member
62
+ // access (`node = self.root` becomes `node = self`). Pass B below still
63
+ // catches egregious repetition at the higher threshold.
64
+ if (mode !== "code" && proseSentences.length >= 6) {
58
65
  const counts = new Map();
59
66
  for (const s of proseSentences) {
60
67
  const key = s.toLowerCase();
@@ -68,7 +75,17 @@ export function passesQualityGate(stripped, thinkOnly, finishReason, mode) {
68
75
  // inside fake code blocks or other structural elements. Higher
69
76
  // threshold avoids false positives on legitimate code patterns
70
77
  // (e.g. `node = self.root` × 4 is fine, × 5 is suspicious).
71
- const allSentences = stripped.split(/[.!?\n]+/).map(s => s.trim()).filter(s => s.length > 10);
78
+ // In code mode, periods are member-access operators, not sentence
79
+ // boundaries. Splitting `input.length` at "." made several different
80
+ // loops share the fragment `while (i < input` and falsely counted it as
81
+ // repeated output. Full-line matching still catches genuinely duplicated
82
+ // source while preserving valid repeated control-flow shapes.
83
+ const fullTextUnits = mode === "code"
84
+ ? stripped.split(/\n+/)
85
+ : stripped.split(/[.!?\n]+/);
86
+ const allSentences = fullTextUnits
87
+ .map(s => s.trim())
88
+ .filter(s => s.length > 10);
72
89
  if (allSentences.length >= 10) {
73
90
  const counts = new Map();
74
91
  for (const s of allSentences) {
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Generic route-output contract for prism_infer.
3
+ *
4
+ * This module deliberately contains no intent taxonomy or benchmark-derived
5
+ * routing heuristics. It only parses the public Prism tool-call envelope and
6
+ * enforces the caller-advertised registry before a host can act on a route.
7
+ */
8
+ export const DEFAULT_PRISM_ROUTE_TOOLS = new Set([
9
+ "session_load_context",
10
+ "session_save_ledger",
11
+ "session_save_handoff",
12
+ "session_compact_ledger",
13
+ "session_search_memory",
14
+ "knowledge_search",
15
+ "brave_web_search",
16
+ ]);
17
+ const PIPE_START = "<|tool_call|>";
18
+ const PIPE_END = "<|tool_call_end|>";
19
+ const ANGLE_START = "<tool_call>";
20
+ const ANGLE_END = "</tool_call>";
21
+ const TOOL_NAME_RE = /^[A-Za-z][A-Za-z0-9_.:-]{0,127}$/;
22
+ const MAX_ROUTE_OUTPUT_CHARS = 32_000;
23
+ const MAX_ROUTE_REASON_CHARS = 256;
24
+ const MAX_ARGUMENT_DEPTH = 32;
25
+ const MAX_ARGUMENT_NODES = 2_048;
26
+ const TOOL_CALL_KEYS = new Set(["name", "arguments", "args"]);
27
+ export function isRouteToolName(value) {
28
+ return typeof value === "string" && TOOL_NAME_RE.test(value);
29
+ }
30
+ function isBoundedJsonValue(root) {
31
+ const stack = [{ value: root, depth: 0 }];
32
+ let nodes = 0;
33
+ while (stack.length > 0) {
34
+ const current = stack.pop();
35
+ nodes += 1;
36
+ if (nodes > MAX_ARGUMENT_NODES || current.depth > MAX_ARGUMENT_DEPTH) {
37
+ return false;
38
+ }
39
+ if (current.value === null)
40
+ continue;
41
+ if (typeof current.value === "number") {
42
+ if (!Number.isFinite(current.value))
43
+ return false;
44
+ continue;
45
+ }
46
+ if (typeof current.value === "string" ||
47
+ typeof current.value === "boolean") {
48
+ continue;
49
+ }
50
+ if (typeof current.value !== "object")
51
+ return false;
52
+ const values = Array.isArray(current.value)
53
+ ? current.value
54
+ : Object.values(current.value);
55
+ for (const value of values) {
56
+ stack.push({ value, depth: current.depth + 1 });
57
+ }
58
+ }
59
+ return true;
60
+ }
61
+ function primitiveLeaves(value) {
62
+ const leaves = [];
63
+ const stack = [value];
64
+ while (stack.length > 0) {
65
+ const current = stack.pop();
66
+ if (current !== null && typeof current === "object") {
67
+ stack.push(...(Array.isArray(current)
68
+ ? current
69
+ : Object.values(current)));
70
+ }
71
+ else {
72
+ leaves.push(current);
73
+ }
74
+ }
75
+ return leaves;
76
+ }
77
+ function promptContainsPrimitive(prompt, value) {
78
+ const rendered = String(value).toLowerCase()
79
+ .replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
80
+ return new RegExp(`(^|[^A-Za-z0-9_.-])${rendered}($|[^A-Za-z0-9_.-])`)
81
+ .test(prompt.toLowerCase());
82
+ }
83
+ function promptContainsString(prompt, value) {
84
+ const normalizedValue = value.trim().toLowerCase();
85
+ if (!normalizedValue)
86
+ return false;
87
+ if (/^[A-Za-z0-9_.:-]+$/.test(normalizedValue)) {
88
+ const escaped = normalizedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
89
+ return new RegExp(`(^|[^A-Za-z0-9_.:-])${escaped}($|[^A-Za-z0-9_.:-])`)
90
+ .test(prompt.toLowerCase());
91
+ }
92
+ return prompt.toLowerCase().includes(normalizedValue);
93
+ }
94
+ function sameJsonValue(left, right) {
95
+ if (Object.is(left, right))
96
+ return true;
97
+ if (Array.isArray(left) || Array.isArray(right)) {
98
+ return Array.isArray(left) &&
99
+ Array.isArray(right) &&
100
+ left.length === right.length &&
101
+ left.every((entry, index) => sameJsonValue(entry, right[index]));
102
+ }
103
+ if (left === null ||
104
+ right === null ||
105
+ typeof left !== "object" ||
106
+ typeof right !== "object") {
107
+ return false;
108
+ }
109
+ const leftRecord = left;
110
+ const rightRecord = right;
111
+ const leftKeys = Object.keys(leftRecord).sort();
112
+ const rightKeys = Object.keys(rightRecord).sort();
113
+ return leftKeys.length === rightKeys.length &&
114
+ leftKeys.every((key, index) => key === rightKeys[index] &&
115
+ sameJsonValue(leftRecord[key], rightRecord[key]));
116
+ }
117
+ function hasDerivedArguments(correctedArgs, originalArgs, originalPrompt) {
118
+ const originalLeaves = primitiveLeaves(originalArgs);
119
+ return primitiveLeaves(correctedArgs).every((leaf) => {
120
+ if (originalLeaves.some((candidate) => Object.is(candidate, leaf))) {
121
+ return true;
122
+ }
123
+ if (typeof leaf === "string") {
124
+ return promptContainsString(originalPrompt, leaf);
125
+ }
126
+ if (typeof leaf === "number" || typeof leaf === "boolean") {
127
+ return promptContainsPrimitive(originalPrompt, leaf);
128
+ }
129
+ return false;
130
+ });
131
+ }
132
+ function parseToolJson(raw) {
133
+ try {
134
+ const value = JSON.parse(raw);
135
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
136
+ return { kind: "malformed" };
137
+ }
138
+ const record = value;
139
+ if (!isRouteToolName(record.name)) {
140
+ return { kind: "malformed" };
141
+ }
142
+ if (Object.keys(record).some((key) => !TOOL_CALL_KEYS.has(key))) {
143
+ return { kind: "malformed" };
144
+ }
145
+ const hasArguments = Object.prototype.hasOwnProperty.call(record, "arguments");
146
+ const hasArgs = Object.prototype.hasOwnProperty.call(record, "args");
147
+ if (hasArguments && hasArgs)
148
+ return { kind: "malformed" };
149
+ const rawArgs = hasArguments
150
+ ? record.arguments
151
+ : hasArgs
152
+ ? record.args
153
+ : {};
154
+ if (!rawArgs || typeof rawArgs !== "object" || Array.isArray(rawArgs)) {
155
+ return { kind: "malformed" };
156
+ }
157
+ if (!isBoundedJsonValue(rawArgs))
158
+ return { kind: "malformed" };
159
+ return {
160
+ kind: "tool_call",
161
+ name: record.name,
162
+ args: rawArgs,
163
+ };
164
+ }
165
+ catch {
166
+ return { kind: "malformed" };
167
+ }
168
+ }
169
+ function parseEnvelope(output, startToken, endToken) {
170
+ const start = output.indexOf(startToken);
171
+ const end = output.indexOf(endToken, start + startToken.length);
172
+ if (start < 0 ||
173
+ end < 0 ||
174
+ output.indexOf(startToken, start + startToken.length) >= 0 ||
175
+ output.indexOf(endToken, end + endToken.length) >= 0) {
176
+ return { kind: "malformed" };
177
+ }
178
+ const before = output.slice(0, start).trim();
179
+ const after = output.slice(end + endToken.length).trim();
180
+ if (before || after)
181
+ return { kind: "malformed" };
182
+ return parseToolJson(output.slice(start + startToken.length, end).trim());
183
+ }
184
+ export function parseRouteOutput(output) {
185
+ if (output.length > MAX_ROUTE_OUTPUT_CHARS)
186
+ return { kind: "malformed" };
187
+ const trimmed = output.trim();
188
+ const hasPipeMarker = trimmed.includes(PIPE_START) || trimmed.includes(PIPE_END);
189
+ if (hasPipeMarker)
190
+ return parseEnvelope(trimmed, PIPE_START, PIPE_END);
191
+ const hasAngleMarker = trimmed.includes(ANGLE_START) || trimmed.includes(ANGLE_END);
192
+ if (hasAngleMarker)
193
+ return parseEnvelope(trimmed, ANGLE_START, ANGLE_END);
194
+ // A route answer that starts like raw tool JSON is an attempted contract
195
+ // response. Treat invalid JSON/shape as malformed rather than returning it
196
+ // as ordinary prose that a host might accidentally interpret.
197
+ if (trimmed.startsWith("{"))
198
+ return parseToolJson(trimmed);
199
+ return { kind: "plain_text" };
200
+ }
201
+ export function applyLocalRouteContract(draft, allowedTools = DEFAULT_PRISM_ROUTE_TOOLS) {
202
+ const parsed = parseRouteOutput(draft);
203
+ if (parsed.kind === "plain_text") {
204
+ return { output: draft, action: "plain_text", source: "local" };
205
+ }
206
+ if (parsed.kind === "malformed") {
207
+ return {
208
+ output: "NO_TOOL",
209
+ action: "suppressed",
210
+ source: "local",
211
+ reason: "malformed_tool_call",
212
+ };
213
+ }
214
+ if (parsed.name === "NO_TOOL") {
215
+ return { output: "NO_TOOL", action: "plain_text", source: "local" };
216
+ }
217
+ if (!allowedTools.has(parsed.name)) {
218
+ return {
219
+ output: "NO_TOOL",
220
+ action: "suppressed",
221
+ source: "local",
222
+ original_tool: parsed.name,
223
+ reason: "unadvertised_tool",
224
+ };
225
+ }
226
+ return {
227
+ output: draft,
228
+ action: "preserved",
229
+ source: "local",
230
+ original_tool: parsed.name,
231
+ final_tool: parsed.name,
232
+ };
233
+ }
234
+ /**
235
+ * Validate a private portal correction before trusting it.
236
+ *
237
+ * The portal is an authenticated service, but the public client still treats
238
+ * its response as untrusted network input. The declared action must agree with
239
+ * the output shape and with the original draft.
240
+ */
241
+ export function validatePortalRouteGuardOutcome(value, originalDraft, allowedTools, originalPrompt = "") {
242
+ if (!value || typeof value !== "object" || Array.isArray(value))
243
+ return null;
244
+ const record = value;
245
+ if (typeof record.output !== "string" ||
246
+ record.output.length === 0 ||
247
+ record.output.length > MAX_ROUTE_OUTPUT_CHARS ||
248
+ record.source !== "portal" ||
249
+ !["plain_text", "preserved", "remapped", "suppressed"].includes(typeof record.action === "string" ? record.action : "")) {
250
+ return null;
251
+ }
252
+ if (record.original_tool !== undefined &&
253
+ !isRouteToolName(record.original_tool)) {
254
+ return null;
255
+ }
256
+ if (record.final_tool !== undefined &&
257
+ !isRouteToolName(record.final_tool)) {
258
+ return null;
259
+ }
260
+ if (record.reason !== undefined &&
261
+ (typeof record.reason !== "string" ||
262
+ record.reason.length > MAX_ROUTE_REASON_CHARS)) {
263
+ return null;
264
+ }
265
+ const action = record.action;
266
+ const original = parseRouteOutput(originalDraft);
267
+ const corrected = parseRouteOutput(record.output);
268
+ if (action === "plain_text") {
269
+ if (original.kind !== "plain_text" || corrected.kind !== "plain_text") {
270
+ return null;
271
+ }
272
+ }
273
+ else if (action === "suppressed") {
274
+ if (original.kind !== "tool_call" ||
275
+ record.output.trim() !== "NO_TOOL" ||
276
+ (record.original_tool !== undefined &&
277
+ record.original_tool !== original.name) ||
278
+ record.final_tool !== undefined) {
279
+ return null;
280
+ }
281
+ }
282
+ else {
283
+ if (original.kind !== "tool_call" ||
284
+ corrected.kind !== "tool_call" ||
285
+ !allowedTools.has(corrected.name) ||
286
+ record.original_tool !== original.name ||
287
+ record.final_tool !== corrected.name) {
288
+ return null;
289
+ }
290
+ if (action === "preserved" && corrected.name !== original.name) {
291
+ return null;
292
+ }
293
+ if (action === "remapped" && corrected.name === original.name) {
294
+ return null;
295
+ }
296
+ if (action === "preserved" &&
297
+ !sameJsonValue(corrected.args, original.args)) {
298
+ return null;
299
+ }
300
+ if (action === "remapped" &&
301
+ !hasDerivedArguments(corrected.args, original.args, originalPrompt)) {
302
+ return null;
303
+ }
304
+ }
305
+ return {
306
+ output: record.output,
307
+ action,
308
+ source: "portal",
309
+ ...(record.original_tool !== undefined
310
+ ? { original_tool: record.original_tool }
311
+ : {}),
312
+ ...(record.final_tool !== undefined
313
+ ? { final_tool: record.final_tool }
314
+ : {}),
315
+ ...(record.reason !== undefined
316
+ ? { reason: record.reason }
317
+ : {}),
318
+ };
319
+ }
@@ -69,7 +69,7 @@ async function portalPost(path, body, timeoutMs = 15_000) {
69
69
  * the shape of performWebSearch() in braveApi.ts.
70
70
  */
71
71
  export async function synaluxWebSearch(query, count = 10) {
72
- debugLog(`[synaluxSearch] web search: q="${query}", limit=${count}`);
72
+ debugLog(`[synaluxSearch] web search: query_chars=${query.length}, limit=${count}`);
73
73
  const data = await portalPost("/api/v1/prism/search", {
74
74
  query,
75
75
  limit: Math.min(count, 20),
@@ -92,7 +92,7 @@ export async function synaluxWebSearch(query, count = 10) {
92
92
  * Used by code-mode handlers that pass raw data to the QuickJS sandbox.
93
93
  */
94
94
  export async function synaluxWebSearchRaw(query, count = 10) {
95
- debugLog(`[synaluxSearch] web search raw: q="${query}", limit=${count}`);
95
+ debugLog(`[synaluxSearch] web search raw: query_chars=${query.length}, limit=${count}`);
96
96
  const data = await portalPost("/api/v1/prism/search", {
97
97
  query,
98
98
  limit: Math.min(count, 20),
@@ -118,7 +118,7 @@ export async function synaluxWebSearchRaw(query, count = 10) {
118
118
  * Returns formatted text matching performLocalSearch() shape.
119
119
  */
120
120
  export async function synaluxLocalSearch(query, count = 5) {
121
- debugLog(`[synaluxSearch] local search: q="${query}", count=${count}`);
121
+ debugLog(`[synaluxSearch] local search: query_chars=${query.length}, count=${count}`);
122
122
  const data = await portalPost("/api/v1/prism/local-search", {
123
123
  query,
124
124
  count: Math.min(count, 20),
@@ -136,7 +136,7 @@ export async function synaluxLocalSearch(query, count = 5) {
136
136
  * Local/POI search raw — returns JSON string for code-mode sandbox.
137
137
  */
138
138
  export async function synaluxLocalSearchRaw(query, count = 5) {
139
- debugLog(`[synaluxSearch] local search raw: q="${query}", count=${count}`);
139
+ debugLog(`[synaluxSearch] local search raw: query_chars=${query.length}, count=${count}`);
140
140
  const data = await portalPost("/api/v1/prism/local-search", {
141
141
  query,
142
142
  count: Math.min(count, 20),
@@ -162,7 +162,7 @@ export async function synaluxLocalSearchRaw(query, count = 5) {
162
162
  * AI-grounded answers via Synalux portal.
163
163
  */
164
164
  export async function synaluxBraveAnswers(query, model) {
165
- debugLog(`[synaluxSearch] answers: q="${query}", model=${model || "default"}`);
165
+ debugLog(`[synaluxSearch] answers: query_chars=${query.length}, model=${model || "default"}`);
166
166
  const body = { query };
167
167
  if (model)
168
168
  body.model = model;
@@ -179,7 +179,7 @@ export async function synaluxBraveAnswers(query, model) {
179
179
  * Scrape a URL via Synalux portal. Returns the extracted content string.
180
180
  */
181
181
  export async function synaluxScrape(url, options) {
182
- debugLog(`[synaluxSearch] scrape: url="${url}"`);
182
+ debugLog(`[synaluxSearch] scrape: url_chars=${url.length}`);
183
183
  const body = { url };
184
184
  if (options?.formats)
185
185
  body.formats = options.formats;
@@ -187,9 +187,13 @@ export async function synaluxScrape(url, options) {
187
187
  body.onlyMainContent = options.onlyMainContent;
188
188
  if (options?.waitFor !== undefined)
189
189
  body.waitFor = options.waitFor;
190
- const data = await portalPost("/api/v1/prism/scrape", body);
190
+ const data = await portalPost("/api/v1/prism/scrape", body, options?.timeoutMs);
191
191
  if (data.status === "error") {
192
192
  throw new Error(`[synaluxSearch] scrape error: ${data.error || "unknown"}`);
193
193
  }
194
- return data.content || "";
194
+ return data.content ||
195
+ data.data?.markdown ||
196
+ data.data?.html ||
197
+ data.data?.rawHtml ||
198
+ "";
195
199
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.2.6",
3
+ "version": "20.2.8",
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 or subscription-gated Synalux storage, SLERP-optimized GRPO alignment) plus the prism-coder 1.7B–32B open-weights LLM fleet.",
6
6
  "module": "index.ts",
@@ -154,10 +154,11 @@
154
154
  "jsdom": "^29.0.1",
155
155
  "openai": "^6.32.0",
156
156
  "p-limit": "^7.3.0",
157
- "postcss": "8.5.12",
157
+ "postcss": "8.5.23",
158
158
  "quickjs-emscripten": "^0.32.0",
159
159
  "smol-toml": "^1.7.0",
160
160
  "stream-json": "^2.0.0",
161
+ "tldts": "^7.0.27",
161
162
  "turndown": "^7.2.2",
162
163
  "zod": "^4.3.6"
163
164
  }
@@ -1,46 +0,0 @@
1
- /**
2
- * boundaries.ts unit tests
3
- *
4
- * Verifies structural invariants of the safety declaration:
5
- * - BOUNDARIES_VERSION is present and non-empty
6
- * - BOUNDARIES_TEXT covers safety-critical concepts
7
- * - Architecture/routing docs moved to server instructions (not per-call)
8
- */
9
- import { describe, it, expect } from "vitest";
10
- import { BOUNDARIES_VERSION, BOUNDARIES_TEXT } from "../../boundaries/boundaries.js";
11
- describe("BOUNDARIES_VERSION", () => {
12
- it("is a non-empty string", () => {
13
- expect(typeof BOUNDARIES_VERSION).toBe("string");
14
- expect(BOUNDARIES_VERSION.length).toBeGreaterThan(0);
15
- });
16
- });
17
- describe("BOUNDARIES_TEXT structure", () => {
18
- it("is a non-empty string under 500 chars (compressed from ~2000)", () => {
19
- expect(typeof BOUNDARIES_TEXT).toBe("string");
20
- expect(BOUNDARIES_TEXT.length).toBeGreaterThan(50);
21
- expect(BOUNDARIES_TEXT.length).toBeLessThan(600);
22
- });
23
- it("has no leading or trailing whitespace (trim() was applied)", () => {
24
- expect(BOUNDARIES_TEXT).toBe(BOUNDARIES_TEXT.trim());
25
- });
26
- it("references BCBA reserved categories", () => {
27
- expect(BOUNDARIES_TEXT).toMatch(/BCBA reserved/i);
28
- });
29
- it("references fail-closed refusal for reserved content", () => {
30
- expect(BOUNDARIES_TEXT).toMatch(/refused/i);
31
- });
32
- it("states AAC access is never restricted as a consequence", () => {
33
- expect(BOUNDARIES_TEXT).toMatch(/AAC access is never restricted/i);
34
- });
35
- it("references restraint as reserved", () => {
36
- expect(BOUNDARIES_TEXT).toMatch(/restraint/i);
37
- });
38
- it("references crisis/self-harm interception", () => {
39
- expect(BOUNDARIES_TEXT).toMatch(/crisis|self-harm/i);
40
- });
41
- it("does NOT contain architecture/routing docs (those moved to server instructions)", () => {
42
- expect(BOUNDARIES_TEXT).not.toContain("session_save_ledger");
43
- expect(BOUNDARIES_TEXT).not.toContain("inference routing");
44
- expect(BOUNDARIES_TEXT).not.toMatch(/host note/i);
45
- });
46
- });