prism-mcp-server 20.5.2 → 20.5.3

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.
@@ -268,6 +268,16 @@ export async function knowledgeSearchHandler(args) {
268
268
  if (data.results && Array.isArray(data.results) && data.results.length > 0) {
269
269
  const evidenceSnippets = data.results.map((r, i) => ({
270
270
  source: `knowledge_search:${r.id ?? i}`,
271
+ // Carry the record date. Without it a two-year-old note and yesterday's
272
+ // are indistinguishable in the grounding evidence, so a stale memory is
273
+ // cited with the same confidence as a fresh one — local storage removes
274
+ // the external correction pressure that would otherwise catch it.
275
+ // session_date first: it is when the work HAPPENED, which is what a
276
+ // staleness signal needs. created_at is when the row was written and is
277
+ // assigned on save — it cannot be backdated, so an import would stamp
278
+ // every migrated memory as brand new, failing silently toward "fresh".
279
+ // sqlite.ts already treats session_date as authoritative.
280
+ recorded: r.session_date ?? r.created_at ?? r.updated_at ?? r.timestamp ?? undefined,
271
281
  content: (r.content ?? r.summary ?? r.text ?? "").slice(0, 1000),
272
282
  })).filter((s) => s.content);
273
283
  if (evidenceSnippets.length > 0) {
@@ -624,6 +634,7 @@ export async function sessionSearchMemoryHandler(args) {
624
634
  {
625
635
  const evidenceSnippets = results.map((r, i) => ({
626
636
  source: `session_search_memory:${r.id ?? i}`,
637
+ recorded: r.session_date ?? r.created_at ?? r.updated_at ?? r.timestamp ?? undefined,
627
638
  content: (r.summary ?? "").slice(0, 1000),
628
639
  })).filter((s) => s.content);
629
640
  if (evidenceSnippets.length > 0) {
@@ -55,7 +55,7 @@ const GROUNDED_SYNTHESIS_SYSTEM = "Answer the user's question using only the sup
55
55
  "For clinical or behavioral topics, provide educational candidates for credentialed review, " +
56
56
  "not individualized treatment instructions or professional sign-off. " +
57
57
  "If the evidence is insufficient, say exactly what is missing instead of guessing.";
58
- function extractMemorySources(result) {
58
+ export function extractMemorySources(result) {
59
59
  if (result.isError) {
60
60
  throw new Error(result.content[0]?.text || "knowledge_search failed");
61
61
  }
@@ -82,6 +82,7 @@ function extractMemorySources(result) {
82
82
  sources.push({
83
83
  type: "memory",
84
84
  source: snippet.source,
85
+ recorded: typeof snippet.recorded === "string" ? snippet.recorded : undefined,
85
86
  content: snippet.content.slice(0, MAX_EVIDENCE_CHARS),
86
87
  });
87
88
  }
@@ -224,13 +225,49 @@ const DEFAULT_DEPS = {
224
225
  function toEvidence(sources) {
225
226
  return sources.map(({ source, content }) => ({ source, content }));
226
227
  }
227
- function buildGroundedEvidenceContext(sources) {
228
+ /**
229
+ * SQLite `CURRENT_TIMESTAMP` writes "YYYY-MM-DD HH:MM:SS" in UTC with no zone,
230
+ * and `Date.parse` reads a zone-less stamp as LOCAL time. West of UTC that puts
231
+ * a ten-minute-old record hours in the FUTURE, which the guard above then hid
232
+ * entirely — the age disappeared exactly when the memory was freshest. Formats
233
+ * are not uniform across tables (semantic_knowledge writes ISO+Z, memory_links
234
+ * does not), so normalise rather than assume.
235
+ */
236
+ function parseRecordedAt(raw) {
237
+ const zoneless = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
238
+ return Date.parse(zoneless.test(raw) ? `${raw.replace(" ", "T")}Z` : raw);
239
+ }
240
+ /**
241
+ * " (recorded 2026-08-02, 431 days ago)" — or "" when the store gave no date.
242
+ * Never guesses: an absent date stays absent rather than defaulting to now,
243
+ * which would make the oldest memories look freshest.
244
+ */
245
+ export function describeAge(source) {
246
+ const recorded = source.type === "memory" ? source.recorded : undefined;
247
+ if (!recorded)
248
+ return "";
249
+ const at = parseRecordedAt(recorded);
250
+ if (Number.isNaN(at))
251
+ return "";
252
+ const elapsed = Date.now() - at;
253
+ // Tolerate clock skew between machines: slightly-future stamps are "today",
254
+ // not silence. Only a stamp more than a day ahead is treated as bad data.
255
+ if (elapsed < -86_400_000)
256
+ return "";
257
+ const days = Math.max(0, Math.floor(elapsed / 86_400_000));
258
+ const day = recorded.slice(0, 10);
259
+ return days === 0 ? ` (recorded ${day}, today)` : ` (recorded ${day}, ${days} days ago)`;
260
+ }
261
+ export function buildGroundedEvidenceContext(sources) {
228
262
  let remaining = MAX_SYNTHESIS_EVIDENCE_CHARS;
229
263
  const evidenceBlocks = [];
230
264
  for (const [index, source] of sources.entries()) {
231
265
  if (remaining <= 0)
232
266
  break;
233
- const label = `[SOURCE ${index + 1}: ${source.source}]`;
267
+ // Age belongs in the label, not just the payload. An undated fragment
268
+ // cannot be reasoned about; a fragment labelled two years old can be
269
+ // weighed against fresher evidence or challenged outright.
270
+ const label = `[SOURCE ${index + 1}: ${source.source}${describeAge(source)}]`;
234
271
  const availableForContent = Math.max(0, remaining - label.length - 1);
235
272
  if (availableForContent === 0)
236
273
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "20.5.2",
3
+ "version": "20.5.3",
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",