{
  "name": "assistant",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "assistant",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-assistant",
      "name": "Assistant Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        260,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const workflowName = 'assistant';\nconst payload = ($json.body && typeof $json.body === 'object') ? $json.body : $json;\nconst rawQuery = typeof payload.message === 'string' ? payload.message : payload.query;\nconst rawProjectSlug = payload.project_slug;\nconst rawSessionId = payload.session_id;\nconst rawTopK = payload.top_k;\nconst runId = typeof payload.run_id === 'string' && payload.run_id.trim() !== ''\n  ? payload.run_id.trim()\n  : 'cb-assistant-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);\nconst correlationId = typeof payload.correlation_id === 'string' && payload.correlation_id.trim() !== ''\n  ? payload.correlation_id.trim()\n  : runId;\nconst slugPattern = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;\nconst baseTrace = {\n  run_id: runId,\n  correlation_id: correlationId,\n  workflow_name: workflowName,\n  project_slug: null,\n  source_type: 'assistant_request',\n  filename: null,\n  filepath: null,\n  stage: 'received',\n  status: 'received',\n  error_code: null,\n  error_message: null,\n  timestamp: new Date().toISOString(),\n  workflow_chain: [workflowName],\n  stage_history: [],\n};\nconst withStage = (trace, stage, status, extra = {}) => {\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (trace.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (trace.error_message ?? null);\n  return {\n    ...trace,\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...(Array.isArray(trace.stage_history) ? trace.stage_history : []),\n      {\n        stage,\n        status,\n        timestamp,\n        error_code: errorCode,\n        error_message: errorMessage,\n      },\n    ],\n  };\n};\nconst buildError = (code, message) => [{\n  json: {\n    ok: false,\n    error: {\n      code,\n      message,\n      classification: 'validation',\n      retryable: false,\n    },\n    query: null,\n    session_id: null,\n    project_slug: null,\n    top_k: null,\n    retrieval: {\n      strategy: 'project-first-fallback-general',\n      project_match_count: 0,\n      general_match_count: 0,\n      memory_count: 0,\n      strongest_similarity: null,\n      similarity_threshold: 0.72,\n      empty: true,\n    },\n    sources: [],\n    context_preview: '',\n    session: {\n      turn_count_before: 0,\n      history_used: false,\n      stored: false,\n    },\n    trace: withStage(baseTrace, 'validation', 'rejected', {\n      error_code: code,\n      error_message: message,\n    }),\n  },\n}];\nif (typeof rawQuery !== 'string' || rawQuery.trim() === '') {\n  return buildError('INVALID_INPUT', 'Missing or invalid query/message');\n}\nif (rawProjectSlug !== undefined && rawProjectSlug !== null && typeof rawProjectSlug !== 'string') {\n  return buildError('INVALID_PROJECT_SLUG', 'project_slug must be a string when provided');\n}\nif (rawSessionId !== undefined && rawSessionId !== null && typeof rawSessionId !== 'string') {\n  return buildError('INVALID_SESSION_ID', 'session_id must be a string when provided');\n}\nif (rawTopK !== undefined && rawTopK !== null && (!Number.isInteger(rawTopK) || rawTopK < 1 || rawTopK > 8)) {\n  return buildError('INVALID_TOP_K', 'top_k must be an integer between 1 and 8');\n}\nconst query = rawQuery.trim();\nconst projectSlug = typeof rawProjectSlug === 'string' ? rawProjectSlug.trim() : '';\nif (projectSlug && !slugPattern.test(projectSlug)) {\n  return buildError('INVALID_PROJECT_SLUG', 'project_slug must use lowercase slug characters only');\n}\nconst sessionId = typeof rawSessionId === 'string' && rawSessionId.trim() !== ''\n  ? rawSessionId.trim()\n  : 'crispybrain-session-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);\nconst topK = Number.isInteger(rawTopK) ? rawTopK : 5;\nreturn [{\n  json: {\n    request_ok: true,\n    query,\n    session_id: sessionId,\n    project_slug: projectSlug || null,\n    top_k: topK,\n    retrieval_strategy: 'project-first-fallback-general',\n    requested_at: new Date().toISOString(),\n    trace: withStage({\n      ...baseTrace,\n      project_slug: projectSlug || null,\n    }, 'normalized', 'accepted', {\n      project_slug: projectSlug || null,\n    }),\n  },\n}];"
      },
      "id": "code-normalize-assistant-request",
      "name": "Normalize Assistant Request",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        560,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "condition-request-ok",
              "leftValue": "={{ $json.request_ok === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-request-is-valid",
      "name": "Request Is Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        860,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COALESCE(\n  jsonb_agg(row_to_json(turns) ORDER BY turns.created_at),\n  '[]'::jsonb\n) AS session_turns\nFROM (\n  SELECT role, message_text, project_slug, created_at, metadata_json\n  FROM (\n    SELECT role, message_text, project_slug, created_at, metadata_json\n    FROM openbrain_chat_turns\n    WHERE session_id = $1::text\n    ORDER BY created_at DESC\n    LIMIT 6\n  ) recent\n  ORDER BY created_at ASC\n) turns;",
        "options": {
          "queryReplacement": "={{ [$json.session_id] }}"
        }
      },
      "id": "postgres-load-session-turns",
      "name": "Load Session Turns",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1160,
        160
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://host.docker.internal:11434/api/embed",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'nomic-embed-text', input: $('Normalize Assistant Request').first().json.query, truncate: true }) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 120000
        }
      },
      "id": "http-generate-query-embedding",
      "name": "Generate Query Embedding",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1460,
        160
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "jsCode": "const request = $('Normalize Assistant Request').first().json;\nconst sessionTurns = $('Load Session Turns').first().json.session_turns ?? [];\nconst statusCode = $json.statusCode ?? 0;\nconst body = $json.body ?? {};\nconst embeddings = body.embeddings;\nconst stopTerms = new Set(['what', 'when', 'where', 'which', 'who', 'how', 'this', 'that', 'with', 'from', 'into', 'your', 'about', 'does', 'tell', 'note', 'for', 'before']);\nconst uniqueTerms = (values) => Array.from(new Set(values.filter(Boolean)));\nconst detectAnchorHeuristics = (query) => {\n  const value = typeof query === 'string' ? query.trim() : '';\n  const exactFilenameQueryMatch = value.match(/^Which note is named (.+?)\\?\\s*$/i);\n  const quotedTitlePhrases = Array.from(value.matchAll(/\"([^\"\\n]{3,})\"/g))\n    .map((match) => match[1].trim().toLowerCase())\n    .filter(Boolean);\n  const filenameLikeTokens = Array.from(value.matchAll(/\\b[a-z0-9][a-z0-9._-]*\\.[a-z0-9]{2,8}\\b/gi))\n    .map((match) => match[0].toLowerCase());\n  const slugLikeTokens = Array.from(value.matchAll(/\\b[a-z0-9]+(?:-[a-z0-9]+){2,}\\b/gi))\n    .map((match) => match[0].toLowerCase())\n    .filter((token) => token.length >= 12);\n  const mixedAnchorTokens = Array.from(value.matchAll(/\\b[a-z0-9_-]{6,}\\b/gi))\n    .map((match) => match[0].toLowerCase())\n    .filter((token) => (/[a-z]/.test(token) && /\\d/.test(token)) || token.includes('_') || token.includes('-'));\n  const allCapsCodeTokens = Array.from(value.matchAll(/\\b[A-Z]{2,}[A-Z0-9._/-]{1,}\\b/g))\n    .map((match) => match[0].toLowerCase());\n  const anchorTokens = uniqueTerms([\n    ...filenameLikeTokens,\n    ...slugLikeTokens,\n    ...mixedAnchorTokens,\n    ...allCapsCodeTokens,\n  ]);\n  return {\n    ranking_mode: exactFilenameQueryMatch || quotedTitlePhrases.length > 0 || anchorTokens.length > 0 ? 'anchor' : 'semantic',\n    requested_filename: exactFilenameQueryMatch ? exactFilenameQueryMatch[1].trim() : '',\n    quoted_title_phrases: quotedTitlePhrases,\n    anchor_tokens: anchorTokens,\n  };\n};\nconst buildQueryTerms = (query, anchorDetails) => {\n  const value = typeof query === 'string' ? query.trim() : '';\n  const requestedFilename = typeof anchorDetails?.requested_filename === 'string' ? anchorDetails.requested_filename.trim().toLowerCase() : '';\n  const identifierLikeTokens = Array.from(value.matchAll(/\\b[a-z0-9]+(?:[_:/.-][a-z0-9]+)+\\b/gi))\n    .map((match) => match[0].toLowerCase());\n  const protocolHintTokens = Array.from(value.matchAll(/\\b(?:protocol|anchor|code|id|identifier)\\s+([a-z0-9._/-]{2,})\\b/gi))\n    .map((match) => match[1].toLowerCase());\n  const generalTerms = Array.from(value.toLowerCase().matchAll(/\\b[a-z0-9][a-z0-9._/-]{2,}\\b/g))\n    .map((match) => match[0])\n    .filter((term) => !stopTerms.has(term));\n  const queryTerms = uniqueTerms(generalTerms);\n  const strongQueryTokens = uniqueTerms([\n    requestedFilename,\n    ...(Array.isArray(anchorDetails?.quoted_title_phrases) ? anchorDetails.quoted_title_phrases : []),\n    ...(Array.isArray(anchorDetails?.anchor_tokens) ? anchorDetails.anchor_tokens : []),\n    ...identifierLikeTokens,\n    ...protocolHintTokens,\n  ]).filter((term) => term.length >= 2);\n  const lexicalQueryTerms = uniqueTerms([\n    ...queryTerms,\n    ...strongQueryTokens,\n  ]).filter((term) => term.length >= 4 || /\\d/.test(term));\n  return {\n    query_terms: queryTerms,\n    lexical_query_terms: lexicalQueryTerms.slice(0, 16),\n    strong_query_tokens: strongQueryTokens.slice(0, 12),\n  };\n};\nconst failureIntentTerms = new Set(['failure', 'failures', 'problem', 'problems', 'issue', 'issues', 'bug', 'bugs', 'breakdown', 'breakdowns', 'broke', 'mistake', 'mistakes', 'regression', 'regressions', 'incident', 'incidents']);\nconst failureIntentPhrases = ['went wrong', 'weak point', 'weak points'];\nconst detectFailureIntent = (query) => {\n  const value = typeof query === 'string' ? query.trim().toLowerCase() : '';\n  if (value === '') return false;\n  const tokens = new Set(value.match(/[a-z0-9]+/g) ?? []);\n  if ([...failureIntentTerms].some((term) => tokens.has(term))) return true;\n  return failureIntentPhrases.some((phrase) => value.includes(phrase));\n};\nconst withStage = (trace, stage, status, extra = {}) => {\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (trace.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (trace.error_message ?? null);\n  return {\n    ...trace,\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...(Array.isArray(trace.stage_history) ? trace.stage_history : []),\n      { stage, status, timestamp, error_code: errorCode, error_message: errorMessage },\n    ],\n  };\n};\nconst sessionTrace = withStage(request.trace, 'session_loaded', 'accepted', {\n  project_slug: request.project_slug ?? null,\n});\nif (statusCode < 200 || statusCode >= 300 || !Array.isArray(embeddings) || !Array.isArray(embeddings[0])) {\n  return [{\n    json: {\n      ...request,\n      session_turns: Array.isArray(sessionTurns) ? sessionTurns : [],\n      embedding_ok: false,\n      error: {\n        code: 'OLLAMA_EMBEDDING_FAILED',\n        message: 'Ollama embedding request failed',\n        status: statusCode || null,\n        details: body.error ?? null,\n        classification: 'transient',\n        retryable: true,\n      },\n      trace: withStage(sessionTrace, 'embedding_failed', 'failed', {\n        error_code: 'OLLAMA_EMBEDDING_FAILED',\n        error_message: 'Ollama embedding request failed',\n      }),\n    },\n  }];\n}\nconst vector = embeddings[0];\nconst anchorDetails = detectAnchorHeuristics(request.query);\nconst queryTermDetails = buildQueryTerms(request.query, anchorDetails);\nconst isFailureIntent = detectFailureIntent(request.query);\nconst uncertaintyLexiconPattern = /\\b(uncertain|uncertainty|not well documented|incomplete|not fully know|missing from the record|not documented)\\b/i;\nconst applyQueryLexicon = (details, query) => {\n  const extraLexicalTerms = [];\n  const extraStrongTokens = [];\n  const value = typeof query === 'string' ? query : '';\n  if (isFailureIntent) {\n    extraLexicalTerms.push('failure', 'failures', 'problem', 'problems', 'issue', 'issues', 'bug', 'bugs', 'breakdown', 'breakdowns', 'weakness', 'weak point', 'weak points', 'mistake', 'mistakes', 'incident', 'incidents', 'regression', 'regressions', 'went wrong', 'broke');\n    extraStrongTokens.push('failures', 'problems', 'issues', 'bugs', 'breakdowns', 'weakness', 'mistakes', 'incidents', 'regressions', 'broke');\n  }\n  if (uncertaintyLexiconPattern.test(value)) {\n    extraLexicalTerms.push('uncertain', 'incomplete', 'not documented', 'not well documented', 'early development');\n    extraStrongTokens.push('uncertain', 'incomplete', 'not documented');\n  }\n  return {\n    query_terms: uniqueTerms(details.query_terms || []),\n    lexical_query_terms: uniqueTerms([...(details.lexical_query_terms || []), ...extraLexicalTerms]).slice(0, 24),\n    strong_query_tokens: uniqueTerms([...(details.strong_query_tokens || []), ...extraStrongTokens]).slice(0, 18),\n  };\n};\nconst enrichedQueryTermDetails = applyQueryLexicon(queryTermDetails, request.query);\nconst candidateLimit = anchorDetails.ranking_mode === 'anchor'\n  ? Math.min(Math.max(request.top_k * 14, request.top_k + 4), 80)\n  : Math.min(Math.max(request.top_k * 6, request.top_k + 4), 48);\nconst lexicalCandidateLimit = Math.min(Math.max(request.top_k * 4, request.top_k + 3), 32);\nreturn [{\n  json: {\n    ...request,\n    session_turns: Array.isArray(sessionTurns) ? sessionTurns : [],\n    embedding_ok: true,\n    anchor_details: anchorDetails,\n    is_failure_intent: isFailureIntent,\n    query_terms: enrichedQueryTermDetails.query_terms,\n    lexical_query_terms: enrichedQueryTermDetails.lexical_query_terms,\n    strong_query_tokens: enrichedQueryTermDetails.strong_query_tokens,\n    candidate_limit: candidateLimit,\n    lexical_candidate_limit: lexicalCandidateLimit,\n    vector_literal: '[' + vector.join(',') + ']',\n    trace: withStage(sessionTrace, 'embedding_ready', 'accepted'),\n  },\n}];"
      },
      "id": "code-prepare-retrieval-input",
      "name": "Prepare Retrieval Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        160
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "condition-embedding-ready",
              "leftValue": "={{ $json.embedding_ok === true }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-embedding-ready",
      "name": "Embedding Ready?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2060,
        160
      ]
    },
    {
      "parameters": {
        "jsCode": "const withStage = (trace, stage, status, extra = {}) => {\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (trace?.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (trace?.error_message ?? null);\n  return {\n    ...(trace || {}),\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...((trace && Array.isArray(trace.stage_history)) ? trace.stage_history : []),\n      { stage, status, timestamp, error_code: errorCode, error_message: errorMessage },\n    ],\n  };\n};\nconst grounding = {\n  status: 'none',\n  weak_grounding: true,\n  note: 'Grounding is unavailable because the embedding step failed before retrieval.',\n  reasons: ['embedding_failed'],\n  supporting_source_count: 0,\n  reviewed_source_count: 0,\n  strongest_similarity: null,\n  similarity_threshold: 0.72,\n  ranking_mode: null,\n  evidence_strength: 'none',\n  overall_trust_band: 'low',\n  primary_memory_ids: [],\n  primary_chunk_indexes: [],\n};\nreturn [{\n  json: {\n    ok: false,\n    error: $json.error,\n    query: $json.query,\n    session_id: $json.session_id,\n    project_slug: $json.project_slug ?? null,\n    top_k: $json.top_k,\n    retrieval: {\n      strategy: $json.retrieval_strategy,\n      project_match_count: 0,\n      general_match_count: 0,\n      lexical_project_match_count: 0,\n      lexical_general_match_count: 0,\n      lexical_all_match_count: 0,\n      memory_count: 0,\n      strongest_similarity: null,\n      similarity_threshold: 0.72,\n      empty: true,\n    },\n    trust: {\n      overall_band: 'low',\n      evidence_strength: 'none',\n      reviewed_source_count: 0,\n      unreviewed_source_count: 0,\n      scope_match_count: 0,\n      high_trust_source_count: 0,\n      medium_trust_source_count: 0,\n      low_trust_source_count: 0,\n      uncertainty_indicator: true,\n      uncertainty_reasons: ['embedding_failed'],\n    },\n    grounding,\n    sources: [],\n    selected_sources: [],\n    retrieved_candidates: [],\n    answer_mode: 'insufficient',\n    conflict_flag: false,\n    conflict_severity: null,\n    conflict_details: [],\n    claim_support_counts: [],\n    claim_support_counts_raw: [],\n    claim_support_counts_deduped: [],\n    claim_weighted_support: [],\n    claim_independent_support: [],\n    claim_independence_adjusted_support: [],\n    dominant_claim_status: null,\n    dominant_claim_basis: null,\n    claim_confidence: null,\n    conflict_summary_hint: null,\n    most_supported_claim: null,\n    most_recent_claim: null,\n    source_quality_breakdown: [],\n    source_independence_breakdown: [],\n    evidence_clusters: [],\n    entity_focus: null,\n    filtered_candidate_count: 0,\n    context_preview: '',\n    session: {\n      turn_count_before: Array.isArray($json.session_turns) ? $json.session_turns.length : 0,\n      history_used: Array.isArray($json.session_turns) ? $json.session_turns.length > 0 : false,\n      stored: false,\n    },\n    trace: withStage($json.trace, 'response_ready', 'failed', {\n      error_code: $json.error?.code ?? 'OLLAMA_EMBEDDING_FAILED',\n      error_message: $json.error?.message ?? 'Ollama embedding request failed',\n      grounding_status: grounding.status,\n      weak_grounding: grounding.weak_grounding,\n      answer_mode: 'insufficient',\n      conflict_flag: false,\n    }),\n  },\n}];"
      },
      "id": "code-build-embedding-failure-response",
      "name": "Build Embedding Failure Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2360,
        460
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "WITH lexical_terms AS (\n  SELECT DISTINCT lower(value) AS term\n  FROM jsonb_array_elements_text(COALESCE($4::jsonb, '[]'::jsonb)) AS value\n  WHERE btrim(value) <> ''\n),\nstrong_terms AS (\n  SELECT DISTINCT lower(value) AS term\n  FROM jsonb_array_elements_text(COALESCE($5::jsonb, '[]'::jsonb)) AS value\n  WHERE btrim(value) <> ''\n)\nSELECT\n  COALESCE((\n    SELECT jsonb_agg(row_to_json(project_rows) ORDER BY project_rows.review_priority, project_rows.distance, project_rows.created_at DESC NULLS LAST, project_rows.id DESC)\n    FROM (\n      SELECT id, created_at, title, source, category, content, metadata_json,\n        COALESCE(NULLIF(metadata_json->>'project_slug', ''), NULL) AS project_slug,\n        COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') AS review_status,\n        CASE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed')\n          WHEN 'reviewed' THEN 0\n          WHEN 'unreviewed' THEN 1\n          WHEN 'suspect' THEN 2\n          ELSE 3\n        END AS review_priority,\n        ROUND((1 - (embedding <=> $1::vector))::numeric, 6) AS similarity,\n        (embedding <=> $1::vector) AS distance\n      FROM memories\n      WHERE embedding IS NOT NULL\n        AND COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') <> 'suppressed'\n        AND $2::text IS NOT NULL\n        AND NULLIF(COALESCE(metadata_json->>'project_slug', ''), '') = $2::text\n      ORDER BY review_priority ASC, distance ASC, created_at DESC NULLS LAST, id DESC\n      LIMIT $3::int\n    ) project_rows\n  ), '[]'::jsonb) AS project_memories,\n  COALESCE((\n    SELECT jsonb_agg(row_to_json(general_rows) ORDER BY general_rows.review_priority, general_rows.distance, general_rows.created_at DESC NULLS LAST, general_rows.id DESC)\n    FROM (\n      SELECT id, created_at, title, source, category, content, metadata_json,\n        COALESCE(NULLIF(metadata_json->>'project_slug', ''), NULL) AS project_slug,\n        COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') AS review_status,\n        CASE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed')\n          WHEN 'reviewed' THEN 0\n          WHEN 'unreviewed' THEN 1\n          WHEN 'suspect' THEN 2\n          ELSE 3\n        END AS review_priority,\n        ROUND((1 - (embedding <=> $1::vector))::numeric, 6) AS similarity,\n        (embedding <=> $1::vector) AS distance\n      FROM memories\n      WHERE embedding IS NOT NULL\n        AND COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') <> 'suppressed'\n        AND $2::text IS NULL\n        AND COALESCE(NULLIF(metadata_json->>'project_slug', ''), '') = ''\n      ORDER BY review_priority ASC, distance ASC, created_at DESC NULLS LAST, id DESC\n      LIMIT $3::int\n    ) general_rows\n  ), '[]'::jsonb) AS general_memories,\n  COALESCE((\n    SELECT jsonb_agg(row_to_json(all_rows) ORDER BY all_rows.review_priority, all_rows.distance, all_rows.created_at DESC NULLS LAST, all_rows.id DESC)\n    FROM (\n      SELECT id, created_at, title, source, category, content, metadata_json,\n        COALESCE(NULLIF(metadata_json->>'project_slug', ''), NULL) AS project_slug,\n        COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') AS review_status,\n        CASE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed')\n          WHEN 'reviewed' THEN 0\n          WHEN 'unreviewed' THEN 1\n          WHEN 'suspect' THEN 2\n          ELSE 3\n        END AS review_priority,\n        ROUND((1 - (embedding <=> $1::vector))::numeric, 6) AS similarity,\n        (embedding <=> $1::vector) AS distance\n      FROM memories\n      WHERE embedding IS NOT NULL\n        AND COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') <> 'suppressed'\n        AND $2::text IS NULL\n      ORDER BY review_priority ASC, distance ASC, created_at DESC NULLS LAST, id DESC\n      LIMIT $3::int\n    ) all_rows\n  ), '[]'::jsonb) AS all_memories,\n  COALESCE((\n    SELECT jsonb_agg(row_to_json(project_lex_rows) ORDER BY project_lex_rows.strong_token_hits DESC, project_lex_rows.title_lexical_hits DESC, project_lex_rows.lexical_overlap DESC, project_lex_rows.review_priority ASC, project_lex_rows.created_at DESC NULLS LAST, project_lex_rows.id DESC)\n    FROM (\n      SELECT *\n      FROM (\n        SELECT id, created_at, title, source, category, content, metadata_json,\n          COALESCE(NULLIF(metadata_json->>'project_slug', ''), NULL) AS project_slug,\n          COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') AS review_status,\n          CASE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed')\n            WHEN 'reviewed' THEN 0\n            WHEN 'unreviewed' THEN 1\n            WHEN 'suspect' THEN 2\n            ELSE 3\n          END AS review_priority,\n          CASE WHEN embedding IS NOT NULL THEN ROUND((1 - (embedding <=> $1::vector))::numeric, 6) ELSE NULL END AS similarity,\n          CASE WHEN embedding IS NOT NULL THEN (embedding <=> $1::vector) ELSE NULL END AS distance,\n          (SELECT COUNT(*) FROM lexical_terms lt WHERE searchable.haystack LIKE '%' || lt.term || '%') AS lexical_overlap,\n          (SELECT COUNT(*) FROM lexical_terms lt WHERE searchable.title_haystack LIKE '%' || lt.term || '%') AS title_lexical_hits,\n          (SELECT COUNT(*) FROM strong_terms st WHERE searchable.haystack LIKE '%' || st.term || '%') AS strong_token_hits,\n          true AS lexical_match\n        FROM memories\n        CROSS JOIN LATERAL (\n          SELECT lower(concat_ws(E'\\n', COALESCE(title, ''), COALESCE(content, ''), COALESCE(metadata_json->>'filename', ''), COALESCE(metadata_json->>'filepath', ''))) AS haystack,\n            lower(concat_ws(E'\\n', COALESCE(title, ''), COALESCE(metadata_json->>'filename', ''))) AS title_haystack\n        ) searchable\n        WHERE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') <> 'suppressed'\n          AND $2::text IS NOT NULL\n          AND NULLIF(COALESCE(metadata_json->>'project_slug', ''), '') = $2::text\n      ) project_lex_seed\n      WHERE project_lex_seed.lexical_overlap > 0 OR project_lex_seed.strong_token_hits > 0\n      ORDER BY project_lex_seed.strong_token_hits DESC, project_lex_seed.title_lexical_hits DESC, project_lex_seed.lexical_overlap DESC, project_lex_seed.review_priority ASC, project_lex_seed.created_at DESC NULLS LAST, project_lex_seed.id DESC\n      LIMIT $6::int\n    ) project_lex_rows\n  ), '[]'::jsonb) AS lexical_project_memories,\n  COALESCE((\n    SELECT jsonb_agg(row_to_json(general_lex_rows) ORDER BY general_lex_rows.strong_token_hits DESC, general_lex_rows.title_lexical_hits DESC, general_lex_rows.lexical_overlap DESC, general_lex_rows.review_priority ASC, general_lex_rows.created_at DESC NULLS LAST, general_lex_rows.id DESC)\n    FROM (\n      SELECT *\n      FROM (\n        SELECT id, created_at, title, source, category, content, metadata_json,\n          COALESCE(NULLIF(metadata_json->>'project_slug', ''), NULL) AS project_slug,\n          COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') AS review_status,\n          CASE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed')\n            WHEN 'reviewed' THEN 0\n            WHEN 'unreviewed' THEN 1\n            WHEN 'suspect' THEN 2\n            ELSE 3\n          END AS review_priority,\n          CASE WHEN embedding IS NOT NULL THEN ROUND((1 - (embedding <=> $1::vector))::numeric, 6) ELSE NULL END AS similarity,\n          CASE WHEN embedding IS NOT NULL THEN (embedding <=> $1::vector) ELSE NULL END AS distance,\n          (SELECT COUNT(*) FROM lexical_terms lt WHERE searchable.haystack LIKE '%' || lt.term || '%') AS lexical_overlap,\n          (SELECT COUNT(*) FROM lexical_terms lt WHERE searchable.title_haystack LIKE '%' || lt.term || '%') AS title_lexical_hits,\n          (SELECT COUNT(*) FROM strong_terms st WHERE searchable.haystack LIKE '%' || st.term || '%') AS strong_token_hits,\n          true AS lexical_match\n        FROM memories\n        CROSS JOIN LATERAL (\n          SELECT lower(concat_ws(E'\\n', COALESCE(title, ''), COALESCE(content, ''), COALESCE(metadata_json->>'filename', ''), COALESCE(metadata_json->>'filepath', ''))) AS haystack,\n            lower(concat_ws(E'\\n', COALESCE(title, ''), COALESCE(metadata_json->>'filename', ''))) AS title_haystack\n        ) searchable\n        WHERE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') <> 'suppressed'\n          AND $2::text IS NULL\n          AND COALESCE(NULLIF(metadata_json->>'project_slug', ''), '') = ''\n      ) general_lex_seed\n      WHERE general_lex_seed.lexical_overlap > 0 OR general_lex_seed.strong_token_hits > 0\n      ORDER BY general_lex_seed.strong_token_hits DESC, general_lex_seed.title_lexical_hits DESC, general_lex_seed.lexical_overlap DESC, general_lex_seed.review_priority ASC, general_lex_seed.created_at DESC NULLS LAST, general_lex_seed.id DESC\n      LIMIT $6::int\n    ) general_lex_rows\n  ), '[]'::jsonb) AS lexical_general_memories,\n  COALESCE((\n    SELECT jsonb_agg(row_to_json(all_lex_rows) ORDER BY all_lex_rows.strong_token_hits DESC, all_lex_rows.title_lexical_hits DESC, all_lex_rows.lexical_overlap DESC, all_lex_rows.review_priority ASC, all_lex_rows.created_at DESC NULLS LAST, all_lex_rows.id DESC)\n    FROM (\n      SELECT *\n      FROM (\n        SELECT id, created_at, title, source, category, content, metadata_json,\n          COALESCE(NULLIF(metadata_json->>'project_slug', ''), NULL) AS project_slug,\n          COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') AS review_status,\n          CASE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed')\n            WHEN 'reviewed' THEN 0\n            WHEN 'unreviewed' THEN 1\n            WHEN 'suspect' THEN 2\n            ELSE 3\n          END AS review_priority,\n          CASE WHEN embedding IS NOT NULL THEN ROUND((1 - (embedding <=> $1::vector))::numeric, 6) ELSE NULL END AS similarity,\n          CASE WHEN embedding IS NOT NULL THEN (embedding <=> $1::vector) ELSE NULL END AS distance,\n          (SELECT COUNT(*) FROM lexical_terms lt WHERE searchable.haystack LIKE '%' || lt.term || '%') AS lexical_overlap,\n          (SELECT COUNT(*) FROM lexical_terms lt WHERE searchable.title_haystack LIKE '%' || lt.term || '%') AS title_lexical_hits,\n          (SELECT COUNT(*) FROM strong_terms st WHERE searchable.haystack LIKE '%' || st.term || '%') AS strong_token_hits,\n          true AS lexical_match\n        FROM memories\n        CROSS JOIN LATERAL (\n          SELECT lower(concat_ws(E'\\n', COALESCE(title, ''), COALESCE(content, ''), COALESCE(metadata_json->>'filename', ''), COALESCE(metadata_json->>'filepath', ''))) AS haystack,\n            lower(concat_ws(E'\\n', COALESCE(title, ''), COALESCE(metadata_json->>'filename', ''))) AS title_haystack\n        ) searchable\n        WHERE COALESCE(NULLIF(metadata_json->>'review_status', ''), 'unreviewed') <> 'suppressed'\n          AND $2::text IS NULL\n      ) all_lex_seed\n      WHERE all_lex_seed.lexical_overlap > 0 OR all_lex_seed.strong_token_hits > 0\n      ORDER BY all_lex_seed.strong_token_hits DESC, all_lex_seed.title_lexical_hits DESC, all_lex_seed.lexical_overlap DESC, all_lex_seed.review_priority ASC, all_lex_seed.created_at DESC NULLS LAST, all_lex_seed.id DESC\n      LIMIT $6::int\n    ) all_lex_rows\n  ), '[]'::jsonb) AS lexical_all_memories;",
        "options": {
          "queryReplacement": "={{ [$json.vector_literal, $json.project_slug, $json.candidate_limit, JSON.stringify($json.lexical_query_terms || []), JSON.stringify($json.strong_query_tokens || []), $json.lexical_candidate_limit] }}"
        }
      },
      "id": "postgres-retrieve-candidate-memories",
      "name": "Retrieve Candidate Memories",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        2360,
        20
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const base = $('Prepare Retrieval Input').first().json;\nconst rawProjectMemories = Array.isArray($json.project_memories) ? $json.project_memories : [];\nconst rawGeneralMemories = Array.isArray($json.general_memories) ? $json.general_memories : [];\nconst rawAllMemories = Array.isArray($json.all_memories) ? $json.all_memories : [];\nconst rawLexicalProjectMemories = Array.isArray($json.lexical_project_memories) ? $json.lexical_project_memories : [];\nconst rawLexicalGeneralMemories = Array.isArray($json.lexical_general_memories) ? $json.lexical_general_memories : [];\nconst rawLexicalAllMemories = Array.isArray($json.lexical_all_memories) ? $json.lexical_all_memories : [];\nconst requestedProjectSlug = typeof base.project_slug === 'string' && base.project_slug.trim() !== '' ? base.project_slug.trim() : null;\nconst matchesRequestedProjectScope = (memory) => {\n  if (!memory) return false;\n  if (!requestedProjectSlug) return true;\n  const metadata = memory.metadata_json && typeof memory.metadata_json === 'object' ? memory.metadata_json : {};\n  const candidateProjectSlug = typeof memory.project_slug === 'string' && memory.project_slug.trim() !== ''\n    ? memory.project_slug.trim()\n    : (typeof metadata.project_slug === 'string' && metadata.project_slug.trim() !== '' ? metadata.project_slug.trim() : null);\n  return candidateProjectSlug === requestedProjectSlug;\n};\nconst projectMemories = rawProjectMemories.filter(matchesRequestedProjectScope);\nconst generalMemories = requestedProjectSlug ? [] : rawGeneralMemories.filter(matchesRequestedProjectScope);\nconst allMemories = requestedProjectSlug ? [] : rawAllMemories.filter(matchesRequestedProjectScope);\nconst lexicalProjectMemories = rawLexicalProjectMemories.filter(matchesRequestedProjectScope);\nconst lexicalGeneralMemories = requestedProjectSlug ? [] : rawLexicalGeneralMemories.filter(matchesRequestedProjectScope);\nconst lexicalAllMemories = requestedProjectSlug ? [] : rawLexicalAllMemories.filter(matchesRequestedProjectScope);\nconst topK = base.top_k;\nconst candidateLimit = Number.isInteger(base.candidate_limit) ? base.candidate_limit : topK;\nconst lexicalCandidateLimit = Number.isInteger(base.lexical_candidate_limit) ? base.lexical_candidate_limit : Math.max(topK, 4);\nconst similarityThreshold = 0.72;\nconst stopTerms = new Set(['what', 'when', 'where', 'which', 'who', 'how', 'this', 'that', 'with', 'from', 'into', 'your', 'about', 'does', 'tell', 'note', 'for', 'before']);\nconst queryTerms = Array.isArray(base.query_terms)\n  ? base.query_terms.map((value) => String(value).trim().toLowerCase()).filter(Boolean)\n  : Array.from(new Set(((base.query || '').toLowerCase().match(/[a-z0-9]{3,}/g) ?? []).filter((term) => !stopTerms.has(term))));\nconst lexicalQueryTerms = Array.isArray(base.lexical_query_terms)\n  ? base.lexical_query_terms.map((value) => String(value).trim().toLowerCase()).filter(Boolean)\n  : queryTerms;\nconst strongQueryTokens = Array.isArray(base.strong_query_tokens)\n  ? base.strong_query_tokens.map((value) => String(value).trim().toLowerCase()).filter(Boolean)\n  : [];\nconst anchorDetails = base.anchor_details && typeof base.anchor_details === 'object' ? base.anchor_details : {};\nconst requestedFilename = typeof anchorDetails.requested_filename === 'string' ? anchorDetails.requested_filename.trim() : '';\nconst requestedFilenameLower = requestedFilename.toLowerCase();\nconst quotedTitlePhrases = Array.isArray(anchorDetails.quoted_title_phrases)\n  ? anchorDetails.quoted_title_phrases.map((value) => String(value).trim().toLowerCase()).filter(Boolean)\n  : [];\nconst anchorTokens = Array.isArray(anchorDetails.anchor_tokens)\n  ? anchorDetails.anchor_tokens.map((value) => String(value).trim().toLowerCase()).filter(Boolean)\n  : [];\nconst requestedRankingMode = anchorDetails.ranking_mode === 'anchor' ? 'anchor' : 'semantic';\nconst isFailureIntent = base.is_failure_intent === true;\nconst failureDomainTerms = ['problems', 'failures'];\nconst failureDomainPathHint = 'openbrain-history/04-problems-and-failures';\nconst matchesFailureDomain = (titleHaystack, filenameText, filepathText) => {\n  if (!isFailureIntent) return false;\n  if (filepathText.includes(failureDomainPathHint)) return true;\n  if (failureDomainTerms.some((term) => titleHaystack.includes(term))) return true;\n  return failureDomainTerms.some((term) => filenameText.includes(term));\n};\nconst normalizeReviewStatus = (value) => {\n  const normalized = typeof value === 'string' && value.trim() !== '' ? value.trim() : 'unreviewed';\n  if (['reviewed', 'suspect', 'suppressed', 'unreviewed'].includes(normalized)) return normalized;\n  return 'unreviewed';\n};\nconst reviewPriority = (value) => {\n  const normalized = normalizeReviewStatus(value);\n  if (normalized === 'reviewed') return 0;\n  if (normalized === 'unreviewed') return 1;\n  if (normalized === 'suspect') return 2;\n  return 3;\n};\nconst parseTimestamp = (value) => {\n  if (typeof value !== 'string' || value.trim() === '') return null;\n  const parsed = new Date(value);\n  if (Number.isNaN(parsed.getTime())) return null;\n  return parsed.toISOString();\n};\nconst timestampValue = (value) => {\n  const normalized = parseTimestamp(value);\n  return normalized ? Date.parse(normalized) : Number.NEGATIVE_INFINITY;\n};\nconst uniqueValues = (values) => Array.from(new Set(values.filter(Boolean)));\nconst normalizeTopicPhrase = (value) => String(value || '').toLowerCase().replace(/[\"']/g, '').replace(/[()\\[\\]{}]/g, ' ').replace(/\\b(the|a|an|this|that|note|memory)\\b/g, ' ').replace(/\\s+/g, ' ').trim();\nconst inferEntityFocus = (query) => {\n  const raw = typeof query === 'string' ? query.trim() : '';\n  if (raw === '') return null;\n  const patterns = [\n    { regex: /\\bwhat\\s+(protocol|anchor|status|owner|definition|meaning)\\s+does\\s+(.+?)\\s+(?:use|have|mean|map(?:\\s+to)?|point(?:\\s+to)?)\\b/i, map: (match) => ({ property: match[1], entity: match[2] }) },\n    { regex: /\\bwhat\\s+does\\s+(.+?)\\s+say\\b/i, map: (match) => ({ property: 'statement', entity: match[1] }) },\n    { regex: /\\bhow\\s+does\\s+(?:the\\s+)?(.+?)\\s+improve\\b/i, map: (match) => ({ property: 'improve', entity: match[1] }) },\n    { regex: /\\bwhat\\s+is\\s+the\\s+(.+?)\\s+(protocol|anchor|status|owner|definition|meaning)\\b/i, map: (match) => ({ entity: match[1], property: match[2] }) },\n    { regex: /\\bfind\\s+the\\s+note\\s+with\\s+(anchor|code|id|identifier)\\s+(.+)$/i, map: (match) => ({ property: match[1], terms: [normalizeTopicPhrase(match[2])] }) },\n  ];\n  let entity = null;\n  let property = null;\n  let terms = [];\n  for (const pattern of patterns) {\n    const match = raw.match(pattern.regex);\n    if (!match) continue;\n    const extracted = pattern.map(match);\n    entity = normalizeTopicPhrase(extracted.entity || '');\n    property = normalizeTopicPhrase(extracted.property || '');\n    terms = uniqueValues((entity ? entity.split(' ') : []).filter((term) => term.length >= 3 && !stopTerms.has(term)));\n    if (terms.length === 0 && Array.isArray(extracted.terms)) {\n      terms = uniqueValues(extracted.terms.flatMap((value) => normalizeTopicPhrase(value).split(' ')).filter((term) => term.length >= 3 && !stopTerms.has(term)));\n    }\n    break;\n  }\n  if (terms.length === 0) return null;\n  return {\n    entity: entity || null,\n    property: property || null,\n    terms: terms.slice(0, 6),\n  };\n};\nconst entityFocus = inferEntityFocus(base.query);\nconst entityFocusTerms = Array.isArray(entityFocus?.terms) ? entityFocus.terms : [];\nconst entityFocusLabel = typeof entityFocus?.entity === 'string' ? entityFocus.entity : '';\nconst propertyFocus = typeof entityFocus?.property === 'string' ? entityFocus.property : '';\nconst factualQueryPattern = /\\b(what|which|find|list|walk|describe|explain|summarize|outline|show|compare)\\b/i;\nconst conversationalFactualPattern = /\\b(tell me|walk me through|show me)\\b/i;\nconst uncertaintyQueryPattern = /\\b(uncertain|uncertainty|incomplete|not well documented|not documented|not fully know|unknown|history incomplete|partial evidence|missing from the record)\\b/i;\nconst contradictionQueryPattern = /\\b(contradict(?:ion|ory|ions)|conflicting claims|disagree|incompatible claims)\\b/i;\nconst uncertaintyQueryIntent = uncertaintyQueryPattern.test(base.query || '');\nconst contradictionQueryIntent = contradictionQueryPattern.test(base.query || '');\nconst isFactualQuery = requestedRankingMode === 'anchor' || strongQueryTokens.length > 0 || Boolean(propertyFocus) || factualQueryPattern.test(base.query || '') || conversationalFactualPattern.test(base.query || '');\nconst countContains = (haystack, needles) => needles.reduce((count, needle) => count + (needle && haystack.includes(needle) ? 1 : 0), 0);\nconst wordCount = (value) => (typeof value === 'string' ? (value.toLowerCase().match(/[a-z0-9]+/g) ?? []).length : 0);\nconst structuredSignalCount = (memory) => {\n  const metadata = memory?.metadata_json && typeof memory.metadata_json === 'object' ? memory.metadata_json : {};\n  const haystack = [memory?.title, memory?.content, metadata.filename, metadata.filepath].filter((value) => typeof value === 'string' && value.trim() !== '').join('\\n').toLowerCase();\n  const explicitTokens = uniqueValues([...anchorTokens, ...strongQueryTokens]);\n  const explicitMatches = countContains(haystack, explicitTokens);\n  return explicitMatches;\n};\nconst isUsableContent = (memory) => {\n  const value = typeof memory?.content === 'string' ? memory.content.trim() : '';\n  if (value.length < 8) return false;\n  if (value.includes('\\uFFFD')) return false;\n  const alphaCount = (value.match(/[A-Za-z]/g) ?? []).length;\n  const structuredSignals = structuredSignalCount(memory);\n  if (alphaCount < 4 && structuredSignals === 0) return false;\n  const safeCount = (value.match(/[A-Za-z0-9\\s.,:;!?()'\"_\\/-]/g) ?? []).length;\n  return safeCount / value.length >= 0.6;\n};\nconst recencyBand = (timestamp) => {\n  if (!timestamp) return 'unknown';\n  const ageMs = Date.now() - new Date(timestamp).getTime();\n  if (ageMs <= 24 * 60 * 60 * 1000) return 'recent_24h';\n  if (ageMs <= 7 * 24 * 60 * 60 * 1000) return 'recent_7d';\n  if (ageMs <= 30 * 24 * 60 * 60 * 1000) return 'recent_30d';\n  return 'older_than_30d';\n};\nconst chunkSizeBand = (length) => {\n  if (length < 80) return 'small';\n  if (length < 220) return 'medium';\n  if (length < 600) return 'large';\n  return 'xlarge';\n};\nconst summarizeMemory = (memory, scopeProjectSlug = base.project_slug) => {\n  const metadata = memory?.metadata_json && typeof memory?.metadata_json === 'object' ? memory.metadata_json : {};\n  return {\n    id: memory?.id ?? null,\n    title: memory?.title ?? null,\n    project_slug: memory?.project_slug ?? null,\n    review_status: normalizeReviewStatus(memory?.review_status ?? metadata.review_status),\n    source_quality: classifySourceQuality(memory, scopeProjectSlug),\n    source_quality_weight: sourceQualityWeight(classifySourceQuality(memory, scopeProjectSlug)),\n    similarity: typeof memory?.similarity === 'number' ? Number(memory.similarity.toFixed(6)) : null,\n    retrieval_score: typeof memory?.retrieval_score === 'number' ? Number(memory.retrieval_score.toFixed(6)) : null,\n    lexical_overlap: memory?.lexical_overlap ?? 0,\n    strong_token_hits: memory?.strong_token_hits ?? 0,\n    structured_token_hits: memory?.structured_token_hits ?? 0,\n    short_note_boost: typeof memory?.short_note_boost === 'number' ? Number(memory.short_note_boost.toFixed(6)) : 0,\n    lexical_match: memory?.lexical_match === true,\n    passes_relevance: memory?.passes_relevance === true,\n    created_at: parseTimestamp(memory?.created_at) ?? parseTimestamp(metadata.ingested_at),\n    project_match: scopeProjectSlug ? memory?.project_slug === scopeProjectSlug : !memory?.project_slug,\n    anchor_matched: memory?.anchor_matched ?? null,\n    entity_focus_hits: memory?.entity_focus_hits ?? 0,\n    property_focus_hits: memory?.property_focus_hits ?? 0,\n    entity_focus_match: memory?.entity_focus_match === true,\n    generic_runtime_noise: memory?.generic_runtime_noise === true,\n    intent_domain_match: memory?.intent_domain_match === true,\n    intent_domain_boost: typeof memory?.intent_domain_boost === 'number' ? Number(memory.intent_domain_boost.toFixed(6)) : 0,\n  };\n};\nconst semanticSeed = [];\nconst semanticSeen = new Set();\nconst addSemanticSeed = (memory) => {\n  if (!memory || semanticSeen.has(memory.id) || semanticSeed.length >= candidateLimit) return;\n  semanticSeen.add(memory.id);\n  semanticSeed.push(memory);\n};\nif (requestedProjectSlug) {\n  for (const memory of projectMemories) addSemanticSeed(memory);\n} else {\n  for (const memory of allMemories) addSemanticSeed(memory);\n}\nconst lexicalSeed = [];\nconst lexicalSeen = new Set();\nconst addLexicalSeed = (memory) => {\n  if (!memory || lexicalSeen.has(memory.id) || lexicalSeed.length >= lexicalCandidateLimit) return;\n  lexicalSeen.add(memory.id);\n  lexicalSeed.push(memory);\n};\nif (requestedProjectSlug) {\n  for (const memory of lexicalProjectMemories) addLexicalSeed(memory);\n} else {\n  for (const memory of lexicalAllMemories) addLexicalSeed(memory);\n}\nconst enrichMemory = (memory, lexicalFallbackActive) => {\n  const metadata = memory.metadata_json && typeof memory.metadata_json === 'object' ? memory.metadata_json : {};\n  const titleText = typeof memory.title === 'string' ? memory.title.trim() : '';\n  const contentText = typeof memory.content === 'string' ? memory.content.trim() : '';\n  const filenameText = typeof metadata.filename === 'string' ? metadata.filename.trim().toLowerCase() : '';\n  const filepathText = typeof metadata.filepath === 'string' ? metadata.filepath.trim().toLowerCase() : '';\n  const titleHaystack = [titleText, filenameText].filter(Boolean).join('\\n').toLowerCase();\n  const haystack = [titleText, contentText, filenameText, filepathText].filter(Boolean).join('\\n').toLowerCase();\n  const lexicalOverlap = Math.max(Number(memory.lexical_overlap ?? 0), lexicalQueryTerms.filter((term) => haystack.includes(term)).length);\n  const titleLexicalHits = Math.max(Number(memory.title_lexical_hits ?? 0), lexicalQueryTerms.filter((term) => titleHaystack.includes(term)).length);\n  const strongTokenHits = Math.max(Number(memory.strong_token_hits ?? 0), strongQueryTokens.filter((term) => haystack.includes(term)).length);\n  const similarity = typeof memory.similarity === 'number' ? memory.similarity : 0;\n  const reviewStatus = normalizeReviewStatus(memory.review_status ?? metadata.review_status);\n  const createdAt = parseTimestamp(memory.created_at) ?? parseTimestamp(metadata.ingested_at);\n  const exactTitleMatch = requestedFilenameLower !== '' && titleHaystack.includes(requestedFilenameLower);\n  const titleQuotedPhraseHits = countContains(titleHaystack, quotedTitlePhrases);\n  const quotedPhraseHits = countContains(haystack, quotedTitlePhrases);\n  const titleAnchorTokenHits = countContains(titleHaystack, anchorTokens);\n  const anchorTokenHits = countContains(haystack, anchorTokens);\n  const structuredTokenHits = structuredSignalCount(memory);\n  const contentWordCount = Math.max(wordCount(contentText), 1);\n  const contentLength = contentText.length;\n  const shortNoteBoost = contentLength <= 120 ? 0.045 : (contentLength <= 220 ? 0.025 : (contentLength <= 360 ? 0.01 : 0));\n  const signalDensity = Math.min((lexicalOverlap + strongTokenHits + structuredTokenHits) / contentWordCount, 0.4);\n  const densityBoost = signalDensity * 0.12;\n  const lexicalBoost = Math.min(lexicalOverlap, 4) * 0.0125;\n  const structuredBoost = Math.min(strongTokenHits + structuredTokenHits, 4) * 0.015;\n  const titleBoost = Math.min(titleLexicalHits + titleAnchorTokenHits, 3) * 0.0125;\n  const anchorMatched = exactTitleMatch || titleQuotedPhraseHits > 0 || quotedPhraseHits > 0 || titleAnchorTokenHits > 0 || anchorTokenHits > 0;\n  const entityFocusMatch = entityFocusLabel !== '' && haystack.includes(entityFocusLabel);\n  const entityFocusHits = entityFocusTerms.filter((term) => haystack.includes(term)).length;\n  const propertyFocusHits = propertyFocus && haystack.includes(propertyFocus) ? 1 : 0;\n  const subjectRelevance = entityFocusMatch || entityFocusHits > 0 || strongTokenHits > 0 || anchorMatched;\n  const topicRelevance = subjectRelevance || propertyFocusHits > 0;\n  const genericRuntimeNoise = ['build-context', 'runtime-fix', 'runtime-final', 'test-no-project', 'seed-memories', 'openbrain-seed-memories'].some((token) => titleHaystack.includes(token) || haystack.includes(token));\n  const genericNoiseEntityMismatch = entityFocusTerms.length > 0 && !entityFocusMatch && entityFocusHits < Math.min(2, entityFocusTerms.length);\n  const fallbackBoost = lexicalFallbackActive && (memory.lexical_match === true || lexicalOverlap > 0 || strongTokenHits > 0) ? 0.02 : 0;\n  const entityBoost = entityFocusMatch ? 0.07 : Math.min(entityFocusHits, 3) * 0.018;\n  const propertyBoost = propertyFocusHits > 0 ? 0.01 : 0;\n  const intentDomainMatch = matchesFailureDomain(titleHaystack, filenameText, filepathText);\n  const intentDomainBoost = intentDomainMatch ? 0.09 : 0;\n  const genericNoisePenalty = isFactualQuery && genericRuntimeNoise && (!subjectRelevance || genericNoiseEntityMismatch) ? 0.22 : 0;\n  const offTopicPenalty = isFactualQuery && entityFocusTerms.length > 0 && !subjectRelevance\n    ? (propertyFocusHits > 0 ? 0.04 : 0.08)\n    : 0;\n  const passesSemanticRelevance = similarity >= similarityThreshold || lexicalOverlap >= 1;\n  const passesLexicalFallback = (memory.lexical_match === true || lexicalOverlap > 0 || strongTokenHits > 0)\n    && (strongTokenHits > 0 || titleLexicalHits > 0 || lexicalOverlap >= 2 || structuredTokenHits > 0 || exactTitleMatch || entityFocusHits > 0 || propertyFocusHits > 0);\n  const filteredOut = isFactualQuery && genericRuntimeNoise && ((!subjectRelevance && lexicalOverlap <= 1) || genericNoiseEntityMismatch);\n  return {\n    ...memory,\n    review_status: reviewStatus,\n    review_priority: reviewPriority(reviewStatus),\n    exact_title_match: exactTitleMatch,\n    title_quoted_phrase_hits: titleQuotedPhraseHits,\n    quoted_phrase_hits: quotedPhraseHits,\n    title_anchor_token_hits: titleAnchorTokenHits,\n    anchor_token_hits: anchorTokenHits,\n    anchor_matched: anchorMatched,\n    created_at_sort_value: timestampValue(createdAt),\n    lexical_overlap: lexicalOverlap,\n    title_lexical_hits: titleLexicalHits,\n    strong_token_hits: strongTokenHits,\n    structured_token_hits: structuredTokenHits,\n    content_word_count: contentWordCount,\n    signal_density: signalDensity,\n    short_note_boost: shortNoteBoost,\n    lexical_match: memory.lexical_match === true || lexicalOverlap > 0 || strongTokenHits > 0,\n    entity_focus_hits: entityFocusHits,\n    property_focus_hits: propertyFocusHits,\n    entity_focus_match: entityFocusMatch,\n    generic_runtime_noise: genericRuntimeNoise,\n    intent_domain_match: intentDomainMatch,\n    intent_domain_boost: intentDomainBoost,\n    filtered_out: filteredOut,\n    filter_reason: filteredOut ? 'generic_runtime_noise' : null,\n    topic_relevance_score: (entityFocusMatch ? 3 : 0) + entityFocusHits + propertyFocusHits,\n    passes_semantic_relevance: passesSemanticRelevance,\n    passes_relevance: !filteredOut && (passesSemanticRelevance || (lexicalFallbackActive && passesLexicalFallback)),\n    retrieval_score: similarity + shortNoteBoost + densityBoost + lexicalBoost + structuredBoost + titleBoost + fallbackBoost + entityBoost + propertyBoost + intentDomainBoost - genericNoisePenalty - offTopicPenalty,\n  };\n};\nconst semanticUsable = semanticSeed.filter((memory) => isUsableContent(memory));\nconst semanticPreview = semanticUsable.map((memory) => enrichMemory(memory, false));\nconst semanticSortedPreview = [...semanticPreview].sort((left, right) => {\n  if (base.project_slug) {\n    const leftProject = left.project_slug === base.project_slug ? 1 : 0;\n    const rightProject = right.project_slug === base.project_slug ? 1 : 0;\n    if (rightProject !== leftProject) return rightProject - leftProject;\n  }\n  if (isFailureIntent) {\n    const leftDomain = left.intent_domain_match === true ? 1 : 0;\n    const rightDomain = right.intent_domain_match === true ? 1 : 0;\n    if (rightDomain !== leftDomain) return rightDomain - leftDomain;\n  }\n  if (left.review_priority !== right.review_priority) return left.review_priority - right.review_priority;\n  if ((right.retrieval_score ?? 0) !== (left.retrieval_score ?? 0)) return (right.retrieval_score ?? 0) - (left.retrieval_score ?? 0);\n  if ((right.similarity ?? 0) !== (left.similarity ?? 0)) return (right.similarity ?? 0) - (left.similarity ?? 0);\n  if (right.created_at_sort_value !== left.created_at_sort_value) return right.created_at_sort_value - left.created_at_sort_value;\n  return (right.id ?? 0) - (left.id ?? 0);\n});\nconst semanticStrongPreview = semanticSortedPreview.filter((memory) => memory.passes_semantic_relevance).slice(0, Math.max(topK, 2));\nconst semanticTopSimilarity = semanticStrongPreview[0]?.similarity ?? 0;\nconst semanticWeakOrSparse = semanticStrongPreview.length === 0 || semanticTopSimilarity < similarityThreshold + 0.03 || (semanticStrongPreview.length < 2 && semanticTopSimilarity < similarityThreshold + 0.06);\nconst shouldUseLexicalFallback = requestedRankingMode === 'anchor' || strongQueryTokens.length > 0 || semanticWeakOrSparse;\nconst candidateById = new Map();\nconst addCandidate = (memory, origin) => {\n  if (!memory || memory.id == null) return;\n  const existing = candidateById.get(memory.id);\n  const merged = {\n    ...(existing || {}),\n    ...memory,\n    similarity: memory.similarity ?? existing?.similarity ?? null,\n    distance: memory.distance ?? existing?.distance ?? null,\n    lexical_overlap: Math.max(Number(existing?.lexical_overlap ?? 0), Number(memory?.lexical_overlap ?? 0)),\n    title_lexical_hits: Math.max(Number(existing?.title_lexical_hits ?? 0), Number(memory?.title_lexical_hits ?? 0)),\n    strong_token_hits: Math.max(Number(existing?.strong_token_hits ?? 0), Number(memory?.strong_token_hits ?? 0)),\n    lexical_match: Boolean(existing?.lexical_match) || Boolean(memory?.lexical_match),\n    semantic_seed: Boolean(existing?.semantic_seed) || origin === 'semantic',\n    lexical_seed: Boolean(existing?.lexical_seed) || origin === 'lexical',\n  };\n  candidateById.set(memory.id, merged);\n};\nfor (const memory of semanticSeed) addCandidate(memory, 'semantic');\nif (shouldUseLexicalFallback) for (const memory of lexicalSeed) addCandidate(memory, 'lexical');\nconst selected = Array.from(candidateById.values());\nconst usableSelected = selected.filter((memory) => isUsableContent(memory));\nconst suspectMemoryCount = selected.length - usableSelected.length;\nconst scoredSelected = usableSelected.map((memory) => enrichMemory(memory, shouldUseLexicalFallback));\nconst filteredSelected = scoredSelected.filter((memory) => memory.filtered_out !== true);\nconst filteredCandidateCount = scoredSelected.length - filteredSelected.length;\nconst semanticSorted = [...filteredSelected].sort((left, right) => {\n  if (base.project_slug) {\n    const leftProject = left.project_slug === base.project_slug ? 1 : 0;\n    const rightProject = right.project_slug === base.project_slug ? 1 : 0;\n    if (rightProject !== leftProject) return rightProject - leftProject;\n  }\n  if (isFailureIntent) {\n    const leftDomain = left.intent_domain_match === true ? 1 : 0;\n    const rightDomain = right.intent_domain_match === true ? 1 : 0;\n    if (rightDomain !== leftDomain) return rightDomain - leftDomain;\n  }\n  if (left.review_priority !== right.review_priority) return left.review_priority - right.review_priority;\n  if ((right.retrieval_score ?? 0) !== (left.retrieval_score ?? 0)) return (right.retrieval_score ?? 0) - (left.retrieval_score ?? 0);\n  if ((right.similarity ?? 0) !== (left.similarity ?? 0)) return (right.similarity ?? 0) - (left.similarity ?? 0);\n  if (right.created_at_sort_value !== left.created_at_sort_value) return right.created_at_sort_value - left.created_at_sort_value;\n  return (right.id ?? 0) - (left.id ?? 0);\n});\nconst anchorSorted = [...filteredSelected].filter((memory) => memory.anchor_matched || memory.strong_token_hits > 0 || memory.title_lexical_hits > 0).sort((left, right) => {\n  if (left.exact_title_match !== right.exact_title_match) return left.exact_title_match ? -1 : 1;\n  if (right.title_quoted_phrase_hits !== left.title_quoted_phrase_hits) return right.title_quoted_phrase_hits - left.title_quoted_phrase_hits;\n  if (right.quoted_phrase_hits !== left.quoted_phrase_hits) return right.quoted_phrase_hits - left.quoted_phrase_hits;\n  if (right.title_anchor_token_hits !== left.title_anchor_token_hits) return right.title_anchor_token_hits - left.title_anchor_token_hits;\n  if (right.anchor_token_hits !== left.anchor_token_hits) return right.anchor_token_hits - left.anchor_token_hits;\n  if (right.strong_token_hits !== left.strong_token_hits) return right.strong_token_hits - left.strong_token_hits;\n  if (right.title_lexical_hits !== left.title_lexical_hits) return right.title_lexical_hits - left.title_lexical_hits;\n  if (isFailureIntent) {\n    const leftDomain = left.intent_domain_match === true ? 1 : 0;\n    const rightDomain = right.intent_domain_match === true ? 1 : 0;\n    if (rightDomain !== leftDomain) return rightDomain - leftDomain;\n  }\n  if (left.review_priority !== right.review_priority) return left.review_priority - right.review_priority;\n  if ((right.retrieval_score ?? 0) !== (left.retrieval_score ?? 0)) return (right.retrieval_score ?? 0) - (left.retrieval_score ?? 0);\n  if (right.created_at_sort_value !== left.created_at_sort_value) return right.created_at_sort_value - left.created_at_sort_value;\n  return (right.id ?? 0) - (left.id ?? 0);\n});\nconst rankingMode = requestedRankingMode === 'anchor' && anchorSorted.length > 0 ? 'anchor' : 'semantic';\nconst rankedStrongPool = (rankingMode === 'anchor' ? anchorSorted : semanticSorted).filter((memory) => memory.passes_relevance);\nconst selectCompetingMemories = (memories) => {\n  if (memories.length === 0) return [];\n  const baseLimit = Math.min(Math.max(topK + 2, topK), Math.max(topK + 4, 6));\n  const chosen = memories.slice(0, baseLimit);\n  const cutoff = chosen[chosen.length - 1]?.retrieval_score ?? null;\n  for (const memory of memories.slice(baseLimit)) {\n    if (chosen.length >= Math.min(baseLimit + 2, candidateLimit)) break;\n    const closeScore = cutoff !== null && Math.abs((memory.retrieval_score ?? 0) - cutoff) <= 0.025;\n    const preserveLexicalCompetitor = memory.strong_token_hits > 0 || memory.anchor_matched || memory.title_lexical_hits > 0;\n    const preserveFailureDomainCompetitor = isFailureIntent && memory.intent_domain_match === true;\n    if (closeScore || preserveLexicalCompetitor || preserveFailureDomainCompetitor) chosen.push(memory);\n  }\n  return chosen;\n};\nconst dedupeByContent = (memories) => {\n  const seen = new Set();\n  const result = [];\n  const normalizeKey = (value) => String(value || '').toLowerCase().replace(/[\\\"']/g, '').replace(/\\s+/g, ' ').trim();\n  for (const memory of memories) {\n    const key = normalizeKey(typeof memory?.content === 'string' ? memory.content.slice(0, 220) : '') || normalizeKey(memory?.title ?? '') || String(memory?.id ?? '');\n    if (seen.has(key)) continue;\n    seen.add(key);\n    result.push(memory);\n  }\n  return result;\n};\nconst strongMemories = selectCompetingMemories(rankedStrongPool);\nconst directStrongMemories = dedupeByContent(strongMemories);\nconst normalizePhrase = (value) => normalizeTopicPhrase(value);\nconst normalizeClaimValue = (value) => normalizePhrase(String(value || '').replace(/[.;,!?:]+$/g, ''));\nconst extractClaimSegments = (text) => {\n  if (typeof text !== 'string' || text.trim() === '') return [];\n  return text.split(/[\\n\\.]/).map((segment) => segment.trim()).filter((segment) => segment.length >= 8 && segment.length <= 160);\n};\nconst focusTerms = uniqueValues((entityFocusTerms.length > 0 ? [...entityFocusTerms, ...strongQueryTokens] : [...strongQueryTokens, ...queryTerms])).filter((term) => term.length >= 3).slice(0, 10);\nconst extractClaims = (memory) => {\n  const segments = uniqueValues([...extractClaimSegments(memory?.content), ...extractClaimSegments(memory?.title)]);\n  const claims = [];\n  for (const segment of segments) {\n    let match = segment.match(/^([A-Za-z0-9][A-Za-z0-9 _\\/-]{1,40}?)\\s+(?:uses|has)\\s+(protocol|anchor|status|owner|definition|meaning)\\s+(.{1,80})$/i);\n    let relation = null;\n    let subject = null;\n    let value = null;\n    if (match) {\n      subject = normalizePhrase(match[1]);\n      relation = normalizePhrase(match[2]);\n      value = normalizeClaimValue(match[3]);\n    } else {\n      match = segment.match(/^([A-Za-z0-9][A-Za-z0-9 _\\/-]{1,40}?)\\s+(protocol|anchor|status|owner|definition|meaning)\\s+(?:is|=|means|maps to|points to|uses)\\s+(.{1,80})$/i);\n      if (match) {\n        subject = normalizePhrase(match[1]);\n        relation = normalizePhrase(match[2]);\n        value = normalizeClaimValue(match[3]);\n      } else {\n        match = segment.match(/^([A-Za-z0-9][A-Za-z0-9 _\\/-]{1,40}?)\\s+(is|=|means|maps to|points to|uses)\\s+(.{1,80})$/i);\n        if (!match) continue;\n        subject = normalizePhrase(match[1]);\n        relation = normalizePhrase(match[2]);\n        value = normalizeClaimValue(match[3]);\n      }\n    }\n    if (!subject || !relation || !value || value === subject) continue;\n    if (value.split(' ').length > 10 || subject.split(' ').length > 8) continue;\n    const display = segment.replace(/\\s+/g, ' ').trim();\n    if (focusTerms.length > 0 && !focusTerms.some((term) => subject.includes(term) || value.includes(term) || display.toLowerCase().includes(term))) continue;\n    claims.push({ key: subject + '|' + relation, subject, relation, value, display });\n  }\n  return claims;\n};\nconst conflictBuckets = new Map();\nconst sourceQualityWeights = { low: 1.0, medium: 1.5, high: 2.0 };\nconst sourceIndependenceDiscounts = { independent: 1.0, related: 0.5, duplicate_like: 0.2, unclear: 0.35 };\nconst genericSourcePattern = /(build[-_ ]?context|runtime[-_ ]?(?:final|fix|context)|scratchpad|debug|tmp|test[-_ ]?no[-_ ]?project)/i;\nconst normalizeSourceFamily = (value) => {\n  if (typeof value !== 'string' || value.trim() === '') return '';\n  return value\n    .split(' :: ')[0]\n    .toLowerCase()\n    .replace(/\\.[a-z0-9]{1,8}$/i, '')\n    .replace(/-cbv\\d{6,}/g, '')\n    .replace(/[-_]+/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim();\n};\nconst tokenizeSupportSignature = (value) => uniqueValues(normalizeClaimValue(value).split(' ').filter((token) => token.length >= 3 && !stopTerms.has(token)));\nconst tokenOverlapRatio = (leftTokens, rightTokens) => {\n  const left = Array.isArray(leftTokens) ? leftTokens.filter(Boolean) : [];\n  const right = Array.isArray(rightTokens) ? rightTokens.filter(Boolean) : [];\n  const union = new Set([...left, ...right]);\n  if (union.size === 0) return 0;\n  const shared = left.filter((token) => right.includes(token)).length;\n  return shared / union.size;\n};\nconst sourceTextSimilarity = (leftEntry, rightEntry) => {\n  const excerptOverlap = tokenOverlapRatio(tokenizeSupportSignature(leftEntry?.excerpt_signature ?? ''), tokenizeSupportSignature(rightEntry?.excerpt_signature ?? ''));\n  const titleOverlap = tokenOverlapRatio(tokenizeSupportSignature(leftEntry?.source_title ?? ''), tokenizeSupportSignature(rightEntry?.source_title ?? ''));\n  return { excerptOverlap, titleOverlap, maxOverlap: Math.max(excerptOverlap, titleOverlap) };\n};\nconst compareClusterEntry = (entry, representative) => {\n  if (!entry || !representative) return 'unclear';\n  if (entry.support_identity && representative.support_identity && entry.support_identity === representative.support_identity) return 'duplicate_like';\n  const similarity = sourceTextSimilarity(entry, representative);\n  const sameFamily = entry.source_family && representative.source_family && entry.source_family === representative.source_family;\n  if (similarity.maxOverlap >= 0.92) return 'duplicate_like';\n  if (sameFamily && similarity.maxOverlap >= 0.45) return 'related';\n  if (similarity.excerptOverlap >= 0.55 || similarity.titleOverlap >= 0.72) return 'related';\n  return 'independent';\n};\nconst normalizeSupportIdentity = (memory) => {\n  const metadata = memory?.metadata_json && typeof memory?.metadata_json === 'object' ? memory.metadata_json : {};\n  const contentSeed = typeof memory?.content === 'string' ? memory.content.slice(0, 220) : '';\n  const titleSeed = typeof memory?.title === 'string' ? memory.title.split(' :: ')[0] : '';\n  const filenameSeed = typeof metadata.filename === 'string' ? metadata.filename : '';\n  const contentIdentity = normalizeClaimValue(contentSeed);\n  const titleIdentity = normalizeClaimValue(filenameSeed || titleSeed);\n  return contentIdentity || titleIdentity || String(memory?.id ?? 'unknown-support');\n};\nconst isGenericSourceTitle = (value) => {\n  if (typeof value !== 'string' || value.trim() === '') return false;\n  return genericSourcePattern.test(value.split(' :: ')[0]);\n};\nconst supportIdentityCounts = new Map();\nfor (const memory of strongMemories) {\n  const supportIdentity = normalizeSupportIdentity(memory);\n  supportIdentityCounts.set(supportIdentity, (supportIdentityCounts.get(supportIdentity) ?? 0) + 1);\n}\nconst classifySourceQuality = (memory, scopeProjectSlug = base.project_slug) => {\n  const metadata = memory?.metadata_json && typeof memory?.metadata_json === 'object' ? memory.metadata_json : {};\n  const reviewStatus = normalizeReviewStatus(memory?.review_status ?? metadata.review_status);\n  const scopeMatch = scopeProjectSlug ? memory?.project_slug === scopeProjectSlug : !memory?.project_slug;\n  const duplicateCandidate = metadata.duplicate_candidate === true;\n  const duplicateLike = (supportIdentityCounts.get(normalizeSupportIdentity(memory)) ?? 0) > 1;\n  const genericRuntimeNoise = memory?.generic_runtime_noise === true || isGenericSourceTitle(memory?.title ?? metadata.filename ?? '');\n  const specificitySignal = memory?.entity_focus_match === true\n    || memory?.anchor_matched === true\n    || (memory?.strong_token_hits ?? 0) > 0\n    || (memory?.structured_token_hits ?? 0) > 0\n    || (memory?.lexical_overlap ?? 0) >= 3;\n  if (duplicateCandidate || duplicateLike || genericRuntimeNoise || reviewStatus === 'suppressed' || reviewStatus === 'suspect') return 'low';\n  if (reviewStatus === 'reviewed' && scopeMatch && specificitySignal) return 'high';\n  return 'medium';\n};\nconst sourceQualityWeight = (quality) => sourceQualityWeights[quality] ?? sourceQualityWeights.medium;\nconst buildSourceQualityBreakdown = (entries) => {\n  const counts = new Map();\n  for (const entry of Array.isArray(entries) ? entries : []) {\n    const quality = typeof entry?.quality === 'string' ? entry.quality : 'medium';\n    const existing = counts.get(quality) ?? { quality, count: 0, weight: sourceQualityWeight(quality) };\n    existing.count += 1;\n    counts.set(quality, existing);\n  }\n  return ['high', 'medium', 'low']\n    .map((quality) => counts.get(quality))\n    .filter((entry) => entry && entry.count > 0);\n};\nfor (const memory of strongMemories) {\n  const memoryCreatedAt = parseTimestamp(memory.created_at) ?? parseTimestamp(memory?.metadata_json?.ingested_at);\n  const supportIdentity = normalizeSupportIdentity(memory);\n  const quality = classifySourceQuality(memory);\n  const qualityWeight = sourceQualityWeight(quality);\n  for (const claim of extractClaims(memory)) {\n    const bucket = conflictBuckets.get(claim.key) ?? { subject: claim.subject, relation: claim.relation, values: new Map() };\n    const existingValue = bucket.values.get(claim.value) ?? { value: claim.value, source_ids: [], source_titles: [], claim_texts: [], source_created_ats: [], latest_created_at: null, support_identities: [], source_quality_entries: [] };\n    if (!existingValue.source_ids.includes(memory.id)) {\n      existingValue.source_ids.push(memory.id);\n      existingValue.source_titles.push(memory.title ?? ('Memory ' + memory.id));\n    }\n    if (!existingValue.claim_texts.includes(claim.display)) existingValue.claim_texts.push(claim.display);\n    if (supportIdentity && !existingValue.support_identities.includes(supportIdentity)) existingValue.support_identities.push(supportIdentity);\n    if (!existingValue.source_quality_entries.some((entry) => entry.source_id === memory.id)) {\n      existingValue.source_quality_entries.push({\n        source_id: memory.id,\n        source_title: memory.title ?? ('Memory ' + memory.id),\n        quality,\n        weight: qualityWeight,\n        support_identity: supportIdentity,\n        source_family: normalizeSourceFamily(memory?.title ?? memory?.metadata_json?.filename ?? ''),\n        excerpt_signature: typeof memory?.content === 'string' ? memory.content.slice(0, 220) : '',\n        created_at: memoryCreatedAt,\n        source_independence: 'unclear',\n      });\n    }\n    if (memoryCreatedAt && !existingValue.source_created_ats.includes(memoryCreatedAt)) existingValue.source_created_ats.push(memoryCreatedAt);\n    if (memoryCreatedAt && (!existingValue.latest_created_at || Date.parse(memoryCreatedAt) > Date.parse(existingValue.latest_created_at))) existingValue.latest_created_at = memoryCreatedAt;\n    bucket.values.set(claim.value, existingValue);\n    conflictBuckets.set(claim.key, bucket);\n  }\n}\nconst tokenizeConflictValue = (value) => uniqueValues(normalizeClaimValue(value).split(' ').filter((token) => token.length >= 2 && !stopTerms.has(token)));\nconst genericConflictSubjectPattern = /^(there|it|this|that|history|project history|project|repo|record)$/;\nconst genericConflictRelationPattern = /^(is|=|means)$/;\nconst uncertaintyAbsencePattern = /\\b(no\\b|not\\b|uncertain|incomplete|missing|unknown|undocumented|not documented|not well documented|not fully known|no formal|no clean repo-visible|no dedicated)\\b/i;\nconst isAbsenceStyleClaim = (claim) => {\n  const value = String(claim?.value ?? '');\n  const display = String(claim?.claim_texts?.[0] ?? claim?.display ?? '');\n  return uncertaintyAbsencePattern.test(value) || uncertaintyAbsencePattern.test(display);\n};\nconst shouldSuppressConflictBucket = (bucket) => {\n  const subject = normalizePhrase(bucket?.topic ?? '');\n  const relation = normalizePhrase(bucket?.relation ?? bucket?.property ?? '');\n  const claims = Array.isArray(bucket?.claims) ? bucket.claims : [];\n  if (claims.length < 2) return false;\n  const genericSubject = genericConflictSubjectPattern.test(subject);\n  const genericRelation = genericConflictRelationPattern.test(relation);\n  const absenceOnly = claims.every((claim) => isAbsenceStyleClaim(claim));\n  if (genericSubject && genericRelation && absenceOnly) return true;\n  if (uncertaintyQueryIntent && genericRelation && absenceOnly) return true;\n  if (contradictionQueryIntent && genericRelation && absenceOnly) return true;\n  return false;\n};\nconst classifyConflictSeverity = (claims) => {\n  if (!Array.isArray(claims) || claims.length < 2) return null;\n  for (let index = 0; index < claims.length; index += 1) {\n    for (let inner = index + 1; inner < claims.length; inner += 1) {\n      const left = normalizeClaimValue(claims[index]?.value ?? '');\n      const right = normalizeClaimValue(claims[inner]?.value ?? '');\n      if (!left || !right || left === right) continue;\n      const leftContainsRight = left.includes(right) || right.includes(left);\n      const leftTokens = tokenizeConflictValue(left);\n      const rightTokens = tokenizeConflictValue(right);\n      const union = new Set([...leftTokens, ...rightTokens]);\n      const shared = leftTokens.filter((token) => rightTokens.includes(token)).length;\n      const overlap = union.size === 0 ? 0 : shared / union.size;\n      if (!leftContainsRight && overlap < 0.6) return 'strong_conflict';\n    }\n  }\n  return 'possible_conflict';\n};\nconst claimLatestTimestampValue = (claim) => {\n  const latestCreatedAt = typeof claim?.latest_created_at === 'string' ? claim.latest_created_at : null;\n  return latestCreatedAt ? Date.parse(latestCreatedAt) : Number.NEGATIVE_INFINITY;\n};\nconst allQualityEntries = (claim) => {\n  const seen = new Set();\n  const entries = [];\n  for (const entry of Array.isArray(claim?.source_quality_entries) ? claim.source_quality_entries : []) {\n    const sourceId = entry?.source_id ?? null;\n    const uniqueKey = sourceId !== null ? ('source:' + sourceId) : ('title:' + String(entry?.source_title ?? 'unknown'));\n    if (seen.has(uniqueKey)) continue;\n    seen.add(uniqueKey);\n    entries.push({\n      source_id: sourceId,\n      source_title: entry?.source_title ?? null,\n      quality: typeof entry?.quality === 'string' ? entry.quality : 'medium',\n      weight: Number(Number(entry?.weight ?? sourceQualityWeight(entry?.quality)).toFixed(2)),\n      support_identity: typeof entry?.support_identity === 'string' && entry.support_identity.trim() !== '' ? entry.support_identity : ('source:' + String(sourceId ?? 'unknown')),\n      source_family: typeof entry?.source_family === 'string' ? entry.source_family : '',\n      excerpt_signature: typeof entry?.excerpt_signature === 'string' ? entry.excerpt_signature : '',\n      created_at: typeof entry?.created_at === 'string' ? entry.created_at : null,\n      source_independence: typeof entry?.source_independence === 'string' ? entry.source_independence : 'unclear',\n    });\n  }\n  return entries;\n};\nconst uniqueQualityEntries = (claim) => {\n  const bestByIdentity = new Map();\n  for (const entry of allQualityEntries(claim)) {\n    const identity = entry.support_identity;\n    const existing = bestByIdentity.get(identity);\n    if (!existing || entry.weight > existing.weight) bestByIdentity.set(identity, entry);\n  }\n  return Array.from(bestByIdentity.values());\n};\nconst buildEvidenceClusters = (claim) => {\n  const entries = allQualityEntries(claim).sort((left, right) => Number(right.weight ?? 0) - Number(left.weight ?? 0) || timestampValue(right.created_at) - timestampValue(left.created_at) || String(left.source_title ?? '').localeCompare(String(right.source_title ?? '')));\n  const relationPriority = { duplicate_like: 3, related: 2, independent: 1, unclear: 0 };\n  const clusters = [];\n  for (const entry of entries) {\n    let matchedCluster = null;\n    let matchedRelation = 'independent';\n    let matchedPriority = 0;\n    for (const cluster of clusters) {\n      let clusterRelation = 'independent';\n      let clusterPriority = relationPriority.independent;\n      for (const member of Array.isArray(cluster.members) ? cluster.members : []) {\n        const memberRelation = compareClusterEntry(entry, member);\n        const memberPriority = relationPriority[memberRelation] ?? 0;\n        if (memberPriority > clusterPriority) {\n          clusterRelation = memberRelation;\n          clusterPriority = memberPriority;\n        }\n        if (memberPriority === relationPriority.duplicate_like) break;\n      }\n      if (clusterPriority > matchedPriority) {\n        matchedCluster = cluster;\n        matchedRelation = clusterRelation;\n        matchedPriority = clusterPriority;\n      }\n      if (matchedPriority === relationPriority.duplicate_like) break;\n    }\n    if (!matchedCluster || matchedPriority < relationPriority.related) {\n      clusters.push({ representative: entry, relations: [], members: [entry] });\n      continue;\n    }\n    matchedCluster.members.push(entry);\n    matchedCluster.relations.push(matchedRelation);\n    if ((relationPriority[matchedRelation] ?? 0) > (relationPriority[compareClusterEntry(matchedCluster.representative, entry)] ?? 0) || Number(entry.weight ?? 0) > Number(matchedCluster.representative?.weight ?? 0)) matchedCluster.representative = entry;\n  }\n  return clusters.map((cluster, index) => {\n    const relation = cluster.members.length <= 1\n      ? 'independent'\n      : (cluster.relations.includes('duplicate_like') ? 'duplicate_like' : (cluster.relations.includes('related') ? 'related' : 'unclear'));\n    const members = cluster.members.map((member) => ({ ...member, source_independence: relation })).sort((left, right) => Number(right.weight ?? 0) - Number(left.weight ?? 0) || timestampValue(right.created_at) - timestampValue(left.created_at) || String(left.source_title ?? '').localeCompare(String(right.source_title ?? '')));\n    return {\n      cluster_id: 'cluster_' + (index + 1),\n      relation,\n      total_weight: Number(members.reduce((total, member) => total + Number(member.weight ?? 0), 0).toFixed(2)),\n      member_source_ids: members.map((member) => member.source_id).filter((value) => value !== null && value !== undefined),\n      member_source_titles: members.map((member) => member.source_title).filter(Boolean),\n      members,\n    };\n  });\n};\nconst claimEvidenceClusters = (claim) => Array.isArray(claim?.evidence_clusters) ? claim.evidence_clusters : buildEvidenceClusters(claim);\nconst buildSourceIndependenceBreakdown = (entries) => {\n  const counts = new Map();\n  for (const entry of Array.isArray(entries) ? entries : []) {\n    const independence = typeof entry === 'string'\n      ? entry\n      : (typeof entry?.source_independence === 'string' ? entry.source_independence : 'unclear');\n    const key = independence || 'unclear';\n    counts.set(key, (counts.get(key) ?? 0) + 1);\n  }\n  return ['independent', 'related', 'duplicate_like', 'unclear']\n    .map((classification) => counts.has(classification) ? { source_independence: classification, count: counts.get(classification) } : null)\n    .filter(Boolean);\n};\nconst supportCountRaw = (claim) => Array.isArray(claim?.source_ids) ? claim.source_ids.length : 0;\nconst supportCountDeduped = (claim) => uniqueValues(Array.isArray(claim?.support_identities) ? claim.support_identities : []).length;\nconst supportWeighted = (claim) => uniqueQualityEntries(claim).reduce((total, entry) => total + Number(entry.weight ?? 0), 0);\nconst supportIndependent = (claim) => claimEvidenceClusters(claim).length;\nconst supportIndependenceAdjusted = (claim) => claimEvidenceClusters(claim).reduce((total, cluster) => total + cluster.members.reduce((clusterTotal, member, index) => {\n  if (index === 0) return clusterTotal + Number(member.weight ?? 0);\n  const multiplier = sourceIndependenceDiscounts[cluster.relation] ?? sourceIndependenceDiscounts.unclear;\n  return clusterTotal + (Number(member.weight ?? 0) * multiplier);\n}, 0), 0);\nconst buildClaimSupportCountsRaw = (claims) => claims\n  .map((claim) => ({ value: claim.value, count: supportCountRaw(claim) }))\n  .sort((left, right) => right.count - left.count || left.value.localeCompare(right.value));\nconst buildClaimSupportCountsDeduped = (claims) => claims\n  .map((claim) => ({ value: claim.value, count: supportCountDeduped(claim) }))\n  .sort((left, right) => right.count - left.count || left.value.localeCompare(right.value));\nconst buildClaimWeightedSupport = (claims) => claims\n  .map((claim) => ({ value: claim.value, weighted_support: Number(supportWeighted(claim).toFixed(2)) }))\n  .sort((left, right) => right.weighted_support - left.weighted_support || left.value.localeCompare(right.value));\nconst buildClaimIndependentSupport = (claims) => claims\n  .map((claim) => ({ value: claim.value, count: supportIndependent(claim) }))\n  .sort((left, right) => right.count - left.count || left.value.localeCompare(right.value));\nconst buildClaimIndependenceAdjustedSupport = (claims) => claims\n  .map((claim) => ({ value: claim.value, adjusted_support: Number(supportIndependenceAdjusted(claim).toFixed(2)) }))\n  .sort((left, right) => right.adjusted_support - left.adjusted_support || left.value.localeCompare(right.value));\nconst buildClaimSupportCounts = (claims) => buildClaimSupportCountsDeduped(claims);\nconst detectDominantClaimResolution = ({ claimIndependenceAdjustedSupport, claimWeightedSupport, claimSupportCountsDeduped }) => {\n  if (!Array.isArray(claimIndependenceAdjustedSupport) || claimIndependenceAdjustedSupport.length < 2 || !Array.isArray(claimWeightedSupport) || claimWeightedSupport.length < 2 || !Array.isArray(claimSupportCountsDeduped) || claimSupportCountsDeduped.length < 2) {\n    return { dominantClaimStatus: 'unclear', dominantClaimBasis: 'unclear' };\n  }\n  const topAdjusted = Number(claimIndependenceAdjustedSupport[0]?.adjusted_support ?? 0);\n  const secondAdjusted = Number(claimIndependenceAdjustedSupport[1]?.adjusted_support ?? 0);\n  if (topAdjusted > 0 && topAdjusted > secondAdjusted + 0.001) return { dominantClaimStatus: 'dominant', dominantClaimBasis: 'independence_adjusted_support' };\n  const topWeighted = Number(claimWeightedSupport[0]?.weighted_support ?? 0);\n  const secondWeighted = Number(claimWeightedSupport[1]?.weighted_support ?? 0);\n  if (topWeighted > 0 && topWeighted > secondWeighted + 0.001) return { dominantClaimStatus: 'dominant', dominantClaimBasis: 'weighted_support' };\n  const topDeduped = Number(claimSupportCountsDeduped[0]?.count ?? 0);\n  const secondDeduped = Number(claimSupportCountsDeduped[1]?.count ?? 0);\n  if (topDeduped > 0 && topDeduped > secondDeduped) return { dominantClaimStatus: 'dominant', dominantClaimBasis: 'deduped_support' };\n  if ((topAdjusted > 0 || topWeighted > 0 || topDeduped > 0) && Math.abs(topAdjusted - secondAdjusted) <= 0.001 && Math.abs(topWeighted - secondWeighted) <= 0.001 && topDeduped === secondDeduped) return { dominantClaimStatus: 'tie', dominantClaimBasis: 'tie' };\n  if (topAdjusted > 0 || topWeighted > 0 || topDeduped > 0) return { dominantClaimStatus: 'tie', dominantClaimBasis: 'tie' };\n  return { dominantClaimStatus: 'unclear', dominantClaimBasis: 'unclear' };\n};\nconst pickMostSupportedClaim = (claimSupportCountsDeduped, claimWeightedSupport, claimIndependentSupport, claimIndependenceAdjustedSupport, dominantClaimStatus, dominantClaimBasis) => {\n  if (dominantClaimStatus !== 'dominant') return null;\n  if (dominantClaimBasis === 'independence_adjusted_support') {\n    const topAdjusted = Array.isArray(claimIndependenceAdjustedSupport) ? claimIndependenceAdjustedSupport[0] : null;\n    if (!topAdjusted) return null;\n    const matchingDeduped = Array.isArray(claimSupportCountsDeduped) ? claimSupportCountsDeduped.find((entry) => entry.value === topAdjusted.value) : null;\n    const matchingWeighted = Array.isArray(claimWeightedSupport) ? claimWeightedSupport.find((entry) => entry.value === topAdjusted.value) : null;\n    const matchingIndependent = Array.isArray(claimIndependentSupport) ? claimIndependentSupport.find((entry) => entry.value === topAdjusted.value) : null;\n    return {\n      value: topAdjusted.value,\n      count: Number(matchingDeduped?.count ?? 0),\n      weighted_support: Number(matchingWeighted?.weighted_support ?? 0),\n      independent_support: Number(matchingIndependent?.count ?? 0),\n      adjusted_support: Number(topAdjusted.adjusted_support ?? 0),\n      basis: 'independence_adjusted_support',\n    };\n  }\n  if (dominantClaimBasis === 'weighted_support') {\n    const topWeighted = Array.isArray(claimWeightedSupport) ? claimWeightedSupport[0] : null;\n    if (!topWeighted) return null;\n    const matchingDeduped = Array.isArray(claimSupportCountsDeduped) ? claimSupportCountsDeduped.find((entry) => entry.value === topWeighted.value) : null;\n    const matchingIndependent = Array.isArray(claimIndependentSupport) ? claimIndependentSupport.find((entry) => entry.value === topWeighted.value) : null;\n    const matchingAdjusted = Array.isArray(claimIndependenceAdjustedSupport) ? claimIndependenceAdjustedSupport.find((entry) => entry.value === topWeighted.value) : null;\n    return {\n      value: topWeighted.value,\n      count: Number(matchingDeduped?.count ?? 0),\n      weighted_support: Number(topWeighted.weighted_support ?? 0),\n      independent_support: Number(matchingIndependent?.count ?? 0),\n      adjusted_support: Number(matchingAdjusted?.adjusted_support ?? 0),\n      basis: 'weighted_support',\n    };\n  }\n  if (dominantClaimBasis === 'deduped_support') {\n    const topDeduped = Array.isArray(claimSupportCountsDeduped) ? claimSupportCountsDeduped[0] : null;\n    if (!topDeduped) return null;\n    const matchingWeighted = Array.isArray(claimWeightedSupport) ? claimWeightedSupport.find((entry) => entry.value === topDeduped.value) : null;\n    const matchingIndependent = Array.isArray(claimIndependentSupport) ? claimIndependentSupport.find((entry) => entry.value === topDeduped.value) : null;\n    const matchingAdjusted = Array.isArray(claimIndependenceAdjustedSupport) ? claimIndependenceAdjustedSupport.find((entry) => entry.value === topDeduped.value) : null;\n    return {\n      value: topDeduped.value,\n      count: Number(topDeduped.count ?? 0),\n      weighted_support: Number(matchingWeighted?.weighted_support ?? 0),\n      independent_support: Number(matchingIndependent?.count ?? 0),\n      adjusted_support: Number(matchingAdjusted?.adjusted_support ?? 0),\n      basis: 'deduped_support',\n    };\n  }\n  return null;\n};\nconst pickMostRecentClaim = (claims) => {\n  if (!Array.isArray(claims) || claims.length === 0) return null;\n  const withTimestamps = claims.filter((claim) => typeof claim?.latest_created_at === 'string' && claim.latest_created_at.trim() !== '');\n  if (withTimestamps.length === 0) return null;\n  const sorted = [...withTimestamps].sort((left, right) => claimLatestTimestampValue(right) - claimLatestTimestampValue(left));\n  const latestValue = claimLatestTimestampValue(sorted[0]);\n  const secondValue = sorted.length > 1 ? claimLatestTimestampValue(sorted[1]) : Number.NEGATIVE_INFINITY;\n  const leaders = sorted.filter((claim) => claimLatestTimestampValue(claim) === latestValue);\n  if (leaders.length !== 1) return null;\n  if (Number.isFinite(secondValue) && latestValue - secondValue < 1) return null;\n  return { value: leaders[0].value, created_at: leaders[0].latest_created_at };\n};\nconst metricByValue = (entries, value, fieldName = 'count') => {\n  const match = Array.isArray(entries) ? entries.find((entry) => entry?.value === value) : null;\n  return match ? Number(match[fieldName] ?? 0) : 0;\n};\nconst buildClaimConfidence = ({ claimIndependenceAdjustedSupport, claimWeightedSupport, claimSupportCountsDeduped, dominantClaimStatus, dominantClaimBasis }) => {\n  if (dominantClaimStatus !== 'dominant') return 'low';\n  if (dominantClaimBasis === 'independence_adjusted_support') {\n    const topAdjusted = Number(claimIndependenceAdjustedSupport?.[0]?.adjusted_support ?? 0);\n    const secondAdjusted = Number(claimIndependenceAdjustedSupport?.[1]?.adjusted_support ?? 0);\n    const gap = topAdjusted - secondAdjusted;\n    if (gap >= 1.5) return 'high';\n    if (gap >= 0.4) return 'medium';\n    return 'low';\n  }\n  if (dominantClaimBasis === 'weighted_support') {\n    const topWeighted = Number(claimWeightedSupport?.[0]?.weighted_support ?? 0);\n    const secondWeighted = Number(claimWeightedSupport?.[1]?.weighted_support ?? 0);\n    const gap = topWeighted - secondWeighted;\n    if (gap >= 2) return 'high';\n    if (gap >= 0.5) return 'medium';\n    return 'low';\n  }\n  if (dominantClaimBasis === 'deduped_support') {\n    const topCount = Number(claimSupportCountsDeduped?.[0]?.count ?? 0);\n    const secondCount = Number(claimSupportCountsDeduped?.[1]?.count ?? 0);\n    const gap = topCount - secondCount;\n    if (topCount >= 3 && gap >= 2) return 'high';\n    if (topCount >= 2 && gap >= 1) return 'medium';\n  }\n  return 'low';\n};\nconst buildConflictSummaryHint = ({ claimSupportCountsRaw, claimSupportCountsDeduped, claimWeightedSupport, claimIndependentSupport, claimIndependenceAdjustedSupport, dominantClaimStatus, dominantClaimBasis, mostSupportedClaim, mostRecentClaim }) => {\n  if (!Array.isArray(claimSupportCountsDeduped) || claimSupportCountsDeduped.length < 2) return 'Conflict remains unresolved: evidence is limited and split.';\n  const topAdjusted = claimIndependenceAdjustedSupport?.[0] ?? null;\n  const secondAdjusted = claimIndependenceAdjustedSupport?.[1] ?? null;\n  const topWeighted = claimWeightedSupport?.[0] ?? null;\n  const secondWeighted = claimWeightedSupport?.[1] ?? null;\n  const topDeduped = claimSupportCountsDeduped[0] ?? null;\n  const secondDeduped = claimSupportCountsDeduped[1] ?? null;\n  const appendRecency = (summary) => {\n    if (mostRecentClaim && typeof mostRecentClaim.value === 'string' && mostSupportedClaim && mostRecentClaim.value !== mostSupportedClaim.value) return summary + ' But ' + mostRecentClaim.value + ' is more recent.';\n    return summary;\n  };\n  if (dominantClaimStatus === 'tie') {\n    if (topAdjusted && secondAdjusted && Math.abs(Number(topAdjusted.adjusted_support ?? 0) - Number(secondAdjusted.adjusted_support ?? 0)) <= 0.001 && topWeighted && secondWeighted && Math.abs(Number(topWeighted.weighted_support ?? 0) - Number(secondWeighted.weighted_support ?? 0)) <= 0.001) {\n      if (Number(topDeduped?.count ?? 0) === Number(secondDeduped?.count ?? 0)) return 'No dominant claim: independence-adjusted, weighted, and deduplicated support are tied.';\n      return 'No dominant claim: independence-adjusted and weighted support are tied.';\n    }\n    return 'No dominant claim: evidence remains effectively tied.';\n  }\n  if (dominantClaimStatus !== 'dominant' || !mostSupportedClaim) return 'Conflict remains unresolved: evidence is limited and split.';\n  const challengerValue = topAdjusted?.value && topAdjusted.value !== mostSupportedClaim.value ? topAdjusted.value : (secondAdjusted?.value ?? secondWeighted?.value ?? secondDeduped?.value ?? null);\n  const winnerRaw = metricByValue(claimSupportCountsRaw, mostSupportedClaim.value, 'count');\n  const challengerRaw = metricByValue(claimSupportCountsRaw, challengerValue, 'count');\n  const winnerWeighted = metricByValue(claimWeightedSupport, mostSupportedClaim.value, 'weighted_support');\n  const challengerWeighted = metricByValue(claimWeightedSupport, challengerValue, 'weighted_support');\n  const winnerIndependent = metricByValue(claimIndependentSupport, mostSupportedClaim.value, 'count');\n  const challengerIndependent = metricByValue(claimIndependentSupport, challengerValue, 'count');\n  const winnerAdjusted = metricByValue(claimIndependenceAdjustedSupport, mostSupportedClaim.value, 'adjusted_support');\n  const challengerAdjusted = metricByValue(claimIndependenceAdjustedSupport, challengerValue, 'adjusted_support');\n  let summary = 'Conflict remains unresolved: evidence is limited and split.';\n  if (dominantClaimBasis === 'independence_adjusted_support') {\n    if (winnerIndependent > challengerIndependent && Math.abs(winnerWeighted - challengerWeighted) <= 0.001) summary = mostSupportedClaim.value + ' has more independent support despite equal weighted support.';\n    else if (winnerRaw <= challengerRaw && winnerAdjusted > challengerAdjusted) summary = (challengerValue || 'the competing claim') + ' has more raw notes, but several are correlated, so ' + mostSupportedClaim.value + ' still has stronger independence-adjusted support.';\n    else summary = mostSupportedClaim.value + ' has stronger independence-adjusted support (' + winnerAdjusted.toFixed(1) + ' vs ' + challengerAdjusted.toFixed(1) + ').';\n  } else if (dominantClaimBasis === 'weighted_support') {\n    if (Number(topDeduped?.count ?? 0) === Number(secondDeduped?.count ?? 0)) summary = mostSupportedClaim.value + ' has stronger weighted support despite equal deduplicated support.';\n    else summary = mostSupportedClaim.value + ' has stronger weighted support (' + winnerWeighted.toFixed(1) + ' vs ' + challengerWeighted.toFixed(1) + ').';\n  } else if (dominantClaimBasis === 'deduped_support') {\n    summary = mostSupportedClaim.value + ' has stronger deduplicated support (' + Number(metricByValue(claimSupportCountsDeduped, mostSupportedClaim.value, 'count')) + ' vs ' + Number(metricByValue(claimSupportCountsDeduped, challengerValue, 'count')) + ').';\n  }\n  return appendRecency(summary);\n};\nconst conflicts = Array.from(conflictBuckets.values())\n  .map((bucket) => {\n    const claims = Array.from(bucket.values.values()).map((claim) => ({ ...claim, evidence_clusters: buildEvidenceClusters(claim) })).sort((left, right) => {\n      const leftAdjusted = supportIndependenceAdjusted(left);\n      const rightAdjusted = supportIndependenceAdjusted(right);\n      if (rightAdjusted !== leftAdjusted) return rightAdjusted - leftAdjusted;\n      const leftWeighted = supportWeighted(left);\n      const rightWeighted = supportWeighted(right);\n      if (rightWeighted !== leftWeighted) return rightWeighted - leftWeighted;\n      const leftSupport = supportCountDeduped(left);\n      const rightSupport = supportCountDeduped(right);\n      if (rightSupport !== leftSupport) return rightSupport - leftSupport;\n      const leftLatest = claimLatestTimestampValue(left);\n      const rightLatest = claimLatestTimestampValue(right);\n      if (rightLatest !== leftLatest) return rightLatest - leftLatest;\n      return String(left.value || '').localeCompare(String(right.value || ''));\n    });\n    const claimSupportCountsRaw = buildClaimSupportCountsRaw(claims);\n    const claimSupportCountsDeduped = buildClaimSupportCountsDeduped(claims);\n    const claimWeightedSupport = buildClaimWeightedSupport(claims);\n    const claimIndependentSupport = buildClaimIndependentSupport(claims);\n    const claimIndependenceAdjustedSupport = buildClaimIndependenceAdjustedSupport(claims);\n    const { dominantClaimStatus, dominantClaimBasis } = detectDominantClaimResolution({ claimIndependenceAdjustedSupport, claimWeightedSupport, claimSupportCountsDeduped });\n    const mostSupportedClaim = pickMostSupportedClaim(claimSupportCountsDeduped, claimWeightedSupport, claimIndependentSupport, claimIndependenceAdjustedSupport, dominantClaimStatus, dominantClaimBasis);\n    const mostRecentClaim = pickMostRecentClaim(claims);\n    const claimConfidence = buildClaimConfidence({ claimIndependenceAdjustedSupport, claimWeightedSupport, claimSupportCountsDeduped, dominantClaimStatus, dominantClaimBasis });\n    return {\n      topic: bucket.subject,\n      relation: bucket.relation,\n      property: bucket.relation,\n      severity: classifyConflictSeverity(claims),\n      claims,\n      claim_support_counts: claimSupportCountsDeduped,\n      claim_support_counts_raw: claimSupportCountsRaw,\n      claim_support_counts_deduped: claimSupportCountsDeduped,\n      claim_weighted_support: claimWeightedSupport,\n      claim_independent_support: claimIndependentSupport,\n      claim_independence_adjusted_support: claimIndependenceAdjustedSupport,\n      dominant_claim_status: dominantClaimStatus,\n      dominant_claim_basis: dominantClaimBasis,\n      claim_confidence: claimConfidence,\n      most_supported_claim: mostSupportedClaim,\n      most_recent_claim: mostRecentClaim,\n      evidence_clusters: claims.flatMap((claim) => claim.evidence_clusters.map((cluster) => ({\n        value: claim.value,\n        cluster_id: cluster.cluster_id,\n        relation: cluster.relation,\n        member_source_ids: cluster.member_source_ids,\n        member_source_titles: cluster.member_source_titles,\n        total_weight: cluster.total_weight,\n      }))),\n      conflict_summary_hint: buildConflictSummaryHint({\n        claimSupportCountsRaw,\n        claimSupportCountsDeduped,\n        claimWeightedSupport,\n        claimIndependentSupport,\n        claimIndependenceAdjustedSupport,\n        dominantClaimStatus,\n        dominantClaimBasis,\n        mostSupportedClaim,\n        mostRecentClaim,\n      }),\n    };\n  })\n  .filter((bucket) => bucket.claims.length > 1)\n  .filter((bucket) => !shouldSuppressConflictBucket(bucket))\n  .sort((left, right) => right.claims.reduce((total, claim) => total + supportIndependenceAdjusted(claim), 0) - left.claims.reduce((total, claim) => total + supportIndependenceAdjusted(claim), 0));\nconst conflictFlag = conflicts.length > 0;\nconst conflictSeverity = conflictFlag && conflicts.some((bucket) => bucket.severity === 'strong_conflict') ? 'strong_conflict' : (conflictFlag ? 'possible_conflict' : null);\nconst primaryConflict = conflicts[0] ?? null;\nconst claimSupportCounts = Array.isArray(primaryConflict?.claim_support_counts) ? primaryConflict.claim_support_counts : [];\nconst claimSupportCountsRaw = Array.isArray(primaryConflict?.claim_support_counts_raw) ? primaryConflict.claim_support_counts_raw : [];\nconst claimSupportCountsDeduped = Array.isArray(primaryConflict?.claim_support_counts_deduped) ? primaryConflict.claim_support_counts_deduped : claimSupportCounts;\nconst claimWeightedSupport = Array.isArray(primaryConflict?.claim_weighted_support) ? primaryConflict.claim_weighted_support : [];\nconst claimIndependentSupport = Array.isArray(primaryConflict?.claim_independent_support) ? primaryConflict.claim_independent_support : [];\nconst claimIndependenceAdjustedSupport = Array.isArray(primaryConflict?.claim_independence_adjusted_support) ? primaryConflict.claim_independence_adjusted_support : [];\nconst evidenceClusters = Array.isArray(primaryConflict?.evidence_clusters) ? primaryConflict.evidence_clusters : [];\nconst dominantClaimStatus = primaryConflict?.dominant_claim_status ?? null;\nconst dominantClaimBasis = primaryConflict?.dominant_claim_basis ?? null;\nconst claimConfidence = primaryConflict?.claim_confidence ?? null;\nconst conflictSummaryHint = primaryConflict?.conflict_summary_hint ?? null;\nconst mostSupportedClaim = primaryConflict?.most_supported_claim ?? null;\nconst mostRecentClaim = primaryConflict?.most_recent_claim ?? null;\nconst conflictingMemoryIds = new Set(conflicts.flatMap((bucket) => bucket.claims.flatMap((claim) => claim.source_ids)));\nconst sourceIndependencePriority = { duplicate_like: 3, related: 2, unclear: 1, independent: 0 };\nconst sourceIndependenceById = new Map();\nfor (const bucket of conflicts) {\n  for (const claim of bucket.claims) {\n    for (const cluster of claimEvidenceClusters(claim)) {\n      for (const member of cluster.members) {\n        if (member?.source_id === null || member?.source_id === undefined) continue;\n        const classification = typeof member?.source_independence === 'string' ? member.source_independence : 'unclear';\n        const existing = sourceIndependenceById.get(member.source_id);\n        if (!existing || (sourceIndependencePriority[classification] ?? 0) > (sourceIndependencePriority[existing] ?? 0)) sourceIndependenceById.set(member.source_id, classification);\n      }\n    }\n  }\n}\nconst defaultSourceIndependence = (memory) => {\n  const metadata = memory?.metadata_json && typeof memory?.metadata_json === 'object' ? memory.metadata_json : {};\n  const duplicateCandidate = metadata.duplicate_candidate === true;\n  if (duplicateCandidate || (supportIdentityCounts.get(normalizeSupportIdentity(memory)) ?? 0) > 1) return 'duplicate_like';\n  if (memory?.generic_runtime_noise === true || isGenericSourceTitle(memory?.title ?? metadata.filename ?? '')) return 'related';\n  return 'independent';\n};\nconst sourceIndependenceForMemory = (memory) => sourceIndependenceById.get(memory?.id) ?? defaultSourceIndependence(memory);\nconst evidenceScoreTop = directStrongMemories[0]?.retrieval_score ?? 0;\nconst clusteredDirectMemories = directStrongMemories\n  .filter((memory) => (evidenceScoreTop - (memory.retrieval_score ?? 0)) <= 0.035 || memory.strong_token_hits > 0 || memory.anchor_matched === true)\n  .slice(0, Math.min(Math.max(topK, 2), 4));\nconst answerEligibilitySimilarityThreshold = 0.65;\nconst answerEligibilityLexicalThreshold = 2;\nconst selectedMemoriesForAnswerSeed = (conflictFlag\n  ? strongMemories.filter((memory) => conflictingMemoryIds.has(memory.id))\n  : clusteredDirectMemories).filter(matchesRequestedProjectScope);\nconst answerEligibilityTopSimilarity = selectedMemoriesForAnswerSeed[0]?.similarity ?? 0;\nconst answerEligibilityMaxLexicalOverlap = selectedMemoriesForAnswerSeed.reduce((max, memory) => Math.max(max, Number(memory?.lexical_overlap ?? 0)), 0);\nconst answerEligibilityHasFocusedSignal = selectedMemoriesForAnswerSeed.some((memory) =>\n  memory?.intent_domain_match === true\n  || memory?.anchor_matched === true\n  || Number(memory?.strong_token_hits ?? 0) > 0\n  || Number(memory?.structured_token_hits ?? 0) > 0\n  || Number(memory?.entity_focus_hits ?? 0) > 0\n  || Number(memory?.property_focus_hits ?? 0) > 0\n  || Number(memory?.title_lexical_hits ?? 0) > 0\n);\nconst answerEligibilityAllWeak = selectedMemoriesForAnswerSeed.every((memory) => (memory?.similarity ?? 0) < answerEligibilitySimilarityThreshold);\nconst answerEligibilityBlocked = !conflictFlag\n  && !uncertaintyQueryIntent\n  && !contradictionQueryIntent\n  && selectedMemoriesForAnswerSeed.length <= 1\n  && answerEligibilityTopSimilarity < answerEligibilitySimilarityThreshold\n  && answerEligibilityMaxLexicalOverlap <= answerEligibilityLexicalThreshold\n  && !answerEligibilityHasFocusedSignal\n  && answerEligibilityAllWeak;\nconst answerEligibilityReason = answerEligibilityBlocked ? 'low_signal_irrelevant_match' : null;\nconst selectedMemoriesForAnswer = answerEligibilityBlocked ? [] : selectedMemoriesForAnswerSeed;\nconst buildSource = (memory) => {\n  const metadata = memory.metadata_json && typeof memory.metadata_json === 'object' ? memory.metadata_json : {};\n  const content = typeof memory.content === 'string' ? memory.content.trim() : '';\n  const reviewStatus = normalizeReviewStatus(memory.review_status ?? metadata.review_status);\n  const sourceType = memory.source ?? metadata.source_type ?? 'unknown';\n  const createdAt = parseTimestamp(memory.created_at) ?? parseTimestamp(metadata.ingested_at);\n  const scopeMatch = base.project_slug ? memory.project_slug === base.project_slug : !memory.project_slug;\n  const duplicateCandidate = metadata.duplicate_candidate === true;\n  const length = content.length;\n  const sourceQuality = classifySourceQuality(memory);\n  const sourceQualityWeightValue = sourceQualityWeight(sourceQuality);\n  const sourceIndependence = sourceIndependenceForMemory(memory);\n  const flags = [];\n  if (reviewStatus === 'unreviewed') flags.push('review_unreviewed');\n  else if (reviewStatus !== 'reviewed') flags.push('review_' + reviewStatus);\n  if (!scopeMatch && base.project_slug) flags.push('fallback_scope');\n  if (duplicateCandidate) flags.push('duplicate_candidate');\n  if (length < 120) flags.push('short_chunk');\n  const recency = recencyBand(createdAt);\n  if (recency === 'older_than_30d') flags.push('older_source');\n  if (rankingMode === 'semantic' && (memory.similarity ?? 0) < similarityThreshold + 0.04 && (memory.lexical_overlap ?? 0) < 3 && (memory.strong_token_hits ?? 0) === 0) flags.push('borderline_similarity');\n  if (memory.strong_token_hits > 0 || memory.structured_token_hits > 0) flags.push('structured_signal');\n  if (sourceQuality === 'high') flags.push('quality_high');\n  if (sourceQuality === 'low') flags.push('quality_low');\n  flags.push('independence_' + sourceIndependence);\n  let score = 0;\n  if (reviewStatus === 'reviewed') score += 3;\n  else if (reviewStatus === 'unreviewed') score += 1;\n  else score -= 4;\n  if (scopeMatch) score += 2;\n  if (length >= 120) score += 1;\n  if (rankingMode === 'semantic') {\n    if ((memory.similarity ?? 0) >= similarityThreshold + 0.08) score += 2;\n    else if ((memory.similarity ?? 0) >= similarityThreshold + 0.04) score += 1;\n  } else if (memory.anchor_matched || memory.strong_token_hits > 0) score += 2;\n  if (memory.short_note_boost >= 0.025) score += 1;\n  if (recency === 'recent_24h' || recency === 'recent_7d') score += 1;\n  if (recency === 'older_than_30d') score -= 1;\n  if (duplicateCandidate) score -= 2;\n  if (sourceQuality === 'high') score += 1;\n  if (sourceQuality === 'low') score -= 1;\n  if (reviewStatus === 'suppressed') score = -99;\n  const trustBand = score >= 6 ? 'high' : (score >= 3 ? 'medium' : 'low');\n  const uncertaintyReasons = [];\n  if (reviewStatus !== 'reviewed') uncertaintyReasons.push('review_not_confirmed');\n  if (!scopeMatch && base.project_slug) uncertaintyReasons.push('scope_fallback');\n  if (duplicateCandidate) uncertaintyReasons.push('duplicate_candidate');\n  if (recency === 'older_than_30d') uncertaintyReasons.push('stale_source');\n  if (rankingMode === 'semantic' && (memory.similarity ?? 0) < similarityThreshold + 0.04 && (memory.lexical_overlap ?? 0) < 3 && (memory.strong_token_hits ?? 0) === 0) uncertaintyReasons.push('borderline_similarity');\n  if (conflictFlag && conflictingMemoryIds.has(memory.id)) uncertaintyReasons.push('conflicting_claim');\n  if (sourceQuality === 'low') uncertaintyReasons.push('low_source_quality');\n  if (sourceIndependence === 'related' || sourceIndependence === 'duplicate_like') uncertaintyReasons.push('correlated_support');\n  const chunkIndex = Number.isInteger(metadata.chunk_index) ? metadata.chunk_index : null;\n  const totalChunks = Number.isInteger(metadata.total_chunks) ? metadata.total_chunks : null;\n  return {\n    id: memory.id,\n    memory_id: memory.id,\n    title: memory.title,\n    source_label: memory.title ?? null,\n    filename: typeof metadata.filename === 'string' ? metadata.filename : null,\n    filepath: typeof metadata.filepath === 'string' ? metadata.filepath : null,\n    chunk_index: chunkIndex,\n    total_chunks: totalChunks,\n    project_slug: memory.project_slug ?? null,\n    source_type: sourceType,\n    category: memory.category ?? null,\n    created_at: createdAt,\n    review_status: reviewStatus,\n    project_match: scopeMatch,\n    content_length: length,\n    chunk_size_band: chunkSizeBand(length),\n    duplicate_candidate: duplicateCandidate,\n    source_quality: sourceQuality,\n    source_quality_weight: sourceQualityWeightValue,\n    source_independence: sourceIndependence,\n    trust_band: trustBand,\n    confidence_band: trustBand,\n    uncertainty_indicator: uncertaintyReasons.length > 0,\n    uncertainty_reasons: uncertaintyReasons,\n    quality_flags: flags,\n    similarity: memory.similarity,\n    retrieval_score: memory.retrieval_score,\n    lexical_overlap: memory.lexical_overlap,\n    strong_token_hits: memory.strong_token_hits,\n    structured_token_hits: memory.structured_token_hits,\n    snippet: typeof memory.content === 'string' ? memory.content.slice(0, 160) : '',\n  };\n};\nconst selectedSources = selectedMemoriesForAnswer.map(buildSource);\nconst sourceQualityBreakdown = buildSourceQualityBreakdown(selectedSources.map((source) => ({ quality: source.source_quality, weight: source.source_quality_weight })));\nconst sourceIndependenceBreakdown = buildSourceIndependenceBreakdown(selectedSources);\nconst strongSingleSupport = !conflictFlag\n  && selectedMemoriesForAnswer.length === 1\n  && selectedSources[0]?.review_status === 'reviewed'\n  && (((selectedMemoriesForAnswer[0]?.similarity ?? 0) >= similarityThreshold)\n    || ((selectedMemoriesForAnswer[0]?.lexical_overlap ?? 0) >= 3)\n    || ((selectedMemoriesForAnswer[0]?.strong_token_hits ?? 0) > 0)\n    || selectedMemoriesForAnswer[0]?.anchor_matched === true);\nconst strongMultiSupport = !conflictFlag\n  && selectedMemoriesForAnswer.length >= 2\n  && selectedSources.filter((source) => source.review_status === 'reviewed').length >= 2;\nconst hasDirectSupport = strongSingleSupport || strongMultiSupport;\nconst context = selectedMemoriesForAnswer.map((memory, index) => 'Memory ' + (index + 1) + ': ' + memory.title + '\\n' + memory.content).join('\\n\\n');\nconst trustSummary = {\n  overall_band: 'low',\n  evidence_strength: selectedMemoriesForAnswer.length >= 2 ? 'moderate' : (selectedMemoriesForAnswer.length === 1 ? 'weak' : 'none'),\n  reviewed_source_count: selectedSources.filter((source) => source.review_status === 'reviewed').length,\n  unreviewed_source_count: selectedSources.filter((source) => source.review_status === 'unreviewed').length,\n  scope_match_count: selectedSources.filter((source) => source.project_match).length,\n  high_trust_source_count: selectedSources.filter((source) => source.trust_band === 'high').length,\n  medium_trust_source_count: selectedSources.filter((source) => source.trust_band === 'medium').length,\n  low_trust_source_count: selectedSources.filter((source) => source.trust_band === 'low').length,\n  uncertainty_indicator: false,\n  uncertainty_reasons: [],\n  entity_focus: entityFocus ?? null,\n  filtered_candidate_count: filteredCandidateCount,\n  source_quality_breakdown: sourceQualityBreakdown,\n  source_independence_breakdown: sourceIndependenceBreakdown,\n  conflict_severity: conflictSeverity,\n  claim_support_counts: claimSupportCounts,\n  claim_support_counts_raw: claimSupportCountsRaw,\n  claim_support_counts_deduped: claimSupportCountsDeduped,\n  claim_weighted_support: claimWeightedSupport,\n  claim_independent_support: claimIndependentSupport,\n  claim_independence_adjusted_support: claimIndependenceAdjustedSupport,\n  evidence_clusters: evidenceClusters,\n  dominant_claim_status: dominantClaimStatus,\n  dominant_claim_basis: dominantClaimBasis,\n  claim_confidence: claimConfidence,\n  conflict_summary_hint: conflictSummaryHint,\n  source_quality_breakdown: sourceQualityBreakdown,\n  most_supported_claim: mostSupportedClaim,\n  most_recent_claim: mostRecentClaim,\n};\nif (conflictFlag) {\n  trustSummary.overall_band = 'low';\n  trustSummary.evidence_strength = 'conflicting';\n} else if (trustSummary.high_trust_source_count > 0 && trustSummary.low_trust_source_count === 0 && selectedMemoriesForAnswer.length >= 2) {\n  trustSummary.overall_band = 'high';\n  trustSummary.evidence_strength = 'strong';\n} else if (selectedMemoriesForAnswer.length > 0 && (trustSummary.high_trust_source_count > 0 || trustSummary.medium_trust_source_count > 0)) {\n  trustSummary.overall_band = 'medium';\n}\nif (trustSummary.high_trust_source_count === 0) trustSummary.uncertainty_reasons.push('no_high_trust_source');\nif (trustSummary.reviewed_source_count === 0 && selectedMemoriesForAnswer.length > 0) trustSummary.uncertainty_reasons.push('sources_unreviewed');\nif (selectedMemoriesForAnswer.length > 0 && selectedMemoriesForAnswer.length < 2 && !conflictFlag && !hasDirectSupport) trustSummary.uncertainty_reasons.push('limited_source_count');\nif (rankingMode === 'semantic' && (selectedMemoriesForAnswer[0]?.similarity ?? 0) < similarityThreshold + 0.05 && selectedMemoriesForAnswer.length > 0 && !hasDirectSupport) trustSummary.uncertainty_reasons.push('top_similarity_borderline');\nif (conflictFlag) trustSummary.uncertainty_reasons.push('conflicting_sources');\ntrustSummary.uncertainty_indicator = trustSummary.uncertainty_reasons.length > 0;\nconst groundingReasons = [];\nif (selectedMemoriesForAnswer.length === 0) groundingReasons.push('no_retrieved_memory');\nif (selectedSources.length > 0 && selectedSources.length < 2 && !conflictFlag && !hasDirectSupport) groundingReasons.push('limited_supporting_sources');\nif (selectedSources.length > 0 && trustSummary.reviewed_source_count === 0) groundingReasons.push('no_reviewed_sources');\nif (selectedSources.some((source) => source.trust_band === 'low')) groundingReasons.push('low_trust_source_present');\nif (selectedSources.some((source) => source.source_independence === 'related' || source.source_independence === 'duplicate_like')) groundingReasons.push('correlated_supporting_sources');\nif (rankingMode === 'semantic' && (selectedMemoriesForAnswer[0]?.similarity ?? 0) < similarityThreshold + 0.05 && selectedMemoriesForAnswer.length > 0 && !hasDirectSupport) groundingReasons.push('top_similarity_borderline');\nif (selectedSources.some((source) => source.uncertainty_indicator === true)) groundingReasons.push('source_uncertainty_flagged');\nif (!conflictFlag && uncertaintyQueryIntent && selectedSources.length > 0) groundingReasons.push('uncertainty_synthesis');\nif (conflictFlag) groundingReasons.push('conflicting_claims');\nconst dedupedGroundingReasons = Array.from(new Set(groundingReasons));\nconst groundingReasonMessages = {\n  no_retrieved_memory: 'no strong supporting memory was retrieved',\n  limited_supporting_sources: 'only one supporting source was retrieved',\n  no_reviewed_sources: 'none of the visible sources are reviewed',\n  low_trust_source_present: 'at least one visible source is low-trust',\n  correlated_supporting_sources: 'some visible sources appear correlated rather than fully independent',\n  top_similarity_borderline: 'the top semantic match is close to the similarity floor',\n  source_uncertainty_flagged: 'one or more visible sources were already flagged as uncertain',\n  uncertainty_synthesis: 'the retrieved sources describe incomplete or partially documented history rather than a full account',\n  conflicting_claims: 'retrieved notes disagree on the same claim',\n};\nconst answerMode = selectedMemoriesForAnswer.length === 0\n  ? 'insufficient'\n  : (conflictFlag ? 'conflict' : 'direct');\nconst groundingStatus = selectedMemoriesForAnswer.length === 0 ? 'none' : (dedupedGroundingReasons.length > 0 ? 'weak' : 'grounded');\nconst buildGroundingNote = () => {\n  if (answerMode === 'conflict') return 'Grounding is weak: retrieved notes conflict on the same claim' + (conflictSeverity ? ' (' + conflictSeverity + ')' : '') + '.';\n  if (groundingStatus === 'grounded') return 'Grounding is backed by ' + selectedSources.length + ' visible supporting source' + (selectedSources.length === 1 ? '' : 's') + '.';\n  if (groundingStatus === 'none') return 'No strong supporting memory was retrieved.';\n  const reasonText = dedupedGroundingReasons.map((reason) => groundingReasonMessages[reason] ?? reason).join('; ');\n  return 'Grounding is weak: ' + (reasonText || 'support is limited') + '.';\n};\nconst grounding = {\n  status: groundingStatus,\n  weak_grounding: groundingStatus !== 'grounded',\n  note: buildGroundingNote(),\n  reasons: dedupedGroundingReasons,\n  supporting_source_count: selectedSources.length,\n  reviewed_source_count: trustSummary.reviewed_source_count,\n  strongest_similarity: selectedMemoriesForAnswer.length > 0 ? selectedMemoriesForAnswer[0].similarity : null,\n  similarity_threshold: similarityThreshold,\n  ranking_mode: rankingMode,\n  evidence_strength: trustSummary.evidence_strength,\n  overall_trust_band: trustSummary.overall_band,\n  primary_memory_ids: selectedMemoriesForAnswer.map((memory) => memory.id),\n  primary_chunk_indexes: selectedSources.map((source) => source.chunk_index).filter((value) => Number.isInteger(value)),\n};\nconst retrievedCandidates = (rankingMode === 'anchor' ? anchorSorted : semanticSorted).slice(0, Math.min(Math.max(topK + 3, 6), 10)).map((memory) => ({ ...summarizeMemory(memory), source_independence: sourceIndependenceForMemory(memory), selected_for_answer: selectedMemoriesForAnswer.some((candidate) => candidate.id === memory.id) }));\nconst conflictDetails = conflicts.slice(0, 3).map((bucket) => ({ topic: bucket.topic, relation: bucket.relation, property: bucket.property ?? bucket.relation, severity: bucket.severity ?? null, claim_support_counts: bucket.claim_support_counts ?? [], claim_support_counts_raw: bucket.claim_support_counts_raw ?? [], claim_support_counts_deduped: bucket.claim_support_counts_deduped ?? [], claim_weighted_support: bucket.claim_weighted_support ?? [], claim_independent_support: bucket.claim_independent_support ?? [], claim_independence_adjusted_support: bucket.claim_independence_adjusted_support ?? [], evidence_clusters: bucket.evidence_clusters ?? [], dominant_claim_status: bucket.dominant_claim_status ?? null, dominant_claim_basis: bucket.dominant_claim_basis ?? null, claim_confidence: bucket.claim_confidence ?? null, conflict_summary_hint: bucket.conflict_summary_hint ?? null, most_supported_claim: bucket.most_supported_claim ?? null, most_recent_claim: bucket.most_recent_claim ?? null, claims: bucket.claims.map((claim) => ({ value: claim.value, claim_text: claim.claim_texts[0] ?? null, source_ids: claim.source_ids, source_titles: claim.source_titles, support_count: Array.isArray(claim.source_ids) ? claim.source_ids.length : 0, support_count_raw: supportCountRaw(claim), support_count_deduped: supportCountDeduped(claim), weighted_support: Number(supportWeighted(claim).toFixed(2)), independent_support: supportIndependent(claim), independence_adjusted_support: Number(supportIndependenceAdjusted(claim).toFixed(2)), support_identities: Array.isArray(claim.support_identities) ? claim.support_identities : [], source_quality_breakdown: buildSourceQualityBreakdown(uniqueQualityEntries(claim)), source_quality_entries: uniqueQualityEntries(claim), source_independence_breakdown: buildSourceIndependenceBreakdown(claimEvidenceClusters(claim).flatMap((cluster) => cluster.members)), evidence_clusters: claimEvidenceClusters(claim).map((cluster) => ({ cluster_id: cluster.cluster_id, relation: cluster.relation, member_source_ids: cluster.member_source_ids, member_source_titles: cluster.member_source_titles, total_weight: cluster.total_weight })), latest_created_at: claim.latest_created_at ?? null })) }));\nconst sessionTurns = Array.isArray(base.session_turns) ? base.session_turns : [];\nconst sessionHistory = sessionTurns.map((turn) => (turn.role === 'assistant' ? 'Assistant' : 'User') + ': ' + turn.message_text).join('\\n');\nconsole.log(JSON.stringify({\n  event: 'assistant_retrieval_candidates',\n  correlation_id: base.trace?.correlation_id ?? null,\n  query: base.query,\n  project_slug: base.project_slug ?? null,\n  ranking_mode: rankingMode,\n  lexical_fallback_used: shouldUseLexicalFallback,\n  is_failure_intent: isFailureIntent,\n  entity_focus: entityFocus,\n  filtered_candidate_count: filteredCandidateCount,\n  top_k: topK,\n  candidate_counts: {\n    project: projectMemories.length,\n    general: generalMemories.length,\n    all: allMemories.length,\n    lexical_project: lexicalProjectMemories.length,\n    lexical_general: lexicalGeneralMemories.length,\n    lexical_all: lexicalAllMemories.length,\n    selected: selected.length,\n    usable: usableSelected.length,\n    strong: strongMemories.length,\n    answer_selected: selectedMemoriesForAnswer.length,\n    anchor_matched: anchorSorted.length,\n    filtered_out: filteredCandidateCount,\n  },\n  answer_eligibility_blocked: answerEligibilityBlocked,\n  answer_eligibility_reason: answerEligibilityReason,\n  answer_eligibility_top_similarity: Number(answerEligibilityTopSimilarity.toFixed(6)),\n  answer_eligibility_max_lexical_overlap: answerEligibilityMaxLexicalOverlap,\n  candidates: retrievedCandidates,\n}));\nconsole.log(JSON.stringify({\n  event: 'assistant_context_selected',\n  correlation_id: base.trace?.correlation_id ?? null,\n  query: base.query,\n  project_slug: base.project_slug ?? null,\n  ranking_mode: rankingMode,\n  lexical_fallback_used: shouldUseLexicalFallback,\n  entity_focus: entityFocus,\n  filtered_candidate_count: filteredCandidateCount,\n  answer_mode: answerMode,\n  conflict_flag: conflictFlag,\n  conflict_severity: conflictSeverity,\n  claim_support_counts: claimSupportCounts,\n  claim_support_counts_raw: claimSupportCountsRaw,\n  claim_support_counts_deduped: claimSupportCountsDeduped,\n  claim_weighted_support: claimWeightedSupport,\n  claim_independent_support: claimIndependentSupport,\n  claim_independence_adjusted_support: claimIndependenceAdjustedSupport,\n  dominant_claim_status: dominantClaimStatus,\n  dominant_claim_basis: dominantClaimBasis,\n  claim_confidence: claimConfidence,\n  conflict_summary_hint: conflictSummaryHint,\n  most_supported_claim: mostSupportedClaim,\n  most_recent_claim: mostRecentClaim,\n  source_independence_breakdown: sourceIndependenceBreakdown,\n  evidence_clusters: evidenceClusters,\n  answer_eligibility_blocked: answerEligibilityBlocked,\n  answer_eligibility_reason: answerEligibilityReason,\n  answer_eligibility_top_similarity: Number(answerEligibilityTopSimilarity.toFixed(6)),\n  answer_eligibility_max_lexical_overlap: answerEligibilityMaxLexicalOverlap,\n  selected_memory_ids: selectedMemoriesForAnswer.map((memory) => memory.id),\n  selected_sources: selectedMemoriesForAnswer.map((memory) => summarizeMemory(memory)),\n  context_length: context.length,\n}));\nconst promptParts = [\n  'You are CrispyBrain, a local project-aware memory assistant.',\n  'You MUST answer ONLY using the provided retrieved memory context.',\n  'DO NOT use training data, general knowledge, assumptions, speculation, rumors, or invented facts.',\n  'If a detail is not directly supported by the retrieved memory context, state that it cannot be verified from project memory.',\n  'If the memory is incomplete or weakly supported, say so plainly.',\n  'If multiple retrieved notes agree, synthesize only the shared facts. If notes conflict, do not guess.',\n  base.project_slug ? 'Requested project_slug: ' + base.project_slug : null,\n  sessionHistory ? 'Recent session turns:\\n' + sessionHistory : null,\n  context ? 'Retrieved memory context:\\n' + context : null,\n  'User question:\\n' + base.query,\n  'Respond in concise plain text.',\n].filter(Boolean);\nconst withStage = (trace, stage, status, extra = {}) => {\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (trace.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (trace.error_message ?? null);\n  return {\n    ...trace,\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...(Array.isArray(trace.stage_history) ? trace.stage_history : []),\n      { stage, status, timestamp, error_code: errorCode, error_message: errorMessage },\n    ],\n  };\n};\nreturn [{\n  json: {\n    ...base,\n    retrieval_strategy: base.project_slug ? 'project-first-fallback-general' : 'all-memories',\n    project_match_count: projectMemories.length,\n    general_match_count: generalMemories.length,\n    lexical_project_match_count: lexicalProjectMemories.length,\n    lexical_general_match_count: lexicalGeneralMemories.length,\n    lexical_all_match_count: lexicalAllMemories.length,\n    selected_memory_count: selected.length,\n    usable_memory_count: usableSelected.length,\n    suspect_memory_count: suspectMemoryCount,\n    strong_memory_count: selectedMemoriesForAnswer.length,\n    filtered_candidate_count: filteredCandidateCount,\n    strongest_similarity: selectedMemoriesForAnswer.length > 0 ? selectedMemoriesForAnswer[0].similarity : null,\n    similarity_threshold: similarityThreshold,\n    query_terms: queryTerms,\n    lexical_query_terms: lexicalQueryTerms,\n    strong_query_tokens: strongQueryTokens,\n    is_failure_intent: isFailureIntent,\n    lexical_fallback_used: shouldUseLexicalFallback,\n    empty_retrieval: selectedMemoriesForAnswer.length === 0,\n    sources: selectedSources,\n    selected_sources: selectedSources,\n    retrieved_candidates: retrievedCandidates,\n    answer_mode: answerMode,\n    conflict_flag: conflictFlag,\n    conflict_severity: conflictSeverity,\n    conflict_details: conflictDetails,\n    claim_support_counts: claimSupportCounts,\n    claim_support_counts_raw: claimSupportCountsRaw,\n    claim_support_counts_deduped: claimSupportCountsDeduped,\n    claim_weighted_support: claimWeightedSupport,\n    claim_independent_support: claimIndependentSupport,\n    claim_independence_adjusted_support: claimIndependenceAdjustedSupport,\n    dominant_claim_status: dominantClaimStatus,\n    dominant_claim_basis: dominantClaimBasis,\n    claim_confidence: claimConfidence,\n    conflict_summary_hint: conflictSummaryHint,\n    most_supported_claim: mostSupportedClaim,\n    most_recent_claim: mostRecentClaim,\n    source_quality_breakdown: sourceQualityBreakdown,\n    source_independence_breakdown: sourceIndependenceBreakdown,\n    evidence_clusters: evidenceClusters,\n    entity_focus: entityFocus,\n    trust: trustSummary,\n    grounding,\n    context,\n    context_preview: context.slice(0, 280),\n    session_turn_count_before: sessionTurns.length,\n    session_history: sessionHistory,\n    prompt: promptParts.join('\\n\\n'),\n    memory_ids: selectedMemoriesForAnswer.map((memory) => memory.id),\n    trace: withStage(base.trace, selectedMemoriesForAnswer.length > 0 ? 'retrieval_ready' : 'retrieval_empty', 'accepted', {\n      project_slug: base.project_slug ?? null,\n      ranking_mode: rankingMode,\n      lexical_fallback_used: shouldUseLexicalFallback,\n      is_failure_intent: isFailureIntent,\n      answer_mode: answerMode,\n      conflict_flag: conflictFlag,\n      conflict_severity: conflictSeverity,\n      entity_focus: entityFocus,\n      filtered_candidate_count: filteredCandidateCount,\n      grounding_status: grounding.status,\n      weak_grounding: grounding.weak_grounding,\n      candidate_counts: {\n        project: projectMemories.length,\n        general: generalMemories.length,\n        all: allMemories.length,\n        lexical_project: lexicalProjectMemories.length,\n        lexical_general: lexicalGeneralMemories.length,\n        lexical_all: lexicalAllMemories.length,\n        selected: selected.length,\n        usable: usableSelected.length,\n        strong: strongMemories.length,\n        answer_selected: selectedMemoriesForAnswer.length,\n        anchor_matched: anchorSorted.length,\n      },\n      answer_eligibility_blocked: answerEligibilityBlocked,\n      answer_eligibility_reason: answerEligibilityReason,\n      answer_eligibility_top_similarity: Number(answerEligibilityTopSimilarity.toFixed(6)),\n      answer_eligibility_max_lexical_overlap: answerEligibilityMaxLexicalOverlap,\n      selected_memory_ids: selectedMemoriesForAnswer.map((memory) => memory.id),\n      claim_weighted_support: claimWeightedSupport,\n      claim_independence_adjusted_support: claimIndependenceAdjustedSupport,\n      dominant_claim_basis: dominantClaimBasis,\n      source_quality_breakdown: sourceQualityBreakdown,\n      source_independence_breakdown: sourceIndependenceBreakdown,\n    }),\n  },\n}];"
      },
      "id": "code-assemble-retrieval-context",
      "name": "Assemble Retrieval Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2660,
        20
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "condition-has-strong-retrieval",
              "leftValue": "={{ $json.answer_mode !== 'insufficient' }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-has-strong-retrieval",
      "name": "Has Strong Retrieval?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2960,
        20
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://host.docker.internal:11434/api/generate",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'llama3', prompt: $json.prompt, stream: false, options: { temperature: 0, seed: 0 } }) }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true,
              "responseFormat": "json"
            }
          },
          "timeout": 120000
        }
      },
      "id": "http-generate-assistant-answer",
      "name": "Generate Assistant Answer",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        3260,
        -120
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 2000
    },
    {
      "parameters": {
        "jsCode": "const base = $('Assemble Retrieval Context').first().json;\nconst statusCode = $json.statusCode ?? 0;\nconst body = $json.body ?? {};\nconst answer = typeof body.response === 'string' ? body.response.trim() : '';\nconst firstFiniteNumber = (...values) => {\n  for (const value of values) {\n    if (typeof value === 'number' && Number.isFinite(value)) {\n      return value;\n    }\n    if (typeof value === 'string' && value.trim() !== '') {\n      const parsed = Number(value);\n      if (Number.isFinite(parsed)) {\n        return parsed;\n      }\n    }\n  }\n  return null;\n};\nconst buildUnavailableUsage = (reason) => ({\n  provider: 'ollama',\n  source: 'generation',\n  available: false,\n  input_tokens: null,\n  output_tokens: null,\n  total_tokens: null,\n  prompt_tokens: null,\n  completion_tokens: null,\n  prompt_eval_count: null,\n  eval_count: null,\n  reason,\n});\nconst normalizeUsage = (payload, reason) => {\n  const inputTokens = firstFiniteNumber(payload?.prompt_eval_count, payload?.prompt_tokens, payload?.input_tokens, payload?.inputTokens);\n  const outputTokens = firstFiniteNumber(payload?.eval_count, payload?.completion_tokens, payload?.output_tokens, payload?.outputTokens);\n  const totalTokens = firstFiniteNumber(payload?.total_tokens, payload?.totalTokens, typeof inputTokens === 'number' && typeof outputTokens === 'number' ? inputTokens + outputTokens : null);\n  const available = typeof inputTokens === 'number' || typeof outputTokens === 'number' || typeof totalTokens === 'number';\n  if (!available) {\n    return buildUnavailableUsage(reason);\n  }\n  return {\n    provider: 'ollama',\n    source: 'generation',\n    available: true,\n    input_tokens: typeof inputTokens === 'number' ? inputTokens : null,\n    output_tokens: typeof outputTokens === 'number' ? outputTokens : null,\n    total_tokens: typeof totalTokens === 'number' ? totalTokens : null,\n    prompt_tokens: typeof inputTokens === 'number' ? inputTokens : null,\n    completion_tokens: typeof outputTokens === 'number' ? outputTokens : null,\n    prompt_eval_count: firstFiniteNumber(payload?.prompt_eval_count),\n    eval_count: firstFiniteNumber(payload?.eval_count),\n    reason: null,\n  };\n};\nconst usageReason = statusCode < 200 || statusCode >= 300\n  ? 'ollama_generation_failed'\n  : (answer === '' ? 'empty_generation_response' : 'upstream_usage_missing');\nconst usage = normalizeUsage(body, usageReason);\nconst withStage = (trace, stage, status, extra = {}) => {\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (trace.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (trace.error_message ?? null);\n  return {\n    ...trace,\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...(Array.isArray(trace.stage_history) ? trace.stage_history : []),\n      { stage, status, timestamp, error_code: errorCode, error_message: errorMessage },\n    ],\n  };\n};\nconst conflictMode = base.answer_mode === 'conflict' && base.conflict_flag === true;\nconst formatConflictValue = (conflict, claim) => {\n  const rawValue = typeof claim?.value === 'string' ? claim.value.trim() : '';\n  const property = typeof conflict?.property === 'string' && conflict.property.trim() ? conflict.property.trim().toLowerCase() : (typeof conflict?.relation === 'string' ? conflict.relation.trim().toLowerCase() : '');\n  if (rawValue && property && rawValue.toLowerCase().startsWith(property + ' ')) {\n    return rawValue.slice(property.length + 1).trim();\n  }\n  return rawValue || (typeof claim?.claim_text === 'string' ? claim.claim_text.trim() : 'unknown claim');\n};\nconst formatHintDate = (value) => {\n  if (typeof value !== 'string' || value.trim() === '') return null;\n  const parsed = new Date(value);\n  if (Number.isNaN(parsed.getTime())) return null;\n  return parsed.toISOString().slice(0, 10);\n};\nconst formatSupport = (claim) => {\n  const raw = Number(claim?.support_count_raw ?? claim?.support_count ?? (Array.isArray(claim?.source_ids) ? claim.source_ids.length : 0));\n  const deduped = Number(claim?.support_count_deduped ?? raw);\n  const weighted = Number(claim?.weighted_support ?? 0);\n  const independent = Number(claim?.independent_support ?? 0);\n  const adjusted = Number(claim?.independence_adjusted_support ?? 0);\n  return 'Support: ' + raw + ' | Deduped: ' + deduped + ' | Weighted: ' + weighted.toFixed(1) + ' | Independent: ' + independent + ' | Adjusted: ' + adjusted.toFixed(1);\n};\nconst buildConflictAnswer = () => {\n  const conflicts = Array.isArray(base.conflict_details) ? base.conflict_details : [];\n  if (conflicts.length === 0) return 'I found conflicting stored memory for this question, so I cannot collapse it into one answer.';\n  const lines = ['I found conflicting stored memory for this question, so I cannot collapse it into one answer.'];\n  for (const conflict of conflicts) {\n    const subject = typeof conflict.topic === 'string' && conflict.topic.trim() ? conflict.topic.trim() : 'unknown subject';\n    const property = typeof conflict.property === 'string' && conflict.property.trim() ? conflict.property.trim() : (typeof conflict.relation === 'string' && conflict.relation.trim() ? conflict.relation.trim() : 'claim');\n    const severity = typeof conflict.severity === 'string' && conflict.severity.trim()\n      ? conflict.severity.trim()\n      : (typeof base.conflict_severity === 'string' && base.conflict_severity.trim() ? base.conflict_severity.trim() : 'possible_conflict');\n    lines.push('');\n    lines.push('Subject: ' + subject);\n    lines.push('Property: ' + property);\n    lines.push('Severity: ' + severity);\n    lines.push('Dominant: ' + String(conflict?.dominant_claim_status ?? 'unclear'));\n    lines.push('Basis: ' + String(conflict?.dominant_claim_basis ?? 'unclear'));\n    lines.push('Confidence: ' + String(conflict?.claim_confidence ?? 'low'));\n    let index = 1;\n    for (const claim of Array.isArray(conflict.claims) ? conflict.claims : []) {\n      const sourceTitles = Array.isArray(claim.source_titles) && claim.source_titles.length > 0 ? claim.source_titles.join('; ') : 'unknown source';\n      lines.push('Claim ' + index + ': ' + formatConflictValue(conflict, claim));\n      lines.push('Sources: ' + sourceTitles);\n      lines.push(formatSupport(claim));\n      index += 1;\n    }\n    if (typeof conflict?.conflict_summary_hint === 'string' && conflict.conflict_summary_hint.trim() !== '') {\n      lines.push('Hint: ' + conflict.conflict_summary_hint.trim());\n    }\n    if (conflict?.most_recent_claim && typeof conflict.most_recent_claim.value === 'string') {\n      const formattedDate = formatHintDate(conflict.most_recent_claim.created_at);\n      if (formattedDate) lines.push('Most recent: ' + conflict.most_recent_claim.value + ' (' + formattedDate + ')');\n    }\n  }\n  return lines.join('\\n');\n};\nif (!conflictMode && (statusCode < 200 || statusCode >= 300 || answer === '')) {\n  console.log(JSON.stringify({\n    event: 'assistant_response_failed',\n    correlation_id: base.trace?.correlation_id ?? null,\n    query: base.query,\n    project_slug: base.project_slug ?? null,\n    ranking_mode: base.trace?.ranking_mode ?? 'semantic',\n    grounding_status: base.grounding?.status ?? null,\n    answer_mode: base.answer_mode ?? null,\n    conflict_severity: base.conflict_severity ?? null,\n    entity_focus: base.entity_focus ?? null,\n    filtered_candidate_count: Number(base.filtered_candidate_count ?? 0),\n    status_code: statusCode || null,\n    source_count: Array.isArray(base.sources) ? base.sources.length : 0,\n    usage_available: usage.available,\n    input_tokens: usage.input_tokens,\n    output_tokens: usage.output_tokens,\n    total_tokens: usage.total_tokens,\n    usage_reason: usage.reason,\n    error: body.error ?? null,\n  }));\n  return [{\n    json: {\n      ok: false,\n      error: {\n        code: 'OLLAMA_GENERATION_FAILED',\n        message: 'Ollama answer generation failed',\n        status: statusCode || null,\n        details: body.error ?? null,\n        classification: 'transient',\n        retryable: true,\n      },\n      query: base.query,\n      session_id: base.session_id,\n      project_slug: base.project_slug ?? null,\n      top_k: base.top_k,\n      usage,\n      retrieval: {\n        strategy: base.retrieval_strategy,\n        project_match_count: base.project_match_count,\n        general_match_count: base.general_match_count,\n        lexical_project_match_count: base.lexical_project_match_count,\n        lexical_general_match_count: base.lexical_general_match_count,\n        lexical_all_match_count: base.lexical_all_match_count,\n        usable_memory_count: base.usable_memory_count,\n        suspect_memory_count: base.suspect_memory_count,\n        memory_count: base.strong_memory_count,\n        strongest_similarity: base.strongest_similarity,\n        similarity_threshold: base.similarity_threshold,\n        empty: false,\n      },\n      trust: base.trust,\n      grounding: base.grounding ?? null,\n      sources: base.sources,\n      selected_sources: base.selected_sources ?? base.sources,\n      retrieved_candidates: base.retrieved_candidates ?? [],\n      answer_mode: base.answer_mode ?? 'direct',\n      conflict_flag: base.conflict_flag === true,\n      conflict_severity: base.conflict_severity ?? null,\n      conflict_details: base.conflict_details ?? [],\n      claim_support_counts: base.claim_support_counts ?? [],\n      claim_support_counts_raw: base.claim_support_counts_raw ?? [],\n      claim_support_counts_deduped: base.claim_support_counts_deduped ?? [],\n      claim_weighted_support: base.claim_weighted_support ?? [],\n      claim_independent_support: base.claim_independent_support ?? [],\n      claim_independence_adjusted_support: base.claim_independence_adjusted_support ?? [],\n      dominant_claim_status: base.dominant_claim_status ?? null,\n      dominant_claim_basis: base.dominant_claim_basis ?? null,\n      claim_confidence: base.claim_confidence ?? null,\n      conflict_summary_hint: base.conflict_summary_hint ?? null,\n      most_supported_claim: base.most_supported_claim ?? null,\n      most_recent_claim: base.most_recent_claim ?? null,\n      source_quality_breakdown: base.source_quality_breakdown ?? [],\n      source_independence_breakdown: base.source_independence_breakdown ?? [],\n      evidence_clusters: base.evidence_clusters ?? [],\n      entity_focus: base.entity_focus ?? null,\n      filtered_candidate_count: Number(base.filtered_candidate_count ?? 0),\n      context_preview: base.context_preview,\n      session: {\n        turn_count_before: base.session_turn_count_before,\n        history_used: base.session_turn_count_before > 0,\n        stored: false,\n      },\n      trace: withStage(base.trace, 'answer_failed', 'failed', {\n        error_code: 'OLLAMA_GENERATION_FAILED',\n        error_message: 'Ollama answer generation failed',\n        grounding_status: base.grounding?.status ?? null,\n        weak_grounding: base.grounding?.weak_grounding ?? null,\n        answer_mode: base.answer_mode ?? null,\n        input_tokens: usage.input_tokens,\n        output_tokens: usage.output_tokens,\n        total_tokens: usage.total_tokens,\n        prompt_tokens: usage.prompt_tokens,\n        completion_tokens: usage.completion_tokens,\n        prompt_eval_count: usage.prompt_eval_count,\n        eval_count: usage.eval_count,\n        usage_available: usage.available,\n        usage_reason: usage.reason,\n        usage_provider: usage.provider,\n        usage_source: usage.source,\n        conflict_flag: base.conflict_flag === true,\n      }),\n    },\n  }];\n}\nlet finalAnswer = conflictMode ? buildConflictAnswer() : answer;\nconst topSource = Array.isArray(base.sources) ? base.sources[0] : null;\nconst selectedSources = Array.isArray(base.selected_sources)\n  ? base.selected_sources\n  : (Array.isArray(base.sources) ? base.sources : []);\nconst anchorLookupQuery = typeof base.query === 'string' && /^Which note (?:is named|contains)\\b/i.test(base.query);\nconst undocumentedEvidenceQuery = typeof base.query === 'string' && /\\b(undocumented|not documented|not in the memory pack|not recorded|not captured)\\b/i.test(base.query);\nconst contradictionEvidenceQuery = typeof base.query === 'string' && /\\b(contradict(?:ion|ory|ions)|conflicting claims|disagree|incompatible claims)\\b/i.test(base.query);\nconst boundaryQuery = typeof base.query === 'string' && /\\b(not in the project memory|outside (?:the )?project memory|outside (?:my|the) memory|beyond (?:the )?project memory|outside the stored record|not in the stored memory)\\b/i.test(base.query);\nconst uncertaintyFocusQuery = typeof base.query === 'string' && /\\b(?:what parts? of (?:the )?history are uncertain|what remains uncertain|what is uncertain|uncertain parts? of (?:the )?history)\\b/i.test(base.query);\nconst trustLead = (() => {\n  const grounding = base.grounding && typeof base.grounding === 'object' ? base.grounding : null;\n  if (!grounding || conflictMode) return '';\n  if (grounding.status === 'grounded') return 'Based on available project memory, this answer is backed by visible supporting sources.';\n  if (grounding.status === 'weak') return typeof grounding.note === 'string' && grounding.note.trim() !== ''\n    ? grounding.note.trim()\n    : 'The evidence is limited, but the answer below reflects the best available project memory.';\n  return '';\n})();\nconst topicLabelFromSource = (source) => {\n  const filename = typeof source?.filename === 'string' && source.filename.trim() !== '' ? source.filename.trim().toLowerCase() : '';\n  if (!filename) return '';\n  return filename\n    .replace(/^\\d+[-_]?/, '')\n    .replace(/\\.txt$/i, '')\n    .replace(/-vs-/g, ' vs ')\n    .replace(/[-_]+/g, ' ')\n    .replace(/\\s+/g, ' ')\n    .trim();\n};\nconst formatTopicList = (items) => {\n  if (items.length === 0) return '';\n  if (items.length === 1) return items[0];\n  if (items.length === 2) return items[0] + ' and ' + items[1];\n  return items.slice(0, -1).join(', ') + ', and ' + items[items.length - 1];\n};\nconst sourceTopicLabels = Array.from(new Set(selectedSources.map((source) => topicLabelFromSource(source)).filter(Boolean))).slice(0, 3);\nconst metaLinePatterns = [\n  /^i(?:'m| am) crispybrain\\b/i,\n  /^i(?: do not| don't) have (?:any )?information outside\\b/i,\n  /^my knowledge is based on\\b/i,\n  /^as an ai\\b/i,\n  /^as per my training\\b/i,\n  /^based on (?:my |the )?training(?: data)?\\b/i,\n  /^based on general knowledge\\b/i,\n  /^generally(?: speaking)?\\b/i,\n  /^it is known that\\b/i,\n  /^rumou?red\\b/i,\n  /^in general\\b/i,\n  /^i(?:'m| am) hesitant to\\b/i,\n  /^i should only\\b/i,\n  /^i will refrain\\b/i,\n  /^i cannot access\\b/i,\n  /^i cannot provide\\b/i,\n  /^i cannot provide information that is not present\\b/i,\n  /^i(?: do not| don't) know beyond\\b/i,\n  /^if you're looking for something specific\\b/i,\n  /^feel free to ask\\b/i,\n  /^i only provide facts supported by\\b/i,\n  /^i am a local assistant\\b/i,\n  /^because this note does not\\b/i,\n  /^this appears to be self-referential\\b/i,\n];\nconst metaSentencePatterns = [\n  /\\bI(?:'m| am) CrispyBrain[^.?!]*[.?!]?\\s*/gi,\n  /\\bI(?: do not| don't) have (?:any )?information outside[^.?!]*[.?!]?\\s*/gi,\n  /\\bMy knowledge is based on[^.?!]*[.?!]?\\s*/gi,\n  /\\bI only provide facts supported by[^.?!]*[.?!]?\\s*/gi,\n  /\\bIf you're looking for something specific[^.?!]*[.?!]?\\s*/gi,\n  /\\bFeel free to ask[^.?!]*[.?!]?\\s*/gi,\n  /\\bAs an AI[^.?!]*[.?!]?\\s*/gi,\n  /\\bAs per my training[^.?!]*[.?!]?\\s*/gi,\n  /\\bBased on (?:my |the )?training(?: data)?[^.?!]*[.?!]?\\s*/gi,\n  /\\bBased on general knowledge[^.?!]*[.?!]?\\s*/gi,\n  /\\bGenerally(?: speaking)?[^.?!]*[.?!]?\\s*/gi,\n  /\\bIt is known that[^.?!]*[.?!]?\\s*/gi,\n  /\\bRumou?red[^.?!]*[.?!]?\\s*/gi,\n  /\\bIn general[^.?!]*[.?!]?\\s*/gi,\n  /\\bI(?:'m| am) hesitant to[^.?!]*[.?!]?\\s*/gi,\n  /\\bI should only[^.?!]*[.?!]?\\s*/gi,\n  /\\bI will refrain[^.?!]*[.?!]?\\s*/gi,\n  /\\bI cannot access[^.?!]*[.?!]?\\s*/gi,\n  /\\bI cannot provide[^.?!]*[.?!]?\\s*/gi,\n  /\\bI cannot provide information that is not present[^.?!]*[.?!]?\\s*/gi,\n  /\\bI(?: do not| don't) know beyond[^.?!]*[.?!]?\\s*/gi,\n  /\\bI am a local assistant[^.?!]*[.?!]?\\s*/gi,\n  /\\bBecause this note does not[^.?!]*[.?!]?\\s*/gi,\n  /\\bThis appears to be self-referential[^.?!]*[.?!]?\\s*/gi,\n];\nconst sanitizeDomainAnswer = (value) => {\n  let normalized = typeof value === 'string' ? value.replace(/\\r/g, '').trim() : '';\n  if (normalized === '') return '';\n  for (const pattern of metaSentencePatterns) {\n    normalized = normalized.replace(pattern, '');\n  }\n  const lines = normalized.split('\\n');\n  const cleaned = [];\n  for (const line of lines) {\n    const trimmed = line.trim();\n    if (trimmed === '') {\n      cleaned.push('');\n      continue;\n    }\n    if (/^Based on available project memory, this answer is backed by visible supporting sources\\.?$/i.test(trimmed)) continue;\n    if (metaLinePatterns.some((pattern) => pattern.test(trimmed))) continue;\n    cleaned.push(line.trimEnd());\n  }\n  return cleaned.join('\\n').replace(/[ \\t]{2,}/g, ' ').replace(/\\n{3,}/g, '\\n\\n').trim();\n};\nconst sanitizeUserFacingAnswer = (value) => String(value || '')\n  .replace(/\\bcb-v[0-9a-z-]+\\b/gi, 'available project memory')\n  .replace(/\\bcbv[0-9]+\\b/gi, 'available project memory')\n  .replace(/\\b[a-z0-9._-]+\\.txt\\s*::\\s*chunk\\s*\\d+\\b/gi, 'retrieved notes')\n  .replace(/\\bchunk\\s+\\d+\\b/gi, 'retrieved notes')\n  .replace(/\\b(?:seed-data|runtime)\\/[^\\n\\s,;]+/gi, 'retrieved notes')\n  .replace(/(?:\\/Users|\\/tmp|\\/var|[A-Za-z]:\\\\)[^\\n\\s,;]+/g, 'retrieved notes')\n  .replace(/\\boperators?\\s+should\\s+(?:inspect|check|review|refer to)[^.?!]*[.?!]?\\s*/gi, '')\n  .replace(/\\b(?:inspect|check)\\s+(?:grounding|selected sources|selected source|trace|memory ids?|selected_sources)[^.?!]*[.?!]?\\s*/gi, '')\n  .replace(/\\brefer to\\s+memory ids?[^.?!]*[.?!]?\\s*/gi, '')\n  .replace(/\\bBased on available project memory, this answer is backed by visible supporting sources\\.?\\s*/gi, '')\n  .replace(/\\bBased on the retrieved memory context,?\\s*(?:here is the answer:|here's the answer:|here is the breakdown:|here's the breakdown:)?\\s*/gi, '')\n  .replace(/\\boperators?\\s+needing a clearer trace of what stage had failed\\b/gi, 'the need for a clearer trace of which stage had failed')\n  .replace(/\\boperator UI\\b/gi, 'UI')\n  .replace(/\\bthere was an unresolved ([^,]+) left unresolved\\b/gi, 'there was an unresolved $1')\n  .replace(/[ \\t]+\\n/g, '\\n')\n  .replace(/\\n{3,}/g, '\\n\\n')\n  .replace(/[ \\t]{2,}/g, ' ')\n  .trim();\nconst stripQualityPreamble = (value) => String(value || '')\n  .replace(/^Based on available project memory, this answer is backed by visible supporting sources\\.\\s*/i, '')\n  .replace(/^Based on available project memory,\\s*/i, '')\n  .replace(/^Based on the retrieved memory context,?\\s*/i, '')\n  .replace(/^here(?:'s| is)\\s+(?:what I found|a summary[^:]*|the breakdown[^:]*|what went wrong[^:]*|what was found[^:]*):\\s*/i, '')\n  .trim();\nconst containsMetaLanguage = (value) => {\n  const normalized = String(value || '').trim();\n  if (normalized === '') return false;\n  for (const pattern of metaLinePatterns) {\n    if (pattern.test(normalized)) return true;\n  }\n  for (const pattern of metaSentencePatterns) {\n    pattern.lastIndex = 0;\n    if (pattern.test(normalized)) return true;\n  }\n  return /(?:based on the retrieved memory context|here(?:'s| is) what i found|feel free to ask|as per my training|based on (?:my |the )?training(?: data)?|based on general knowledge|generally(?: speaking)?|it is known that|rumou?red|in general|i(?:'m| am) hesitant to|i should only|i will refrain|i cannot provide|because this note does not|this appears to be self-referential)/i.test(normalized);\n};\nconst containsUnsupportedKnowledgeLanguage = (value) => /(?:based on (?:my |the )?training(?: data)?|based on general knowledge|generally(?: speaking)?|it is known that|rumou?red|in general|as an ai)/i.test(String(value || ''));\nconst dedupeAnswerParts = (parts) => {\n  const seen = new Set();\n  const unique = [];\n  for (const part of parts) {\n    const cleaned = String(part || '').trim();\n    if (cleaned === '') continue;\n    const key = cleaned.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();\n    if (seen.has(key)) continue;\n    seen.add(key);\n    unique.push(cleaned);\n  }\n  return unique;\n};\nconst normalizeSynthesisItem = (value) => String(value || '')\n  .replace(/^\\*\\*([^*]+?)\\s*:?\\*\\*\\s*/, '$1 ')\n  .replace(/^\\*+\\s*/, '')\n  .replace(/\\s*\\*+$/, '')\n  .replace(/^[*-]\\s+/, '')\n  .replace(/[\\s]+/g, ' ')\n  .replace(/^[,;:\\-]+\\s*/, '')\n  .trim();\nconst ensureSentence = (value) => {\n  const cleaned = normalizeSynthesisItem(value).replace(/[.;:]+$/, '').trim();\n  if (cleaned === '') return '';\n  return /[.!?]$/.test(cleaned) ? cleaned : cleaned + '.';\n};\nconst normalizeSentenceList = (items, maxItems) => {\n  const normalized = dedupeAnswerParts(items.map((item) => ensureSentence(item)).filter(Boolean));\n  return Number.isInteger(maxItems) && maxItems > 0 ? normalized.slice(0, maxItems) : normalized;\n};\nconst bulletizeSummaryItems = (items) => normalizeSentenceList(items, 4)\n  .map((item) => '- ' + item.replace(/[.!?]$/, ''))\n  .join('\\n');\nconst sectionHeadingMatchers = [\n  { section: 'known', pattern: /^(?:summary of )?(?:known facts?|what is known|known|clearly supported)\\s*:?\\s*(.*)$/i },\n  { section: 'uncertain', pattern: /^(?:what is uncertain|what remains uncertain|uncertain(?: or incomplete)?|uncertain or incomplete|what is incomplete|limitations?|partially documented)\\s*:?\\s*(.*)$/i },\n  { section: 'verify', pattern: /^(?:what cannot be verified|cannot be verified|cannot verify|not verified)\\s*:?\\s*(.*)$/i },\n];\nconst inlineSectionHeadingPattern = /(\\*\\*(?:what is known|what is uncertain|what cannot be verified|clearly supported|partially documented|cannot be verified)\\s*:?\\*\\*|(?:what is known|what is uncertain|what cannot be verified|clearly supported|partially documented|cannot be verified)\\s*:)/gi;\nconst contradictionLeadPattern = /^(?:no (?:further )?information (?:is )?available|there is no information(?: about)?|there is no information provided|there is no explicit information|no explicit information is available|no information is available)/i;\nconst uncertaintyItemPattern = /\\b(?:uncertain|incomplete|partially documented|partial evidence|not fully documented|remain(?:s)? open|still open|unfinished|deferred|limited|only partially documented|missing from the record)\\b/i;\nconst verificationItemPattern = /\\b(?:cannot be verified|no reliable (?:project-memory )?evidence|no explicit information|no further information|there is no information(?: about)?|there is no information provided|not recorded|not captured|outside (?:that )?stored record|outside (?:the )?project memory|beyond those notes|beyond the provided memory context)\\b/i;\nconst splitSynthesisFragments = (value) => String(value || '')\n  .split('\\n')\n  .flatMap((line) => line.split(/\\s*;\\s+/))\n  .map((item) => normalizeSynthesisItem(item))\n  .filter(Boolean);\nconst genericUncertaintyLeadPattern = /^(?:\\*+\\s*)?(?:the following parts of the history are uncertain|these parts of the history are uncertain)\\.?$/i;\nconst sectionLabelItemPattern = /^(?:clearly supported|partially documented|cannot be verified|what is known|what is uncertain|what cannot be verified)[.:;]?$/i;\nconst canonicalizeVerificationItem = (item) => {\n  if (/^i cannot provide information that is not present\\b/i.test(item)) {\n    return 'Details outside the stored record cannot be verified from project memory.';\n  }\n  if (contradictionLeadPattern.test(item) || /\\bthere is no information(?: about)?\\b/i.test(item)) {\n    return boundaryQuery\n      ? 'Details outside that stored record cannot be verified from project memory.'\n      : 'Anything beyond those retrieved notes cannot be verified from project memory.';\n  }\n  return item;\n};\nconst collectSynthesisSections = (value) => {\n  const sections = {\n    known: [],\n    uncertain: [],\n    verify: [],\n  };\n  let currentSection = 'known';\n  const normalizedValue = stripQualityPreamble(value)\n    .replace(inlineSectionHeadingPattern, '\\n$1')\n    .replace(/\\n{3,}/g, '\\n\\n');\n  for (const rawLine of normalizedValue.split('\\n')) {\n    const trimmed = rawLine.trim();\n    if (trimmed === '') continue;\n    let matchedSection = null;\n    for (const matcher of sectionHeadingMatchers) {\n      const match = trimmed.match(matcher.pattern);\n      if (!match) continue;\n      matchedSection = matcher.section;\n      currentSection = matcher.section;\n      const remainder = normalizeSynthesisItem(match[1] || '');\n      if (remainder !== '') sections[matcher.section].push(remainder);\n      break;\n    }\n    if (matchedSection) continue;\n    sections[currentSection].push(trimmed);\n  }\n  return {\n    known: splitSynthesisFragments(sections.known.join('\\n')),\n    uncertain: splitSynthesisFragments(sections.uncertain.join('\\n')),\n    verify: splitSynthesisFragments(sections.verify.join('\\n')),\n  };\n};\nconst analyzeSynthesisAnswer = (value) => {\n  const sections = collectSynthesisSections(value);\n  const known = [];\n  const uncertain = [];\n  const verify = [];\n  let contradictionPhraseRemoved = false;\n  for (const item of sections.known) {\n    if (sectionLabelItemPattern.test(item)) {\n      continue;\n    }\n    if (verificationItemPattern.test(item)) {\n      verify.push(canonicalizeVerificationItem(item));\n      continue;\n    }\n    if (uncertaintyItemPattern.test(item)) {\n      uncertain.push(item);\n      continue;\n    }\n    known.push(item);\n  }\n  uncertain.push(...sections.uncertain.filter((item) => !sectionLabelItemPattern.test(item) && !verificationItemPattern.test(item) && !genericUncertaintyLeadPattern.test(item)));\n  verify.push(...sections.verify.filter((item) => !sectionLabelItemPattern.test(item)).map((item) => canonicalizeVerificationItem(item)));\n  const normalizedKnown = normalizeSentenceList(known, 4);\n  const normalizedUncertain = normalizeSentenceList(uncertain, 3);\n  const normalizedVerify = normalizeSentenceList(verify, 2);\n  const factCount = normalizedKnown.length;\n  const filteredKnown = [];\n  for (const item of normalizedKnown) {\n    if (factCount > 0 && contradictionLeadPattern.test(item)) {\n      contradictionPhraseRemoved = true;\n      continue;\n    }\n    filteredKnown.push(item);\n  }\n  let finalKnown = [...filteredKnown];\n  let finalUncertain = [...normalizedUncertain];\n  if (uncertaintyFocusQuery && finalKnown.length > 0) {\n    finalUncertain = normalizeSentenceList([...finalUncertain, ...finalKnown], 4)\n      .filter((item) => !genericUncertaintyLeadPattern.test(item));\n    finalKnown = [];\n  }\n  return {\n    known: finalKnown,\n    uncertain: finalUncertain,\n    verify: normalizedVerify,\n    contradictionPhraseRemoved,\n    repeatedUncertaintyCollapsed: finalUncertain.length > 1 || normalizedVerify.length > 1,\n  };\n};\nconst sourceStrengthSummary = (() => {\n  const strongestSimilarity = selectedSources.reduce((best, source) => Math.max(best, Number(source?.similarity ?? 0)), 0);\n  const highestLexicalOverlap = selectedSources.reduce((best, source) => Math.max(best, Number(source?.lexical_overlap ?? 0)), 0);\n  const highestStrongTokenHits = selectedSources.reduce((best, source) => Math.max(best, Number(source?.strong_token_hits ?? 0)), 0);\n  const meaningfulSourceCount = selectedSources.filter((source) => Number(source?.strong_token_hits ?? 0) > 0 || Number(source?.lexical_overlap ?? 0) >= 3 || Number(source?.similarity ?? 0) >= 0.6).length;\n  const highSignalSourceCount = selectedSources.filter((source) => Number(source?.strong_token_hits ?? 0) > 0 || Number(source?.lexical_overlap ?? 0) >= 4 || Number(source?.similarity ?? 0) >= 0.64).length;\n  return {\n    selected_source_count: selectedSources.length,\n    strongest_similarity: strongestSimilarity,\n    highest_lexical_overlap: highestLexicalOverlap,\n    highest_strong_token_hits: highestStrongTokenHits,\n    meaningful_source_count: meaningfulSourceCount,\n    high_signal_source_count: highSignalSourceCount,\n  };\n})();\nconst joinSummaryItems = (items) => {\n  const normalized = normalizeSentenceList(items, 4);\n  if (normalized.length === 0) return '';\n  return normalized.join(' ');\n};\nconst buildKnownSummary = (details) => {\n  if (details.known.length > 0) {\n    const summary = joinSummaryItems(details.known);\n    if (base.grounding?.status === 'weak' || sourceStrengthSummary.meaningful_source_count <= 1) {\n      return 'Available project memory is limited, but it supports these points: ' + summary;\n    }\n    return 'Project memory supports these points: ' + summary;\n  }\n  if (boundaryQuery) return 'Project memory only shows what the repo explicitly records.';\n  const topicSummary = formatTopicList(sourceTopicLabels);\n  if (topicSummary) return 'Project memory provides only partial information here, mainly about ' + topicSummary + '.';\n  return 'Project memory provides only partial information relevant to this request.';\n};\nconst buildLimitationSummary = (details) => {\n  if (details.uncertain.length > 0) {\n    if (uncertaintyFocusQuery) {\n      return 'The available notes leave several areas uncertain: ' + joinSummaryItems(details.uncertain);\n    }\n    return joinSummaryItems(details.uncertain);\n  }\n  if (boundaryQuery) {\n    return 'The stored history only covers what the repo explicitly records.';\n  }\n  const note = typeof base.grounding?.note === 'string' ? base.grounding.note.trim() : '';\n  if (base.grounding?.status === 'weak' && note !== '') {\n    return 'Early development is only partially documented in the retrieved notes.';\n  }\n  return 'The available project memory is limited to the retrieved notes.';\n};\nconst buildVerificationSummary = (details) => {\n  if (details.verify.length > 0) {\n    return joinSummaryItems(details.verify);\n  }\n  if (boundaryQuery) {\n    return 'Details outside that stored record cannot be verified from project memory.';\n  }\n  if (undocumentedEvidenceQuery) {\n    return 'Undocumented details cannot be verified from project memory.';\n  }\n  return 'Anything beyond those retrieved notes cannot be verified from project memory.';\n};\nconst renderNarrativeAnswer = (details) => dedupeAnswerParts([\n  buildKnownSummary(details),\n  buildLimitationSummary(details),\n  buildVerificationSummary(details),\n]).join('\\n\\n');\nconst renderStructuredSection = (title, items, fallback) => {\n  const bulletBody = bulletizeSummaryItems(items.length > 0 ? items : (fallback ? [fallback] : []));\n  if (bulletBody === '') return '';\n  return '**' + title + '**\\n' + bulletBody;\n};\nconst renderStructuredAnswer = (details) => dedupeAnswerParts([\n  renderStructuredSection('What is known', details.known, ''),\n  renderStructuredSection('What is uncertain', details.uncertain, buildLimitationSummary(details)),\n  renderStructuredSection('What cannot be verified', details.verify, buildVerificationSummary(details)),\n]).join('\\n\\n');\nif (!conflictMode && anchorLookupQuery && base.trace?.ranking_mode === 'anchor' && typeof topSource?.title === 'string') finalAnswer = 'Memory 1: ' + topSource.title;\nif (!conflictMode && undocumentedEvidenceQuery) finalAnswer = 'Based on available project memory, documented problems, failures, decisions, and explicit uncertainty markers are visible, but there is no reliable evidence to confirm mistakes that were not recorded.';\nif (!conflictMode && contradictionEvidenceQuery) finalAnswer = 'Based on available project memory, the retrieved history does not show a clear mutually exclusive contradiction. The uncertainty is mostly about incomplete documentation and partial evidence rather than incompatible claims.';\nconst answerBeforeSanitize = finalAnswer;\nconst sanitizedAnswer = sanitizeDomainAnswer(finalAnswer);\nconst assistantMetaRemoved = sanitizedAnswer !== answerBeforeSanitize;\nconst memoryLeakageRemoved = assistantMetaRemoved;\nfinalAnswer = sanitizedAnswer || finalAnswer;\nconst memoryOnlyVerificationRequired = !conflictMode && (containsUnsupportedKnowledgeLanguage(finalAnswer) || (memoryLeakageRemoved && stripQualityPreamble(finalAnswer) === ''));\nlet synthesisDetails = analyzeSynthesisAnswer(finalAnswer);\nif (memoryOnlyVerificationRequired) {\n  finalAnswer = dedupeAnswerParts([\n    buildKnownSummary(synthesisDetails),\n    buildVerificationSummary(synthesisDetails),\n  ]).join('\\n\\n');\n  synthesisDetails = analyzeSynthesisAnswer(finalAnswer);\n}\nconst supportedFactCount = synthesisDetails.known.length;\nconst contradictionPhraseRemoved = synthesisDetails.contradictionPhraseRemoved;\nconst repeatedUncertaintyCollapsed = synthesisDetails.repeatedUncertaintyCollapsed;\nconst sanitizedWordCount = (finalAnswer.match(/[A-Za-z0-9]+/g) ?? []).length;\nconst sparseAnswerCandidate = sourceStrengthSummary.selected_source_count <= 1 || sourceStrengthSummary.meaningful_source_count <= 1 || supportedFactCount <= 1;\nconst adaptiveStructureCandidate = !conflictMode && (boundaryQuery || base.grounding?.status === 'weak' || sparseAnswerCandidate);\nconst structuredModeEligible = supportedFactCount >= 2 && sourceStrengthSummary.meaningful_source_count >= 2 && !uncertaintyFocusQuery;\nconst answerStructureMode = adaptiveStructureCandidate ? (structuredModeEligible ? 'structured' : 'narrative') : 'passthrough';\nconst answerQualityGuardApplied = adaptiveStructureCandidate;\nconst memoryOnlyGuardApplied = memoryLeakageRemoved || memoryOnlyVerificationRequired;\nconst synthesisRefined = answerQualityGuardApplied || memoryOnlyGuardApplied;\nif (answerStructureMode === 'structured') {\n  finalAnswer = renderStructuredAnswer(synthesisDetails);\n} else if (answerStructureMode === 'narrative') {\n  finalAnswer = renderNarrativeAnswer(synthesisDetails);\n}\nfinalAnswer = finalAnswer\n  .replace(/\\bI cannot provide information that is not present in the provided retrieved memory context\\.?\\s*/gi, '')\n  .replace(/^the following parts of the history are uncertain\\.?$/gim, 'Early development is only partially documented in the retrieved notes.')\n  .replace(/\\bThere is no information about the development of CrispyBrain beyond the provided memory context\\.?\\s*/gi, 'Anything beyond those retrieved notes cannot be verified from project memory. ')\n  .replace(/\\n{3,}/g, '\\n\\n')\n  .trim();\nconst answerAlreadySignalsTrust = /^(Based on available project memory|Available project memory|Project memory|\\*\\*What is known\\*\\*|Grounding is weak:|However, the available information is limited|The evidence is limited, but|I do not have enough stored memory)/i.test(finalAnswer);\nif (!conflictMode && trustLead && !answerAlreadySignalsTrust && !undocumentedEvidenceQuery && !contradictionEvidenceQuery) finalAnswer = trustLead + '\\n\\n' + finalAnswer;\nfinalAnswer = sanitizeUserFacingAnswer(finalAnswer);\nconsole.log(JSON.stringify({\n  event: 'assistant_response_ready',\n  correlation_id: base.trace?.correlation_id ?? null,\n  query: base.query,\n  project_slug: base.project_slug ?? null,\n  ranking_mode: base.trace?.ranking_mode ?? 'semantic',\n  grounding_status: base.grounding?.status ?? null,\n  answer_mode: base.answer_mode ?? null,\n  conflict_flag: base.conflict_flag === true,\n  conflict_severity: base.conflict_severity ?? null,\n  dominant_claim_status: base.dominant_claim_status ?? null,\n  claim_confidence: base.claim_confidence ?? null,\n  conflict_summary_hint: base.conflict_summary_hint ?? null,\n  claim_weighted_support: base.claim_weighted_support ?? [],\n  claim_independent_support: base.claim_independent_support ?? [],\n  claim_independence_adjusted_support: base.claim_independence_adjusted_support ?? [],\n  dominant_claim_basis: base.dominant_claim_basis ?? null,\n  source_quality_breakdown: base.source_quality_breakdown ?? [],\n  source_independence_breakdown: base.source_independence_breakdown ?? [],\n  evidence_clusters: base.evidence_clusters ?? [],\n  entity_focus: base.entity_focus ?? null,\n  filtered_candidate_count: Number(base.filtered_candidate_count ?? 0),\n  source_count: Array.isArray(base.sources) ? base.sources.length : 0,\n  selected_memory_ids: Array.isArray(base.memory_ids) ? base.memory_ids : [],\n  review_statuses: Array.isArray(base.sources) ? base.sources.map((source) => source.review_status) : [],\n  usage_available: usage.available,\n  input_tokens: usage.input_tokens,\n  output_tokens: usage.output_tokens,\n  total_tokens: usage.total_tokens,\n  usage_reason: usage.reason,\n  assistant_meta_removed: assistantMetaRemoved,\n  memory_leakage_removed: memoryLeakageRemoved,\n  answer_quality_guard_applied: answerQualityGuardApplied,\n  memory_only_guard_applied: memoryOnlyGuardApplied,\n  synthesis_refined: synthesisRefined,\n  answer_structure_mode: answerStructureMode,\n  supported_fact_count: supportedFactCount,\n  meaningful_source_count: sourceStrengthSummary.meaningful_source_count,\n  contradiction_phrase_removed: contradictionPhraseRemoved,\n  repeated_uncertainty_collapsed: repeatedUncertaintyCollapsed,\n  answer_length: finalAnswer.length,\n}));\nreturn [{\n  json: {\n    ok: true,\n    answer: finalAnswer,\n    query: base.query,\n    session_id: base.session_id,\n    project_slug: base.project_slug ?? null,\n    top_k: base.top_k,\n    usage,\n    retrieval: {\n      strategy: base.retrieval_strategy,\n      project_match_count: base.project_match_count,\n      general_match_count: base.general_match_count,\n      lexical_project_match_count: base.lexical_project_match_count,\n      lexical_general_match_count: base.lexical_general_match_count,\n      lexical_all_match_count: base.lexical_all_match_count,\n      usable_memory_count: base.usable_memory_count,\n      suspect_memory_count: base.suspect_memory_count,\n      memory_count: base.strong_memory_count,\n      strongest_similarity: base.strongest_similarity,\n      similarity_threshold: base.similarity_threshold,\n      empty: false,\n    },\n    trust: base.trust,\n    grounding: base.grounding ?? null,\n    sources: base.sources,\n    selected_sources: base.selected_sources ?? base.sources,\n    retrieved_candidates: base.retrieved_candidates ?? [],\n    answer_mode: base.answer_mode ?? 'direct',\n    conflict_flag: base.conflict_flag === true,\n    conflict_severity: base.conflict_severity ?? null,\n    conflict_details: base.conflict_details ?? [],\n    claim_support_counts: base.claim_support_counts ?? [],\n    claim_support_counts_raw: base.claim_support_counts_raw ?? [],\n    claim_support_counts_deduped: base.claim_support_counts_deduped ?? [],\n    claim_weighted_support: base.claim_weighted_support ?? [],\n    claim_independent_support: base.claim_independent_support ?? [],\n    claim_independence_adjusted_support: base.claim_independence_adjusted_support ?? [],\n    dominant_claim_status: base.dominant_claim_status ?? null,\n    dominant_claim_basis: base.dominant_claim_basis ?? null,\n    claim_confidence: base.claim_confidence ?? null,\n    conflict_summary_hint: base.conflict_summary_hint ?? null,\n    most_supported_claim: base.most_supported_claim ?? null,\n    most_recent_claim: base.most_recent_claim ?? null,\n    source_quality_breakdown: base.source_quality_breakdown ?? [],\n    source_independence_breakdown: base.source_independence_breakdown ?? [],\n    evidence_clusters: base.evidence_clusters ?? [],\n    entity_focus: base.entity_focus ?? null,\n    filtered_candidate_count: Number(base.filtered_candidate_count ?? 0),\n    context_preview: base.context_preview,\n    session: {\n      turn_count_before: base.session_turn_count_before,\n      history_used: base.session_turn_count_before > 0,\n      stored: false,\n    },\n    trace: withStage(base.trace, 'answer_ready', 'succeeded', {\n      grounding_status: base.grounding?.status ?? null,\n      weak_grounding: base.grounding?.weak_grounding ?? null,\n      answer_mode: base.answer_mode ?? null,\n      input_tokens: usage.input_tokens,\n      output_tokens: usage.output_tokens,\n      total_tokens: usage.total_tokens,\n      prompt_tokens: usage.prompt_tokens,\n      completion_tokens: usage.completion_tokens,\n      prompt_eval_count: usage.prompt_eval_count,\n      eval_count: usage.eval_count,\n      usage_available: usage.available,\n      usage_reason: usage.reason,\n      usage_provider: usage.provider,\n      usage_source: usage.source,\n      assistant_meta_removed: assistantMetaRemoved,\n      memory_leakage_removed: memoryLeakageRemoved,\n      answer_quality_guard_applied: answerQualityGuardApplied,\n      memory_only_guard_applied: memoryOnlyGuardApplied,\n      synthesis_refined: synthesisRefined,\n      answer_structure_mode: answerStructureMode,\n      supported_fact_count: supportedFactCount,\n      meaningful_source_count: sourceStrengthSummary.meaningful_source_count,\n      contradiction_phrase_removed: contradictionPhraseRemoved,\n      repeated_uncertainty_collapsed: repeatedUncertaintyCollapsed,\n      conflict_flag: base.conflict_flag === true,\n    }),\n  },\n}];"
      },
      "id": "code-build-assistant-response",
      "name": "Build Assistant Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3560,
        -120
      ]
    },
    {
      "parameters": {
        "jsCode": "const withStage = (trace, stage, status, extra = {}) => {\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (trace.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (trace.error_message ?? null);\n  return {\n    ...trace,\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...(Array.isArray(trace.stage_history) ? trace.stage_history : []),\n      { stage, status, timestamp, error_code: errorCode, error_message: errorMessage },\n    ],\n  };\n};\nconst grounding = {\n  status: 'none',\n  weak_grounding: true,\n  note: 'No strong supporting memory was retrieved.',\n  reasons: ['no_retrieved_memory'],\n  supporting_source_count: 0,\n  reviewed_source_count: 0,\n  strongest_similarity: $json.strongest_similarity ?? null,\n  similarity_threshold: $json.similarity_threshold,\n  ranking_mode: $json.trace?.ranking_mode ?? null,\n  evidence_strength: 'none',\n  overall_trust_band: 'low',\n  primary_memory_ids: [],\n  primary_chunk_indexes: [],\n};\nconst usage = {\n  provider: 'ollama',\n  source: 'generation',\n  available: false,\n  input_tokens: null,\n  output_tokens: null,\n  total_tokens: null,\n  prompt_tokens: null,\n  completion_tokens: null,\n  prompt_eval_count: null,\n  eval_count: null,\n  reason: 'answer_not_generated',\n};\nreturn [{\n  json: {\n    ok: true,\n    answer: 'I do not have enough stored memory to answer that yet. No strong supporting memory was retrieved.',\n    query: $json.query,\n    session_id: $json.session_id,\n    project_slug: $json.project_slug ?? null,\n    top_k: $json.top_k,\n    usage,\n    retrieval: {\n      strategy: $json.retrieval_strategy,\n      project_match_count: $json.project_match_count,\n      general_match_count: $json.general_match_count,\n      lexical_project_match_count: $json.lexical_project_match_count ?? 0,\n      lexical_general_match_count: $json.lexical_general_match_count ?? 0,\n      lexical_all_match_count: $json.lexical_all_match_count ?? 0,\n      usable_memory_count: $json.usable_memory_count,\n      suspect_memory_count: $json.suspect_memory_count,\n      memory_count: 0,\n      strongest_similarity: $json.strongest_similarity,\n      similarity_threshold: $json.similarity_threshold,\n      empty: true,\n    },\n    trust: {\n      overall_band: 'low',\n      evidence_strength: 'none',\n      reviewed_source_count: 0,\n      unreviewed_source_count: 0,\n      scope_match_count: 0,\n      high_trust_source_count: 0,\n      medium_trust_source_count: 0,\n      low_trust_source_count: 0,\n      uncertainty_indicator: true,\n      uncertainty_reasons: ['no_retrieved_memory'],\n    },\n    grounding,\n    sources: [],\n    selected_sources: [],\n    retrieved_candidates: $json.retrieved_candidates ?? [],\n    answer_mode: 'insufficient',\n    conflict_flag: false,\n    conflict_severity: null,\n    conflict_details: [],\n    claim_support_counts: [],\n    claim_support_counts_raw: [],\n    claim_support_counts_deduped: [],\n    claim_weighted_support: [],\n    claim_independent_support: [],\n    claim_independence_adjusted_support: [],\n    dominant_claim_status: null,\n    dominant_claim_basis: null,\n    claim_confidence: null,\n    conflict_summary_hint: null,\n    most_supported_claim: null,\n    most_recent_claim: null,\n    source_quality_breakdown: [],\n    source_independence_breakdown: [],\n    evidence_clusters: [],\n    entity_focus: $json.entity_focus ?? null,\n    filtered_candidate_count: Number($json.filtered_candidate_count ?? 0),\n    context_preview: '',\n    session: {\n      turn_count_before: $json.session_turn_count_before,\n      history_used: $json.session_turn_count_before > 0,\n      stored: false,\n    },\n    trace: withStage($json.trace, 'retrieval_empty', 'succeeded', {\n      grounding_status: grounding.status,\n      weak_grounding: grounding.weak_grounding,\n      answer_mode: 'insufficient',\n      input_tokens: usage.input_tokens,\n      output_tokens: usage.output_tokens,\n      total_tokens: usage.total_tokens,\n      prompt_tokens: usage.prompt_tokens,\n      completion_tokens: usage.completion_tokens,\n      prompt_eval_count: usage.prompt_eval_count,\n      eval_count: usage.eval_count,\n      usage_available: usage.available,\n      usage_reason: usage.reason,\n      usage_provider: usage.provider,\n      usage_source: usage.source,\n      conflict_flag: false,\n    }),\n  },\n}];"
      },
      "id": "code-build-empty-retrieval-response",
      "name": "Build Empty Retrieval Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3260,
        160
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO openbrain_chat_turns (\n  session_id,\n  role,\n  message_text,\n  project_slug,\n  metadata_json\n)\nVALUES (\n  $1::text,\n  'user',\n  $2::text,\n  NULLIF($3::text, ''),\n  $4::jsonb\n)\nRETURNING\n  id,\n  $1::text AS session_id,\n  NULLIF($3::text, '') AS project_slug,\n  $5::text AS answer_text,\n  ($6::jsonb)::text AS assistant_metadata_json_text,\n  ($7::jsonb)::text AS response_json_text;",
        "options": {
          "queryReplacement": "={{ [$json.session_id, $json.query, $json.project_slug || '', JSON.stringify({ top_k: $json.top_k, request_source: 'assistant', trace: $json.trace || null, run_id: $json.trace?.run_id || null, correlation_id: $json.trace?.correlation_id || null }), $json.answer || $json.error?.message || 'CrispyBrain assistant request failed.', JSON.stringify({ ok: $json.ok, query: $json.query, retrieval: $json.retrieval, usage: $json.usage || null, source_count: Array.isArray($json.sources) ? $json.sources.length : 0, error: $json.error || null, trace: $json.trace || null }), JSON.stringify($json)] }}"
        }
      },
      "id": "postgres-store-user-turn",
      "name": "Store User Turn",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        3860,
        140
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO openbrain_chat_turns (\n  session_id,\n  role,\n  message_text,\n  project_slug,\n  metadata_json\n)\nVALUES (\n  $1::text,\n  'assistant',\n  $2::text,\n  NULLIF($3::text, ''),\n  $4::jsonb\n)\nRETURNING id, ($5::jsonb)::text AS response_json_text;",
        "options": {
          "queryReplacement": "={{ [$json.session_id, $json.answer_text, $json.project_slug || '', $json.assistant_metadata_json_text, $json.response_json_text] }}"
        }
      },
      "id": "postgres-store-assistant-turn",
      "name": "Store Assistant Turn",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        4160,
        140
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const response = typeof $json.response_json_text === 'string'\n  ? JSON.parse($json.response_json_text)\n  : $json;\nconst session = response.session ?? {\n  turn_count_before: 0,\n  history_used: false,\n  stored: false,\n};\nconst trace = response.trace ?? null;\nconst withStage = (existingTrace, stage, status, extra = {}) => {\n  if (!existingTrace) {\n    return null;\n  }\n  const timestamp = new Date().toISOString();\n  const errorCode = Object.prototype.hasOwnProperty.call(extra, 'error_code') ? extra.error_code : (existingTrace.error_code ?? null);\n  const errorMessage = Object.prototype.hasOwnProperty.call(extra, 'error_message') ? extra.error_message : (existingTrace.error_message ?? null);\n  return {\n    ...existingTrace,\n    ...extra,\n    stage,\n    status,\n    error_code: errorCode,\n    error_message: errorMessage,\n    timestamp,\n    stage_history: [\n      ...(Array.isArray(existingTrace.stage_history) ? existingTrace.stage_history : []),\n      { stage, status, timestamp, error_code: errorCode, error_message: errorMessage },\n    ],\n  };\n};\nreturn [{\n  json: {\n    ...response,\n    session: {\n      ...session,\n      stored: true,\n      turn_count_after: (session.turn_count_before ?? 0) + 2,\n    },\n    trace: withStage(trace, 'stored', 'succeeded'),\n  },\n}];"
      },
      "id": "code-return-assistant-response",
      "name": "Return Assistant Response",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        4460,
        140
      ]
    },
    {
      "parameters": {
        "respondWith": "firstIncomingItem",
        "options": {}
      },
      "id": "respond-assistant-result",
      "name": "Respond Assistant Result",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [
        4760,
        300
      ]
    }
  ],
  "connections": {
    "Assistant Webhook": {
      "main": [
        [
          {
            "node": "Normalize Assistant Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Assistant Request": {
      "main": [
        [
          {
            "node": "Request Is Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Is Valid?": {
      "main": [
        [
          {
            "node": "Load Session Turns",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond Assistant Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Session Turns": {
      "main": [
        [
          {
            "node": "Generate Query Embedding",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Query Embedding": {
      "main": [
        [
          {
            "node": "Prepare Retrieval Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Retrieval Input": {
      "main": [
        [
          {
            "node": "Embedding Ready?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Embedding Ready?": {
      "main": [
        [
          {
            "node": "Retrieve Candidate Memories",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Embedding Failure Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retrieve Candidate Memories": {
      "main": [
        [
          {
            "node": "Assemble Retrieval Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assemble Retrieval Context": {
      "main": [
        [
          {
            "node": "Has Strong Retrieval?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Strong Retrieval?": {
      "main": [
        [
          {
            "node": "Generate Assistant Answer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Build Empty Retrieval Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Assistant Answer": {
      "main": [
        [
          {
            "node": "Build Assistant Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Assistant Response": {
      "main": [
        [
          {
            "node": "Store User Turn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Empty Retrieval Response": {
      "main": [
        [
          {
            "node": "Store User Turn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Embedding Failure Response": {
      "main": [
        [
          {
            "node": "Store User Turn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store User Turn": {
      "main": [
        [
          {
            "node": "Store Assistant Turn",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Assistant Turn": {
      "main": [
        [
          {
            "node": "Return Assistant Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Return Assistant Response": {
      "main": [
        [
          {
            "node": "Respond Assistant Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "versionId": "27db0f91-94c5-4ba6-89dc-8b5b8e31e5a1",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "assistant"
}