From 956bf39da96627d2748334ea353778a68adf62b6 Mon Sep 17 00:00:00 2001 From: LienJack Date: Sat, 25 Apr 2026 17:58:42 +0900 Subject: [PATCH 1/5] feat(conversation): add ResolveSavedPriorSqlNode and integrate into agent flow - Introduced the ResolveSavedPriorSqlNode to evaluate and reuse saved SQL queries. - Updated agent step metadata to include the new node in the analysis stage. - Enhanced LangGraph runtime to incorporate the new node, allowing for conditional execution based on prior SQL resolution. - Modified state management to track saved prior SQL resolutions and fallback scenarios. - Updated delivery contract to capture saved prior SQL evidence for improved tracking and diagnostics. - Implemented service for capturing and managing saved prior SQL records, enhancing overall SQL handling in the conversation module. --- .../conversation/agent/agent.module.ts | 2 + .../agent/graph/agent-step-metadata.ts | 2 + .../agent/graph/langgraph.node-handlers.ts | 102 +- .../agent/graph/langgraph.runtime.ts | 25 +- .../agent/graph/langgraph.state.ts | 7 + .../nodes/resolve-saved-prior-sql.node.ts | 157 + .../application/save-view-from-run.usecase.ts | 123 +- .../delivery/delivery-contract.mapper.ts | 89 +- .../contracts/knowledge-memory.contract.ts | 57 + .../knowledge-chat-support.facade.ts | 5 +- .../modules/knowledge/memory/memory.module.ts | 5 +- .../memory/saved-prior-sql.service.ts | 442 + .../src/modules/knowledge/rag/rag.module.ts | 12 + .../rag/retrieval/rag-retrieval.types.ts | 32 +- .../modules/rag/index/rag-index.repository.ts | 19 + .../rag/ingestion/rag-document.repository.ts | 212 + .../rag/quality/rag-quality.service.ts | 60 +- .../rag/retrieval/rag-retrieval.service.ts | 304 +- .../rag/retrieval/rag-retrieval.types.ts | 32 +- .../agent-saved-prior-sql-shortcut.spec.ts | 130 + .../test/integration/rag-quality.spec.ts | 28 +- .../integration/rag-retrieval.service.spec.ts | 126 + .../integration/save-view-from-run.spec.ts | 79 +- .../saved-prior-sql-capture.spec.ts | 208 + .../saved-prior-sql-ingestion.spec.ts | 110 + .../saved-prior-sql-quality.spec.ts | 60 + .../unit/delivery-contract.mapper.spec.ts | 45 + .../test/unit/langgraph-runtime.spec.ts | 34 +- .../unit/resolve-saved-prior-sql.node.spec.ts | 175 + .../components/chat/rag-delivery-panel.tsx | 50 + .../components/chat/run-visibility-mapper.ts | 84 + .../chat-rag-sync-stream-consistency.spec.tsx | 30 + .../tests/unit/rag-delivery-panel.spec.tsx | 31 + graphify-out/GRAPH_REPORT.md | 1183 +-- graphify-out/graph.html | 8 +- graphify-out/graph.json | 9120 ++++++++++------- .../shared-types/src/api.contract.spec.ts | 10 + packages/shared-types/src/api.ts | 12 + 38 files changed, 9058 insertions(+), 4152 deletions(-) create mode 100644 apps/backend/src/modules/conversation/agent/nodes/resolve-saved-prior-sql.node.ts create mode 100644 apps/backend/src/modules/knowledge/memory/saved-prior-sql.service.ts create mode 100644 apps/backend/src/modules/rag/ingestion/rag-document.repository.ts create mode 100644 apps/backend/test/integration/agent-saved-prior-sql-shortcut.spec.ts create mode 100644 apps/backend/test/integration/saved-prior-sql-capture.spec.ts create mode 100644 apps/backend/test/integration/saved-prior-sql-ingestion.spec.ts create mode 100644 apps/backend/test/integration/saved-prior-sql-quality.spec.ts create mode 100644 apps/backend/test/unit/resolve-saved-prior-sql.node.spec.ts diff --git a/apps/backend/src/modules/conversation/agent/agent.module.ts b/apps/backend/src/modules/conversation/agent/agent.module.ts index 7f01026..e9286d4 100644 --- a/apps/backend/src/modules/conversation/agent/agent.module.ts +++ b/apps/backend/src/modules/conversation/agent/agent.module.ts @@ -24,6 +24,7 @@ import { BuildSemanticQueryNode } from "./nodes/build-semantic-query.node"; import { GraphBuilderService } from "./graph/graph.builder"; import { LangGraphRuntimeService } from "./graph/langgraph.runtime"; import { GenerateSqlNode } from "./nodes/generate-sql.node"; +import { ResolveSavedPriorSqlNode } from "./nodes/resolve-saved-prior-sql.node"; import { SafetyCheckNode } from "./nodes/safety-check.node"; import { ExecuteSqlNode } from "./nodes/execute-sql.node"; import { SqlGenerationService } from "./sql/sql-generation.service"; @@ -70,6 +71,7 @@ import { PlannerCacheService } from "./planner/planner-cache.service"; PlannerCacheService, BuildSemanticQueryNode, BuildPhysicalPlanNode, + ResolveSavedPriorSqlNode, GenerateSqlNode, SafetyCheckNode, ExecuteSqlNode, diff --git a/apps/backend/src/modules/conversation/agent/graph/agent-step-metadata.ts b/apps/backend/src/modules/conversation/agent/graph/agent-step-metadata.ts index 81abd28..45f62c2 100644 --- a/apps/backend/src/modules/conversation/agent/graph/agent-step-metadata.ts +++ b/apps/backend/src/modules/conversation/agent/graph/agent-step-metadata.ts @@ -8,6 +8,7 @@ const AGENT_STEP_STAGE_MAP: Record = { "build-intent-plan": "analysis", "build-semantic-query": "analysis", "build-physical-plan": "analysis", + "resolve-saved-prior-sql": "analysis", "generate-sql": "generation", "safety-check": "validation", "execute-sql": "execution", @@ -20,6 +21,7 @@ const AGENT_STEP_TITLE_MAP: Record = { "build-intent-plan": "意图规划", "build-semantic-query": "语义规划", "build-physical-plan": "物理规划", + "resolve-saved-prior-sql": "Prior SQL 复用判断", "generate-sql": "生成 SQL", "safety-check": "安全校验", "execute-sql": "执行查询", diff --git a/apps/backend/src/modules/conversation/agent/graph/langgraph.node-handlers.ts b/apps/backend/src/modules/conversation/agent/graph/langgraph.node-handlers.ts index 89113e5..f4f19d2 100644 --- a/apps/backend/src/modules/conversation/agent/graph/langgraph.node-handlers.ts +++ b/apps/backend/src/modules/conversation/agent/graph/langgraph.node-handlers.ts @@ -8,6 +8,7 @@ import { ClarifyNode } from "../nodes/clarify.node"; import { ExecuteSqlNode } from "../nodes/execute-sql.node"; import { FormatAnswerNode } from "../nodes/format-answer.node"; import { GenerateSqlNode } from "../nodes/generate-sql.node"; +import { ResolveSavedPriorSqlNode } from "../nodes/resolve-saved-prior-sql.node"; import { RetrieveKnowledgeNode } from "../nodes/retrieve-knowledge.node"; import { SafetyCheckNode } from "../nodes/safety-check.node"; import type { @@ -198,6 +199,7 @@ export interface LangGraphNodeDependencies { buildIntentPlanNode: Pick; buildSemanticQueryNode: Pick; buildPhysicalPlanNode: Pick; + resolveSavedPriorSqlNode: Pick; generateSqlNode: Pick; safetyNode: Pick; executeNode: Pick; @@ -835,6 +837,7 @@ export const createLangGraphNodeHandlers = (deps: LangGraphNodeDependencies) => }, sql: generated.sql, explanation: generated.explanation, + savedPriorSqlFallbackRequested: false, error: undefined, fatalError: undefined }; @@ -920,6 +923,9 @@ export const createLangGraphNodeHandlers = (deps: LangGraphNodeDependencies) => workspaceId: state.accessContext?.workspaceId }; if (!safety.allowed) { + const shouldFallbackToGeneration = + state.savedPriorSqlResolution?.status === "hit" && + state.savedPriorSqlResolution.fallbackToGeneration === false; const trace = appendStep(state, { step: { node: "safety-check", @@ -939,10 +945,28 @@ export const createLangGraphNodeHandlers = (deps: LangGraphNodeDependencies) => error: safety.reason }); await runtime.emitStep(latestStep(trace)); + if (shouldFallbackToGeneration) { + return { + ...trace, + error: safety.reason, + safetyDecision: safety, + savedPriorSqlResolution: { + ...state.savedPriorSqlResolution, + safetyRejected: true, + fallbackToGeneration: true, + reused: true + }, + savedPriorSqlFallbackRequested: true, + sql: undefined, + explanation: undefined, + terminalStatus: undefined + }; + } return { ...trace, error: safety.reason, safetyDecision: safety, + savedPriorSqlFallbackRequested: false, terminalStatus: "rejected" }; } @@ -973,7 +997,82 @@ export const createLangGraphNodeHandlers = (deps: LangGraphNodeDependencies) => await runtime.emitStep(latestStep(trace)); return { ...trace, - safetyDecision: safety + safetyDecision: safety, + savedPriorSqlFallbackRequested: false + }; + }; + + const resolveSavedPriorSql = async ( + state: LangGraphState, + config?: LangGraphRunnableConfig + ) => { + const startedAt = new Date().toISOString(); + const runtime = createNodeRuntime(config); + await runtime.emitStep( + buildRunningStep( + state, + "resolve-saved-prior-sql", + startedAt, + "正在评估是否复用已保存 SQL" + ) + ); + const resolution = deps.resolveSavedPriorSqlNode.run({ + retrievalBundle: state.retrievalBundle, + question: state.question + }); + const endedAt = new Date().toISOString(); + const detail = + resolution.status === "hit" + ? "命中已保存 SQL,跳过 LLM SQL 生成。" + : `未命中 saved prior shortcut,状态=${resolution.status}。`; + const trace = appendStep(state, { + step: { + node: "resolve-saved-prior-sql", + status: resolution.status === "hit" ? "success" : "skipped", + detail, + ...withTiming(startedAt, endedAt), + outputSummary: summarize({ + status: resolution.status, + reasonCodes: resolution.reasonCodes, + selectedChunkId: resolution.selectedChunkId, + selectedViewId: resolution.selectedViewId, + selectedSourceRunId: resolution.selectedSourceRunId + }) + }, + outputs: { + status: resolution.status, + reasonCodes: resolution.reasonCodes, + selectedChunkId: resolution.selectedChunkId, + selectedViewId: resolution.selectedViewId, + selectedSourceRunId: resolution.selectedSourceRunId + } + }); + await runtime.emitStep(latestStep(trace)); + if (resolution.status !== "hit" || !resolution.sql) { + return { + ...trace, + savedPriorSqlResolution: { + ...resolution, + reused: false, + safetyRejected: false, + fallbackToGeneration: false + }, + savedPriorSqlFallbackRequested: false + }; + } + return { + ...trace, + model: "saved-prior-sql", + sql: resolution.sql, + explanation: resolution.explanation, + error: undefined, + savedPriorSqlResolution: { + ...resolution, + reused: true, + safetyRejected: false, + fallbackToGeneration: false + }, + savedPriorSqlFallbackRequested: false }; }; @@ -1144,6 +1243,7 @@ export const createLangGraphNodeHandlers = (deps: LangGraphNodeDependencies) => buildIntentPlan, buildSemanticQuery, buildPhysicalPlan, + resolveSavedPriorSql, generateSql, safetyCheck, executeSql, diff --git a/apps/backend/src/modules/conversation/agent/graph/langgraph.runtime.ts b/apps/backend/src/modules/conversation/agent/graph/langgraph.runtime.ts index 388c72e..e94a041 100644 --- a/apps/backend/src/modules/conversation/agent/graph/langgraph.runtime.ts +++ b/apps/backend/src/modules/conversation/agent/graph/langgraph.runtime.ts @@ -14,6 +14,7 @@ import { ClarifyNode } from "../nodes/clarify.node"; import { ExecuteSqlNode } from "../nodes/execute-sql.node"; import { FormatAnswerNode } from "../nodes/format-answer.node"; import { GenerateSqlNode } from "../nodes/generate-sql.node"; +import { ResolveSavedPriorSqlNode } from "../nodes/resolve-saved-prior-sql.node"; import { RetrieveKnowledgeNode } from "../nodes/retrieve-knowledge.node"; import { SafetyCheckNode } from "../nodes/safety-check.node"; import { @@ -46,6 +47,8 @@ const LangGraphStateAnnotation = Annotation.Root({ physicalPlan: Annotation(), planningStatus: Annotation(), planningWarnings: Annotation(), + savedPriorSqlResolution: Annotation(), + savedPriorSqlFallbackRequested: Annotation(), safetyDecision: Annotation(), sql: Annotation(), explanation: Annotation(), @@ -69,6 +72,7 @@ export const createLangGraphRuntime = (deps: LangGraphNodeDependencies) => { .addNode("build-intent-plan", handlers.buildIntentPlan) .addNode("build-semantic-query", handlers.buildSemanticQuery) .addNode("build-physical-plan", handlers.buildPhysicalPlan) + .addNode("resolve-saved-prior-sql", handlers.resolveSavedPriorSql) .addNode("generate-sql", handlers.generateSql) .addNode("safety-check", handlers.safetyCheck) .addNode("execute-sql", handlers.executeSql) @@ -86,7 +90,16 @@ export const createLangGraphRuntime = (deps: LangGraphNodeDependencies) => { .addEdge("retrieve-knowledge", "build-intent-plan") .addEdge("build-intent-plan", "build-semantic-query") .addEdge("build-semantic-query", "build-physical-plan") - .addEdge("build-physical-plan", "generate-sql") + .addEdge("build-physical-plan", "resolve-saved-prior-sql") + .addConditionalEdges( + "resolve-saved-prior-sql", + (state) => + state.savedPriorSqlResolution?.status === "hit" ? "shortcut" : "continue", + { + shortcut: "safety-check", + continue: "generate-sql" + } + ) .addConditionalEdges( "generate-sql", (state) => (state.fatalError ? "fatal" : "continue"), @@ -97,8 +110,14 @@ export const createLangGraphRuntime = (deps: LangGraphNodeDependencies) => { ) .addConditionalEdges( "safety-check", - (state) => (state.terminalStatus === "rejected" ? "rejected" : "continue"), + (state) => { + if (state.savedPriorSqlFallbackRequested) { + return "fallback-generate"; + } + return state.terminalStatus === "rejected" ? "rejected" : "continue"; + }, { + "fallback-generate": "generate-sql", rejected: END, continue: "execute-sql" } @@ -142,6 +161,7 @@ export class LangGraphRuntimeService { private readonly buildIntentPlanNode: BuildIntentPlanNode, private readonly buildSemanticQueryNode: BuildSemanticQueryNode, private readonly buildPhysicalPlanNode: BuildPhysicalPlanNode, + private readonly resolveSavedPriorSqlNode: ResolveSavedPriorSqlNode, private readonly generateSqlNode: GenerateSqlNode, private readonly safetyNode: SafetyCheckNode, private readonly executeNode: ExecuteSqlNode, @@ -153,6 +173,7 @@ export class LangGraphRuntimeService { buildIntentPlanNode: this.buildIntentPlanNode, buildSemanticQueryNode: this.buildSemanticQueryNode, buildPhysicalPlanNode: this.buildPhysicalPlanNode, + resolveSavedPriorSqlNode: this.resolveSavedPriorSqlNode, generateSqlNode: this.generateSqlNode, safetyNode: this.safetyNode, executeNode: this.executeNode, diff --git a/apps/backend/src/modules/conversation/agent/graph/langgraph.state.ts b/apps/backend/src/modules/conversation/agent/graph/langgraph.state.ts index f04e254..0170cc2 100644 --- a/apps/backend/src/modules/conversation/agent/graph/langgraph.state.ts +++ b/apps/backend/src/modules/conversation/agent/graph/langgraph.state.ts @@ -11,6 +11,7 @@ import type { IntentPlan } from "../nodes/build-intent-plan.node"; import type { SemanticQueryPlan } from "../nodes/build-semantic-query.node"; import type { PhysicalPlan } from "../nodes/build-physical-plan.node"; import type { SqlSafetyDecision } from "../sql/tools/sql-safety.guard"; +import type { SavedPriorSqlResolution } from "../nodes/resolve-saved-prior-sql.node"; import type { RagContextPack, RagRetrievalBundle @@ -37,6 +38,12 @@ export interface LangGraphState extends GraphInput { physicalPlan?: PhysicalPlan; planningStatus?: "legacy" | "ready" | "degraded"; planningWarnings?: string[]; + savedPriorSqlResolution?: SavedPriorSqlResolution & { + reused: boolean; + safetyRejected: boolean; + fallbackToGeneration: boolean; + }; + savedPriorSqlFallbackRequested?: boolean; safetyDecision?: SqlSafetyDecision; sql?: string; explanation?: string; diff --git a/apps/backend/src/modules/conversation/agent/nodes/resolve-saved-prior-sql.node.ts b/apps/backend/src/modules/conversation/agent/nodes/resolve-saved-prior-sql.node.ts new file mode 100644 index 0000000..2e89bca --- /dev/null +++ b/apps/backend/src/modules/conversation/agent/nodes/resolve-saved-prior-sql.node.ts @@ -0,0 +1,157 @@ +import { Injectable } from "@nestjs/common"; +import type { + RagPriorSqlLaneEvidence, + RagPriorSqlShortcutDecision, + RagRetrievalBundle, + RagRetrievalChunkPayload +} from "../../../knowledge/rag/retrieval/rag-retrieval.types"; + +export interface SavedPriorSqlResolution { + status: "hit" | "miss" | "filtered" | "stale" | "ambiguous"; + reasonCodes: string[]; + selectedChunkId?: string; + selectedViewId?: string; + selectedSourceRunId?: string; + sql?: string; + explanation?: string; +} + +@Injectable() +export class ResolveSavedPriorSqlNode { + run(input: { + retrievalBundle?: RagRetrievalBundle; + question: string; + }): SavedPriorSqlResolution { + const lane = this.readPriorSqlLane(input.retrievalBundle); + if (!lane) { + return { + status: "miss", + reasonCodes: ["prior_sql_lane_missing"] + }; + } + const decision = this.readShortcutDecision(lane); + const status = decision?.status ?? lane.status; + const reasonCodes = this.readReasonCodes(decision, lane); + + if (status !== "hit") { + return { + status, + reasonCodes, + selectedChunkId: decision?.selected_chunk_id ?? decision?.selectedChunkId, + selectedViewId: decision?.selected_view_id ?? decision?.selectedViewId, + selectedSourceRunId: + decision?.selected_source_run_id ?? decision?.selectedSourceRunId + }; + } + + const selectedChunk = this.pickSelectedChunk(input.retrievalBundle, decision); + const sql = this.extractSql(selectedChunk); + if (!sql) { + return { + status: "miss", + reasonCodes: ["prior_sql_shortcut_missing_sql"], + selectedChunkId: selectedChunk?.chunk_id, + selectedViewId: decision?.selected_view_id ?? decision?.selectedViewId, + selectedSourceRunId: + decision?.selected_source_run_id ?? decision?.selectedSourceRunId + }; + } + + return { + status: "hit", + reasonCodes, + selectedChunkId: selectedChunk?.chunk_id, + selectedViewId: this.readString( + selectedChunk?.metadata.sourceMetadata?.viewId ?? + selectedChunk?.metadata.sourceMetadata?.view_id ?? + decision?.selected_view_id ?? + decision?.selectedViewId + ), + selectedSourceRunId: this.readString( + selectedChunk?.metadata.sourceMetadata?.sourceRunId ?? + selectedChunk?.metadata.sourceMetadata?.source_run_id ?? + decision?.selected_source_run_id ?? + decision?.selectedSourceRunId + ), + sql, + explanation: `复用已保存 SQL(问题:${input.question})。` + }; + } + + private readPriorSqlLane( + bundle: RagRetrievalBundle | undefined + ): RagPriorSqlLaneEvidence | undefined { + if (!bundle) { + return undefined; + } + return bundle.prior_sql_lane ?? bundle.priorSqlLane; + } + + private readShortcutDecision( + lane: RagPriorSqlLaneEvidence + ): RagPriorSqlShortcutDecision | undefined { + return lane.shortcut ?? lane.shortcutDecision; + } + + private readReasonCodes( + decision: RagPriorSqlShortcutDecision | undefined, + lane: RagPriorSqlLaneEvidence + ): string[] { + const reasonCodes = decision?.reason_codes ?? decision?.reasonCodes ?? []; + if (reasonCodes.length > 0) { + return reasonCodes; + } + return lane.degrade_reasons ?? lane.degradeReasons ?? []; + } + + private pickSelectedChunk( + bundle: RagRetrievalBundle | undefined, + decision?: RagPriorSqlShortcutDecision + ): RagRetrievalChunkPayload | undefined { + if (!bundle) { + return undefined; + } + const selectedChunkId = decision?.selected_chunk_id ?? decision?.selectedChunkId; + const selectedFromCandidates = bundle.candidates.find( + (candidate) => candidate.chunk_id === selectedChunkId + )?.chunk; + if (selectedFromCandidates) { + return selectedFromCandidates; + } + if (selectedChunkId) { + return bundle.selected_context?.find((chunk) => chunk.chunk_id === selectedChunkId); + } + return bundle.candidates.find((candidate) => + candidate.evidence.includes("prior_sql:trusted") + )?.chunk; + } + + private extractSql(chunk: RagRetrievalChunkPayload | undefined): string | undefined { + if (!chunk) { + return undefined; + } + const sourceMetadata = chunk.metadata.sourceMetadata; + const directSql = + this.readString(sourceMetadata?.sql) ?? + this.readString(sourceMetadata?.viewSql) ?? + this.readString(sourceMetadata?.view_sql); + if (directSql) { + return directSql; + } + + const fromContent = /\bSQL\s*:\s*([\s\S]+)/i.exec(chunk.content)?.[1]; + const normalized = this.readString(fromContent); + if (normalized) { + return normalized; + } + return this.readString(chunk.content); + } + + private readString(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined; + } + const normalized = value.trim(); + return normalized.length > 0 ? normalized : undefined; + } +} diff --git a/apps/backend/src/modules/conversation/chat/application/save-view-from-run.usecase.ts b/apps/backend/src/modules/conversation/chat/application/save-view-from-run.usecase.ts index fe2ccff..bee68e7 100644 --- a/apps/backend/src/modules/conversation/chat/application/save-view-from-run.usecase.ts +++ b/apps/backend/src/modules/conversation/chat/application/save-view-from-run.usecase.ts @@ -1,13 +1,23 @@ import { createHash } from "node:crypto"; -import { Injectable } from "@nestjs/common"; +import { Inject, Injectable, Optional } from "@nestjs/common"; import { DomainError } from "../../../../common/domain-error"; import { ChatRepository } from "../../../platform/data/persistence"; import { ModelingGraphRepository } from "../../../platform/data/persistence/modeling-graph.repository"; import { ModelingGraphValidator } from "../../../platform/data/persistence/modeling-graph.validator"; import type { ModelingGraphPayload, ModelingGraphView } from "../../../platform/data/persistence/modeling-graph.types"; +import { + KNOWLEDGE_MEMORY_CONTRACT, + type KnowledgeMemoryContract, + type SavedPriorSqlCaptureResult +} from "../../../knowledge/contracts/knowledge-memory.contract"; const VIEW_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/; +export interface SaveViewFromRunSavedPriorSqlDiagnostics + extends SavedPriorSqlCaptureResult { + message?: string; +} + export interface SaveViewFromRunInput { runId: string; name: string; @@ -25,6 +35,7 @@ export interface SaveViewFromRunResult { activeRevision?: number; draftRevision: number; view: ModelingGraphView; + savedPriorSql?: SaveViewFromRunSavedPriorSqlDiagnostics; } @Injectable() @@ -32,7 +43,10 @@ export class SaveViewFromRunUsecase { constructor( private readonly chatRepository: ChatRepository, private readonly modelingGraphRepository: ModelingGraphRepository, - private readonly modelingGraphValidator: ModelingGraphValidator + private readonly modelingGraphValidator: ModelingGraphValidator, + @Optional() + @Inject(KNOWLEDGE_MEMORY_CONTRACT) + private readonly knowledgeMemoryContract?: KnowledgeMemoryContract ) {} async execute(input: SaveViewFromRunInput): Promise { @@ -79,6 +93,20 @@ export class SaveViewFromRunUsecase { const viewId = this.buildViewId(runId); const existingById = basePayload.views.find((item) => item.id === viewId); if (existingById) { + const savedPriorSql = await this.captureSavedPriorSql({ + workspaceId, + datasourceId, + run: { + runId: run.runId, + status: run.status, + createdAt: run.createdAt, + question: run.question, + sql, + columns: run.columns + }, + view: existingById, + replayed: true + }); return { stage: "chat_run_view_saved", workspaceId, @@ -87,7 +115,8 @@ export class SaveViewFromRunUsecase { replayed: true, activeRevision: current.activeRevision, draftRevision: current.draft?.revision ?? 0, - view: existingById + view: existingById, + savedPriorSql }; } @@ -135,6 +164,20 @@ export class SaveViewFromRunUsecase { graphPayload: nextPayload, actorId: this.normalizeOptional(input.actorId) ?? undefined }); + const savedPriorSql = await this.captureSavedPriorSql({ + workspaceId, + datasourceId, + run: { + runId: run.runId, + status: run.status, + createdAt: run.createdAt, + question: run.question, + sql, + columns: run.columns + }, + view: normalizedView, + replayed: false + }); return { stage: "chat_run_view_saved", @@ -144,8 +187,80 @@ export class SaveViewFromRunUsecase { replayed: false, activeRevision: current.activeRevision, draftRevision: nextRevision.revision, - view: normalizedView + view: normalizedView, + savedPriorSql + }; + } + + private async captureSavedPriorSql(input: { + workspaceId: string; + datasourceId: string; + run: { + runId: string; + status: string; + createdAt: string; + question?: string; + sql: string; + columns?: string[]; }; + view: Pick; + replayed: boolean; + }): Promise { + const service = this.knowledgeMemoryContract?.savedPriorSql; + if (!service) { + return undefined; + } + try { + return await service.captureFromSavedView({ + workspaceId: input.workspaceId, + datasourceId: input.datasourceId, + sourceRunId: input.run.runId, + sourceRunStatus: input.run.status, + sourceRunCreatedAt: input.run.createdAt, + question: this.normalizeOptional(input.run.question) ?? undefined, + sql: input.run.sql, + viewId: input.view.id, + viewName: input.view.name, + viewSql: input.view.sql, + tableNames: [], + columnNames: Array.isArray(input.run.columns) + ? input.run.columns.filter( + (column): column is string => + typeof column === "string" && column.trim().length > 0 + ) + : [], + replayed: input.replayed, + savedAt: new Date().toISOString() + }); + } catch (error) { + return { + outcome: "capture_failed", + priorId: this.safeBuildPriorId(input), + replayKey: "saved_prior_sql:capture_failed", + reason: "saved_prior_sql_capture_exception", + message: error instanceof Error ? error.message : String(error) + }; + } + } + + private safeBuildPriorId(input: { + workspaceId: string; + datasourceId: string; + view: Pick; + }): string { + const service = this.knowledgeMemoryContract?.savedPriorSql; + if (!service) { + return "saved_prior_sql.unavailable"; + } + try { + return service.buildPriorId({ + workspaceId: input.workspaceId, + datasourceId: input.datasourceId, + viewId: input.view.id + }); + } catch { + return "saved_prior_sql.unresolved"; + } } private createEmptyPayload(): ModelingGraphPayload { diff --git a/apps/backend/src/modules/conversation/delivery/delivery-contract.mapper.ts b/apps/backend/src/modules/conversation/delivery/delivery-contract.mapper.ts index 2159863..506d647 100644 --- a/apps/backend/src/modules/conversation/delivery/delivery-contract.mapper.ts +++ b/apps/backend/src/modules/conversation/delivery/delivery-contract.mapper.ts @@ -109,6 +109,17 @@ interface SqlCoverageEvidenceSnapshot { triggerSource: "selected_context" | "semantic_context" | "explicit_pinning" | "none"; } +interface SavedPriorSqlEvidenceSnapshot { + status: "hit" | "miss" | "filtered" | "stale" | "ambiguous"; + shortcutUsed: boolean; + reasonCodes?: string[]; + selectedChunkId?: string; + selectedViewId?: string; + selectedViewName?: string; + selectedSourceRunId?: string; + safetyResult?: "passed" | "rejected" | "fallback_generated"; +} + type ClarificationDecisionLayer = NonNullable< NonNullable["clarificationDecision"] >; @@ -117,6 +128,7 @@ type DeliveryEvidenceWithContext = NonNullable & { effectiveContextSummary?: EffectiveContextSummary; conflictHint?: ContextConflictHint; sqlCoverage?: SqlCoverageEvidenceSnapshot; + savedPriorSql?: SavedPriorSqlEvidenceSnapshot; }; interface SandboxPostProcessOutcome { @@ -139,6 +151,7 @@ export class DeliveryContractMapper { const traceContextEvidence = this.readTraceContextEvidence(input.run); const clarificationDecision = this.readClarificationDecisionEvidence(input.run); const sqlCoverage = this.readSqlCoverageEvidence(input.run); + const savedPriorSql = this.readSavedPriorSqlEvidence(input.run); const invalidInput = replayIndex.invalidPayload; const artifact = this.buildArtifact(input.run); const sandboxOutcome = this.applySandboxPostProcess({ @@ -154,6 +167,9 @@ export class DeliveryContractMapper { ? ["context_conflict_detected"] : []), ...(sqlCoverage?.gateStatus === "failed" ? ["sql_coverage_gate_failed"] : []), + ...(savedPriorSql?.safetyResult === "rejected" + ? ["saved_prior_sql_safety_rejected"] + : []), ...sandboxOutcome.riskTags ]); @@ -211,7 +227,8 @@ export class DeliveryContractMapper { effectiveContextSummary: traceContextEvidence.effectiveContextSummary, conflictHint: traceContextEvidence.conflictHint, clarificationDecision, - sqlCoverage + sqlCoverage, + savedPriorSql }; return { @@ -668,6 +685,76 @@ export class DeliveryContractMapper { return undefined; } + private readSavedPriorSqlEvidence( + run: SqlRun + ): SavedPriorSqlEvidenceSnapshot | undefined { + const steps = run.trace.steps ?? []; + if (steps.length === 0) { + return undefined; + } + const resolutionStepIndex = [...steps] + .map((step, index) => ({ step, index })) + .reverse() + .find((entry) => entry.step.node === "resolve-saved-prior-sql"); + if (!resolutionStepIndex) { + return undefined; + } + const output = this.parseSummaryObject(resolutionStepIndex.step.outputSummary); + const statusRaw = this.readString(output?.status); + const status = + statusRaw === "hit" || + statusRaw === "miss" || + statusRaw === "filtered" || + statusRaw === "stale" || + statusRaw === "ambiguous" + ? statusRaw + : undefined; + if (!status) { + return undefined; + } + + const reasonCodes = this.readStringArray( + output?.reasonCodes ?? output?.reason_codes + ); + const selectedChunkId = this.readString( + output?.selectedChunkId ?? output?.selected_chunk_id + ); + const selectedViewId = this.readString( + output?.selectedViewId ?? output?.selected_view_id + ); + const selectedSourceRunId = this.readString( + output?.selectedSourceRunId ?? output?.selected_source_run_id + ); + + const subsequentSteps = steps.slice(resolutionStepIndex.index + 1); + const safetyStep = subsequentSteps.find((step) => step.node === "safety-check"); + const hasFallbackGeneration = subsequentSteps.some( + (step) => step.node === "generate-sql" + ); + const safetyResult: SavedPriorSqlEvidenceSnapshot["safetyResult"] | undefined = + status !== "hit" + ? undefined + : safetyStep?.status === "success" + ? "passed" + : safetyStep?.status === "failed" + ? hasFallbackGeneration + ? "fallback_generated" + : "rejected" + : undefined; + + const shortcutUsed = status === "hit" && safetyResult === "passed"; + + return { + status, + shortcutUsed, + ...(reasonCodes.length > 0 ? { reasonCodes } : {}), + ...(selectedChunkId ? { selectedChunkId } : {}), + ...(selectedViewId ? { selectedViewId } : {}), + ...(selectedSourceRunId ? { selectedSourceRunId } : {}), + ...(safetyResult ? { safetyResult } : {}) + }; + } + private readSqlCoverageFromRecord( value: unknown ): SqlCoverageEvidenceSnapshot | undefined { diff --git a/apps/backend/src/modules/knowledge/contracts/knowledge-memory.contract.ts b/apps/backend/src/modules/knowledge/contracts/knowledge-memory.contract.ts index 4d2fb30..899b530 100644 --- a/apps/backend/src/modules/knowledge/contracts/knowledge-memory.contract.ts +++ b/apps/backend/src/modules/knowledge/contracts/knowledge-memory.contract.ts @@ -1,7 +1,60 @@ import type { MemoryPromotionService } from "../memory/memory-promotion.service"; +import type { SavedPriorSqlService } from "../memory/saved-prior-sql.service"; export const KNOWLEDGE_MEMORY_CONTRACT = Symbol("KNOWLEDGE_MEMORY_CONTRACT"); +export type SavedPriorSqlCaptureOutcome = + | "captured" + | "duplicate" + | "skipped_ineligible" + | "capture_failed"; + +export interface SavedPriorSqlCaptureInput { + workspaceId: string; + datasourceId: string; + sourceRunId: string; + sourceRunStatus: string; + sourceRunCreatedAt?: string; + question?: string; + sql: string; + viewId: string; + viewName: string; + viewSql: string; + tableNames?: string[]; + columnNames?: string[]; + replayed: boolean; + savedAt?: string; +} + +export interface SavedPriorSqlRecord { + priorId: string; + workspaceId: string; + datasourceId: string; + sourceRunId: string; + sourceRunStatus: string; + sourceRunCreatedAt?: string; + viewId: string; + viewName: string; + question: string; + sql: string; + tableNames: string[]; + columnNames: string[]; + savedAt: string; + metadata: { + trusted: true; + verified: true; + priorSql: true; + }; +} + +export interface SavedPriorSqlCaptureResult { + outcome: SavedPriorSqlCaptureOutcome; + priorId: string; + replayKey: string; + reason?: string; + record?: SavedPriorSqlRecord; +} + export interface KnowledgeMemoryContract { promotion: Pick< MemoryPromotionService, @@ -11,4 +64,8 @@ export interface KnowledgeMemoryContract { | "buildCandidateIdForRun" | "applyFeedback" >; + savedPriorSql: Pick< + SavedPriorSqlService, + "captureFromSavedView" | "getRecord" | "listRecords" | "buildPriorId" + >; } diff --git a/apps/backend/src/modules/knowledge/knowledge-chat-support.facade.ts b/apps/backend/src/modules/knowledge/knowledge-chat-support.facade.ts index 8a96b53..a12b518 100644 --- a/apps/backend/src/modules/knowledge/knowledge-chat-support.facade.ts +++ b/apps/backend/src/modules/knowledge/knowledge-chat-support.facade.ts @@ -13,6 +13,7 @@ import { type KnowledgeSemanticSpineContract } from "./contracts/knowledge-semantic-registry.contract"; import { MemoryPromotionService } from "./memory/memory-promotion.service"; +import { SavedPriorSqlService } from "./memory/saved-prior-sql.service"; import { RagReplayRepository } from "./rag/observability/rag-replay.repository"; import { RagRetrievalService } from "./rag/retrieval/rag-retrieval.service"; import { RagRerankService } from "./rag/rerank/rag-rerank.service"; @@ -28,6 +29,7 @@ export class KnowledgeChatSupportFacade implements KnowledgeFacadeContract { constructor( readonly memoryPromotionService: MemoryPromotionService, + readonly savedPriorSqlService: SavedPriorSqlService, readonly ragReplayRepository: RagReplayRepository, readonly ragRetrievalService: RagRetrievalService, readonly ragRerankService: RagRerankService, @@ -63,7 +65,8 @@ export class KnowledgeChatSupportFacade implements KnowledgeFacadeContract { semanticSpine: semanticSpineContract }; this.memoryContract = { - promotion: this.memoryPromotionService + promotion: this.memoryPromotionService, + savedPriorSql: this.savedPriorSqlService }; } diff --git a/apps/backend/src/modules/knowledge/memory/memory.module.ts b/apps/backend/src/modules/knowledge/memory/memory.module.ts index f67a72e..50ce2b4 100644 --- a/apps/backend/src/modules/knowledge/memory/memory.module.ts +++ b/apps/backend/src/modules/knowledge/memory/memory.module.ts @@ -6,6 +6,7 @@ import { MemoryController } from "../../memory/memory.controller"; import { MemoryPromotionPolicy } from "../../memory/memory-promotion-policy"; import { MemoryPromotionService as LegacyMemoryPromotionService } from "../../memory/memory-promotion.service"; import { MemoryPromotionService as KnowledgeMemoryPromotionService } from "./memory-promotion.service"; +import { SavedPriorSqlService } from "./saved-prior-sql.service"; export const KNOWLEDGE_MEMORY_COMPAT_BRIDGE = Object.freeze({ capability: "memory", @@ -24,12 +25,14 @@ export const KNOWLEDGE_MEMORY_COMPAT_BRIDGE = Object.freeze({ provide: KnowledgeMemoryPromotionService, useExisting: LegacyMemoryPromotionService }, + SavedPriorSqlService, AdminOnlyGuard ], exports: [ MemoryPromotionPolicy, LegacyMemoryPromotionService, - KnowledgeMemoryPromotionService + KnowledgeMemoryPromotionService, + SavedPriorSqlService ] }) export class MemoryModule {} diff --git a/apps/backend/src/modules/knowledge/memory/saved-prior-sql.service.ts b/apps/backend/src/modules/knowledge/memory/saved-prior-sql.service.ts new file mode 100644 index 0000000..b4fd6ae --- /dev/null +++ b/apps/backend/src/modules/knowledge/memory/saved-prior-sql.service.ts @@ -0,0 +1,442 @@ +import { createHash } from "node:crypto"; +import { Injectable } from "@nestjs/common"; +import { BuildRagIndexJob } from "../../rag/jobs/build-rag-index.job"; +import type { RagChunkBuildInput } from "../../rag/index/rag-index.repository"; +import { RagIndexRepository } from "../../rag/index/rag-index.repository"; +import { RagDocumentFactory } from "../../rag/ingestion/rag-document.factory"; +import { RagDocumentRepository } from "../../rag/ingestion/rag-document.repository"; +import { + type SavedPriorSqlCaptureInput, + type SavedPriorSqlCaptureResult, + type SavedPriorSqlRecord +} from "../contracts/knowledge-memory.contract"; +import { RagReplayRepository } from "../rag/observability/rag-replay.repository"; + +interface NormalizedCaptureInput { + workspaceId: string; + datasourceId: string; + sourceRunId: string; + sourceRunStatus: string; + sourceRunCreatedAt?: string; + question?: string; + sql: string; + viewId: string; + viewName: string; + viewSql: string; + tableNames: string[]; + columnNames: string[]; + replayed: boolean; + savedAt: string; +} + +@Injectable() +export class SavedPriorSqlService { + private readonly records = new Map(); + + constructor( + private readonly ragReplayRepository: RagReplayRepository, + private readonly ragDocumentFactory: RagDocumentFactory, + private readonly ragDocumentRepository: RagDocumentRepository, + private readonly ragIndexRepository: RagIndexRepository, + private readonly buildRagIndexJob: BuildRagIndexJob + ) {} + + async captureFromSavedView( + input: SavedPriorSqlCaptureInput + ): Promise { + const normalized = this.normalizeInput(input); + const priorId = this.buildPriorId({ + workspaceId: normalized.workspaceId, + datasourceId: normalized.datasourceId, + viewId: normalized.viewId + }); + + if (normalized.replayed) { + return this.writeReplayAndReturn({ + outcome: "duplicate", + reason: "save_replayed", + priorId, + input: normalized, + record: this.getRecord(priorId) + }); + } + + if (normalized.sourceRunStatus !== "executionResult") { + return this.writeReplayAndReturn({ + outcome: "skipped_ineligible", + reason: "source_run_not_execution_result", + priorId, + input: normalized + }); + } + + if (!normalized.question) { + return this.writeReplayAndReturn({ + outcome: "skipped_ineligible", + reason: "source_run_question_missing", + priorId, + input: normalized + }); + } + + if (this.records.has(priorId)) { + return this.writeReplayAndReturn({ + outcome: "duplicate", + reason: "prior_already_captured", + priorId, + input: normalized, + record: this.getRecord(priorId) + }); + } + + const record: SavedPriorSqlRecord = { + priorId, + workspaceId: normalized.workspaceId, + datasourceId: normalized.datasourceId, + sourceRunId: normalized.sourceRunId, + sourceRunStatus: normalized.sourceRunStatus, + sourceRunCreatedAt: normalized.sourceRunCreatedAt, + viewId: normalized.viewId, + viewName: normalized.viewName, + question: normalized.question, + sql: normalized.sql, + tableNames: this.mergeUniqueTokens( + normalized.tableNames, + this.extractTableNames(normalized.sql) + ), + columnNames: this.mergeUniqueTokens( + normalized.columnNames, + this.extractColumnNames(normalized.sql) + ), + savedAt: normalized.savedAt, + metadata: { + trusted: true, + verified: true, + priorSql: true + } + }; + + const sourceVersion = this.buildSourceVersion(record); + const contentChecksum = this.buildContentChecksum(record); + const documentBuild = this.ragDocumentFactory.create({ + sourceType: "sql_example", + datasourceId: record.datasourceId, + sourceVersion, + contentChecksum, + sourceRef: `saved_prior_sql:${record.priorId}`, + exampleId: record.priorId, + question: record.question, + sql: record.sql, + tableNames: record.tableNames, + columnNames: record.columnNames, + metadata: { + trusted: true, + verified: true, + priorSql: true, + workspaceId: record.workspaceId, + datasourceId: record.datasourceId, + sourceRunId: record.sourceRunId, + sourceRunStatus: record.sourceRunStatus, + sourceRunCreatedAt: record.sourceRunCreatedAt, + viewId: record.viewId, + viewName: record.viewName, + viewSql: normalized.viewSql, + savedAt: record.savedAt, + viewExists: true, + viewStatus: "active" + } + }); + + await this.ragDocumentRepository.upsertDocumentWithChunks({ + document: documentBuild.document, + chunks: documentBuild.chunks + }); + + const buildChunks = documentBuild.chunks.map((chunk) => + this.toBuildChunkInput(chunk) + ); + this.ragIndexRepository.upsertChunksForDatasource(record.datasourceId, buildChunks); + + const buildResult = await this.buildRagIndexJob.run({ + datasourceId: record.datasourceId, + sourceVersion, + buildReason: "saved_prior_sql_capture", + runId: record.sourceRunId, + chunks: buildChunks + }); + + this.records.set(priorId, record); + + return this.writeReplayAndReturn({ + outcome: "captured", + priorId, + input: normalized, + record, + indexVersionId: buildResult.indexVersionId, + documentId: documentBuild.document.id, + chunkCount: documentBuild.chunks.length + }); + } + + getRecord(priorId: string): SavedPriorSqlRecord | undefined { + const key = priorId.trim(); + if (!key) { + return undefined; + } + const record = this.records.get(key); + if (!record) { + return undefined; + } + return this.cloneRecord(record); + } + + listRecords(): SavedPriorSqlRecord[] { + return Array.from(this.records.values()).map((item) => this.cloneRecord(item)); + } + + buildPriorId(input: { + workspaceId: string; + datasourceId: string; + viewId: string; + }): string { + const workspaceId = this.requireTrimmed(input.workspaceId, "workspaceId"); + const datasourceId = this.requireTrimmed(input.datasourceId, "datasourceId"); + const viewId = this.requireTrimmed(input.viewId, "viewId"); + const digest = createHash("sha256") + .update(`${workspaceId}|${datasourceId}|${viewId}`) + .digest("hex"); + return `saved_prior_sql.${digest}`; + } + + private toBuildChunkInput(chunk: { + id: string; + datasourceId: string; + domain: string; + content: string; + metadata?: string; + }): RagChunkBuildInput { + return { + id: chunk.id, + datasourceId: chunk.datasourceId, + domain: chunk.domain, + content: chunk.content, + metadata: chunk.metadata + }; + } + + private buildSourceVersion(record: SavedPriorSqlRecord): string { + const digest = createHash("sha256") + .update( + [ + record.workspaceId, + record.datasourceId, + record.priorId, + record.viewId, + record.question, + record.sql, + record.tableNames.join(","), + record.columnNames.join(",") + ].join("|") + ) + .digest("hex"); + return `saved_prior_sql:${record.priorId}:${digest.slice(0, 16)}`; + } + + private buildContentChecksum(record: SavedPriorSqlRecord): string { + return createHash("sha256") + .update( + [record.question, record.sql, record.tableNames.join(","), record.columnNames.join(",")].join( + "|" + ) + ) + .digest("hex"); + } + + private async writeReplayAndReturn(input: { + outcome: "captured" | "duplicate" | "skipped_ineligible"; + reason?: string; + priorId: string; + input: NormalizedCaptureInput; + record?: SavedPriorSqlRecord; + indexVersionId?: string; + documentId?: string; + chunkCount?: number; + }): Promise { + const replayKey = this.buildReplayKey(input.outcome, input.priorId); + await this.ragReplayRepository.writeReplay({ + runId: input.input.sourceRunId, + replayKey, + datasourceId: input.input.datasourceId, + stage: "saved_prior_sql_capture", + ...(input.indexVersionId ? { indexVersionId: input.indexVersionId } : {}), + ...(input.documentId ? { documentId: input.documentId } : {}), + payload: { + outcome: input.outcome, + reason: input.reason ?? null, + priorId: input.priorId, + workspaceId: input.input.workspaceId, + datasourceId: input.input.datasourceId, + sourceRunId: input.input.sourceRunId, + sourceRunStatus: input.input.sourceRunStatus, + viewId: input.input.viewId, + viewName: input.input.viewName, + replayedSave: input.input.replayed, + savedAt: input.input.savedAt, + indexVersionId: input.indexVersionId, + documentId: input.documentId, + chunkCount: input.chunkCount ?? 0 + } + }); + return { + outcome: input.outcome, + reason: input.reason, + priorId: input.priorId, + replayKey, + record: input.record ? this.cloneRecord(input.record) : undefined + }; + } + + private buildReplayKey( + outcome: "captured" | "duplicate" | "skipped_ineligible", + priorId: string + ): string { + return `saved_prior_sql:${outcome}:${priorId}`; + } + + private normalizeInput(input: SavedPriorSqlCaptureInput): NormalizedCaptureInput { + const workspaceId = this.requireTrimmed(input.workspaceId, "workspaceId"); + const datasourceId = this.requireTrimmed(input.datasourceId, "datasourceId"); + const sourceRunId = this.requireTrimmed(input.sourceRunId, "sourceRunId"); + const sourceRunStatus = this.requireTrimmed( + input.sourceRunStatus, + "sourceRunStatus" + ); + const sql = this.requireTrimmed(input.sql, "sql"); + const viewId = this.requireTrimmed(input.viewId, "viewId"); + const viewName = this.requireTrimmed(input.viewName, "viewName"); + const viewSql = this.requireTrimmed(input.viewSql, "viewSql"); + const sourceRunCreatedAt = this.normalizeOptional(input.sourceRunCreatedAt); + const question = this.normalizeOptional(input.question); + return { + workspaceId, + datasourceId, + sourceRunId, + sourceRunStatus, + sourceRunCreatedAt: sourceRunCreatedAt ?? undefined, + question: question ?? undefined, + sql, + viewId, + viewName, + viewSql, + tableNames: this.mergeUniqueTokens(input.tableNames ?? []), + columnNames: this.mergeUniqueTokens(input.columnNames ?? []), + replayed: input.replayed === true, + savedAt: this.toIsoTimestamp(input.savedAt) + }; + } + + private cloneRecord(record: SavedPriorSqlRecord): SavedPriorSqlRecord { + return { + ...record, + tableNames: [...record.tableNames], + columnNames: [...record.columnNames], + metadata: { + ...record.metadata + } + }; + } + + private mergeUniqueTokens(...sources: string[][]): string[] { + const dedup = new Set(); + for (const source of sources) { + for (const raw of source) { + const normalized = this.normalizeOptional(raw); + if (!normalized) { + continue; + } + dedup.add(normalized); + } + } + return Array.from(dedup.values()); + } + + private extractTableNames(sql: string): string[] { + const extracted: string[] = []; + const matcher = /\b(?:from|join)\s+([A-Za-z0-9_.`"\[\]-]+)/gi; + let match = matcher.exec(sql); + while (match) { + const value = this.cleanSqlIdentifier(match[1] ?? ""); + if (value) { + extracted.push(value); + } + match = matcher.exec(sql); + } + return this.mergeUniqueTokens(extracted); + } + + private extractColumnNames(sql: string): string[] { + const selectMatch = /^\s*select\s+([\s\S]+?)\s+from\s+/i.exec(sql); + if (!selectMatch?.[1]) { + return []; + } + const projectedColumns = selectMatch[1] + .split(",") + .map((segment) => segment.trim()) + .filter((segment) => segment.length > 0) + .map((segment) => this.normalizeProjectedColumn(segment)) + .filter((segment): segment is string => Boolean(segment)); + return this.mergeUniqueTokens(projectedColumns); + } + + private normalizeProjectedColumn(segment: string): string | null { + if (segment === "*") { + return null; + } + const aliasMatch = /\bas\s+([A-Za-z0-9_`"\[\]-]+)\s*$/i.exec(segment); + if (aliasMatch?.[1]) { + return this.cleanSqlIdentifier(aliasMatch[1]); + } + const parts = segment.split(/\s+/).filter((item) => item.length > 0); + const canonical = parts[parts.length - 1] ?? segment; + if (canonical === "*") { + return null; + } + return this.cleanSqlIdentifier(canonical.split(".").pop() ?? canonical); + } + + private cleanSqlIdentifier(value: string): string | null { + const normalized = value + .replace(/^[`"\[]+/, "") + .replace(/[`"\]]+$/, "") + .trim(); + return normalized.length > 0 ? normalized : null; + } + + private toIsoTimestamp(value?: string): string { + const normalized = this.normalizeOptional(value); + if (!normalized) { + return new Date().toISOString(); + } + const timestamp = Date.parse(normalized); + if (Number.isNaN(timestamp)) { + throw new Error(`saved prior sql savedAt illegal timestamp: ${normalized}`); + } + return new Date(timestamp).toISOString(); + } + + private normalizeOptional(value: string | undefined | null): string | null { + if (typeof value !== "string") { + return null; + } + const normalized = value.trim(); + return normalized.length > 0 ? normalized : null; + } + + private requireTrimmed(value: string, field: string): string { + const normalized = this.normalizeOptional(value); + if (!normalized) { + throw new Error(`saved prior sql ${field} is required`); + } + return normalized; + } +} diff --git a/apps/backend/src/modules/knowledge/rag/rag.module.ts b/apps/backend/src/modules/knowledge/rag/rag.module.ts index 52c0641..fa4136c 100644 --- a/apps/backend/src/modules/knowledge/rag/rag.module.ts +++ b/apps/backend/src/modules/knowledge/rag/rag.module.ts @@ -11,6 +11,10 @@ import { RagAuditReplayService } from "../../rag/audit/rag-audit-replay.service" import { RagEventConsumerService } from "../../rag/events/rag-event-consumer.service"; import { RagIndexBuilderService } from "../../rag/index/rag-index-builder.service"; import { RagIndexRepository } from "../../rag/index/rag-index.repository"; +import { RagDocumentFactory } from "../../rag/ingestion/rag-document.factory"; +import { RagDocumentRepository } from "../../rag/ingestion/rag-document.repository"; +import { IngestionSourceAdapter } from "../../rag/ingestion/ingestion-source.adapter"; +import { RagChunkingService } from "../../rag/ingestion/rag-chunking.service"; import { BuildRagIndexJob } from "../../rag/jobs/build-rag-index.job"; import { RagReplayRepository as LegacyRagReplayRepository } from "../../rag/observability/rag-replay.repository"; import { RagReplayRepository } from "./observability/rag-replay.repository"; @@ -74,6 +78,10 @@ export function assertKnowledgeCompatBridgeRetirementReady( controllers: [RagQualityController], providers: [ RagIndexRepository, + IngestionSourceAdapter, + RagChunkingService, + RagDocumentFactory, + RagDocumentRepository, RagIndexBuilderService, BuildRagIndexJob, RagDatasourceQuotaPolicy, @@ -106,6 +114,10 @@ export function assertKnowledgeCompatBridgeRetirementReady( ], exports: [ RagIndexRepository, + IngestionSourceAdapter, + RagChunkingService, + RagDocumentFactory, + RagDocumentRepository, RagIndexBuilderService, BuildRagIndexJob, RagDatasourceQuotaPolicy, diff --git a/apps/backend/src/modules/knowledge/rag/retrieval/rag-retrieval.types.ts b/apps/backend/src/modules/knowledge/rag/retrieval/rag-retrieval.types.ts index 372c7de..7ebfd2f 100644 --- a/apps/backend/src/modules/knowledge/rag/retrieval/rag-retrieval.types.ts +++ b/apps/backend/src/modules/knowledge/rag/retrieval/rag-retrieval.types.ts @@ -142,15 +142,45 @@ export interface RagContextPack { } export interface RagPriorSqlLaneEvidence { - status: "hit" | "miss" | "filtered"; + status: "hit" | "miss" | "filtered" | "stale" | "ambiguous"; matched_count: number; selected_count: number; filtered_count: number; + stale_count?: number; + ambiguous_count?: number; + eligible_count?: number; degrade_reasons?: string[]; + shortcut?: RagPriorSqlShortcutDecision; matchedCount?: number; selectedCount?: number; filteredCount?: number; + staleCount?: number; + ambiguousCount?: number; + eligibleCount?: number; degradeReasons?: string[]; + shortcutDecision?: RagPriorSqlShortcutDecision; +} + +export interface RagPriorSqlShortcutDecision { + status: "hit" | "miss" | "filtered" | "stale" | "ambiguous"; + reason_codes: string[]; + matched_count: number; + eligible_count: number; + filtered_count: number; + stale_count: number; + ambiguous_count: number; + selected_chunk_id?: string; + selected_view_id?: string; + selected_source_run_id?: string; + reasonCodes?: string[]; + matchedCount?: number; + eligibleCount?: number; + filteredCount?: number; + staleCount?: number; + ambiguousCount?: number; + selectedChunkId?: string; + selectedViewId?: string; + selectedSourceRunId?: string; } export interface RagRetrievalBundle { diff --git a/apps/backend/src/modules/rag/index/rag-index.repository.ts b/apps/backend/src/modules/rag/index/rag-index.repository.ts index 07d68e0..4517ed4 100644 --- a/apps/backend/src/modules/rag/index/rag-index.repository.ts +++ b/apps/backend/src/modules/rag/index/rag-index.repository.ts @@ -183,6 +183,25 @@ export class RagIndexRepository implements OnModuleInit, OnModuleDestroy { ); } + upsertChunksForDatasource(datasourceId: string, chunks: RagChunkBuildInput[]): void { + if (chunks.length === 0) { + return; + } + const existing = this.chunksByDatasource.get(datasourceId) ?? []; + const merged = new Map(); + for (const chunk of existing) { + merged.set(chunk.id, { + ...chunk + }); + } + for (const chunk of chunks) { + merged.set(chunk.id, { + ...chunk + }); + } + this.chunksByDatasource.set(datasourceId, [...merged.values()]); + } + async listChunksForBuild(datasourceId: string): Promise { const memory = this.chunksByDatasource.get(datasourceId) ?? []; if (!this.isPrimaryPersistenceConfigured() || !this.prisma) { diff --git a/apps/backend/src/modules/rag/ingestion/rag-document.repository.ts b/apps/backend/src/modules/rag/ingestion/rag-document.repository.ts new file mode 100644 index 0000000..eefa23e --- /dev/null +++ b/apps/backend/src/modules/rag/ingestion/rag-document.repository.ts @@ -0,0 +1,212 @@ +import { Injectable, Logger, OnModuleDestroy, OnModuleInit } from "@nestjs/common"; +import { AppConfigService } from "../../config/app-config.service"; +import type { + RagChunkRecordDraft, + RagDocumentDraft +} from "./rag-document.factory"; + +type PrismaClientLike = { + ragDocument: { + upsert: (args: Record) => Promise; + }; + ragChunk: { + upsert: (args: Record) => Promise; + }; + $transaction: (fn: (tx: PrismaTransactionClientLike) => Promise) => Promise; + $disconnect: () => Promise; +}; + +type PrismaTransactionClientLike = Omit; + +interface RagDocumentMemoryState { + documentsById: Map; + chunksById: Map; +} + +export interface RagDocumentUpsertInput { + document: RagDocumentDraft; + chunks: RagChunkRecordDraft[]; +} + +export interface RagDocumentUpsertResult { + documentId: string; + insertedDocument: boolean; + insertedChunkCount: number; +} + +@Injectable() +export class RagDocumentRepository implements OnModuleInit, OnModuleDestroy { + private readonly logger = new Logger(RagDocumentRepository.name); + private prisma?: PrismaClientLike; + private readonly memoryByDatasource = new Map(); + + constructor(private readonly appConfig: AppConfigService) {} + + async onModuleInit(): Promise { + if (!this.isPrimaryPersistenceConfigured()) { + return; + } + try { + const prismaClientModulePath = "../../../generated/prisma/client"; + const prismaModule = (await import(prismaClientModulePath)) as unknown as { + PrismaClient?: new (...args: unknown[]) => PrismaClientLike; + default?: { + PrismaClient?: new (...args: unknown[]) => PrismaClientLike; + }; + }; + const adapterModule = (await import("@prisma/adapter-pg")) as unknown as { + PrismaPg?: new (...args: unknown[]) => unknown; + default?: { + PrismaPg?: new (...args: unknown[]) => unknown; + }; + }; + const PrismaCtor = prismaModule.PrismaClient ?? prismaModule.default?.PrismaClient; + const PrismaPgCtor = adapterModule.PrismaPg ?? adapterModule.default?.PrismaPg; + if (!PrismaCtor || !PrismaPgCtor) { + throw new Error("Prisma client or pg adapter unavailable"); + } + const adapter = new PrismaPgCtor({ + connectionString: this.appConfig.databaseUrl + }); + this.prisma = new PrismaCtor({ adapter }) as PrismaClientLike; + this.logger.log("RAG Document 仓储已启用 PostgreSQL 持久化。"); + } catch (error) { + this.logger.warn( + `RAG Document 仓储初始化失败,降级为内存模式: ${ + error instanceof Error ? error.message : String(error) + }` + ); + } + } + + async onModuleDestroy(): Promise { + if (this.prisma) { + await this.prisma.$disconnect(); + } + } + + async upsertDocumentWithChunks( + input: RagDocumentUpsertInput + ): Promise { + const datasourceState = this.ensureMemoryState(input.document.datasourceId); + const insertedDocument = !datasourceState.documentsById.has(input.document.id); + datasourceState.documentsById.set(input.document.id, { ...input.document }); + let insertedChunkCount = 0; + for (const chunk of input.chunks) { + if (!datasourceState.chunksById.has(chunk.id)) { + insertedChunkCount += 1; + } + datasourceState.chunksById.set(chunk.id, { ...chunk }); + } + + if (!this.isPrimaryPersistenceConfigured() || !this.prisma) { + return { + documentId: input.document.id, + insertedDocument, + insertedChunkCount + }; + } + + await this.prisma.$transaction(async (tx) => { + await tx.ragDocument.upsert({ + where: { id: input.document.id }, + create: this.toPrismaDocument(input.document), + update: this.toPrismaDocumentUpdate(input.document) + }); + for (const chunk of input.chunks) { + await tx.ragChunk.upsert({ + where: { id: chunk.id }, + create: this.toPrismaChunk(chunk), + update: this.toPrismaChunkUpdate(chunk) + }); + } + }); + + return { + documentId: input.document.id, + insertedDocument, + insertedChunkCount + }; + } + + private ensureMemoryState(datasourceId: string): RagDocumentMemoryState { + const normalized = datasourceId.trim(); + const existing = this.memoryByDatasource.get(normalized); + if (existing) { + return existing; + } + const created: RagDocumentMemoryState = { + documentsById: new Map(), + chunksById: new Map() + }; + this.memoryByDatasource.set(normalized, created); + return created; + } + + private toPrismaDocument(input: RagDocumentDraft): Record { + return { + id: input.id, + datasourceId: input.datasourceId, + domain: input.domain, + sourceType: input.sourceType, + sourceRef: input.sourceRef ?? null, + sourceVersion: input.sourceVersion, + contentChecksum: input.contentChecksum, + title: input.title ?? null, + content: input.content, + tableNames: input.tableNames, + columnNames: input.columnNames, + metadata: input.metadata ?? null + }; + } + + private toPrismaDocumentUpdate(input: RagDocumentDraft): Record { + return { + domain: input.domain, + sourceType: input.sourceType, + sourceRef: input.sourceRef ?? null, + sourceVersion: input.sourceVersion, + contentChecksum: input.contentChecksum, + title: input.title ?? null, + content: input.content, + tableNames: input.tableNames, + columnNames: input.columnNames, + metadata: input.metadata ?? null + }; + } + + private toPrismaChunk(input: RagChunkRecordDraft): Record { + return { + id: input.id, + documentId: input.documentId, + datasourceId: input.datasourceId, + domain: input.domain, + chunkProfile: input.chunkProfile, + chunkOrder: input.chunkOrder, + content: input.content, + contentChecksum: input.contentChecksum, + tableNames: input.tableNames, + columnNames: input.columnNames, + metadata: input.metadata ?? null + }; + } + + private toPrismaChunkUpdate(input: RagChunkRecordDraft): Record { + return { + documentId: input.documentId, + datasourceId: input.datasourceId, + domain: input.domain, + chunkProfile: input.chunkProfile, + chunkOrder: input.chunkOrder, + content: input.content, + contentChecksum: input.contentChecksum, + tableNames: input.tableNames, + columnNames: input.columnNames, + metadata: input.metadata ?? null + }; + } + + private isPrimaryPersistenceConfigured(): boolean { + return this.appConfig.databaseUrl.trim().length > 0; + } +} diff --git a/apps/backend/src/modules/rag/quality/rag-quality.service.ts b/apps/backend/src/modules/rag/quality/rag-quality.service.ts index 989dbd0..bf3b05a 100644 --- a/apps/backend/src/modules/rag/quality/rag-quality.service.ts +++ b/apps/backend/src/modules/rag/quality/rag-quality.service.ts @@ -26,8 +26,13 @@ export interface RagQualityEvaluationInput { export interface RagPriorSqlLaneMetricsInput { totalCount: number; hitCount: number; + missCount?: number; filteredCount?: number; staleCount?: number; + ambiguousCount?: number; + duplicateCount?: number; + safetyRejectedCount?: number; + fallbackToGenerationCount?: number; } interface RagQualityEvaluationRecord extends RagQualityEvaluationInput { @@ -38,8 +43,13 @@ interface RagQualityEvaluationRecord extends RagQualityEvaluationInput { interface RagPriorSqlLaneMetricsRecord { totalCount: number; hitCount: number; + missCount: number; filteredCount: number; staleCount: number; + ambiguousCount: number; + duplicateCount: number; + safetyRejectedCount: number; + fallbackToGenerationCount: number; } export interface RagDatasourceOrchestrationSample { @@ -145,8 +155,13 @@ export interface RagQualityGateReport { export interface RagPriorSqlLaneGateReport { sampleSize: number; priorSqlHitRate: number; + priorSqlMissCount: number; priorSqlFilteredCount: number; priorSqlStaleRate: number; + priorSqlAmbiguousCount: number; + priorSqlDuplicateCount: number; + priorSqlSafetyRejectedCount: number; + priorSqlFallbackToGenerationCount: number; } interface GlossarySelectedContextSampleRow { @@ -639,11 +654,27 @@ export class RagQualityService { const hitCount = Math.min(totalCount, this.normalizeCount(input.hitCount)); const filteredCount = this.normalizeCount(input.filteredCount); const staleCount = Math.min(totalCount, this.normalizeCount(input.staleCount)); + const ambiguousCount = this.normalizeCount(input.ambiguousCount); + const duplicateCount = this.normalizeCount(input.duplicateCount); + const safetyRejectedCount = this.normalizeCount(input.safetyRejectedCount); + const fallbackToGenerationCount = this.normalizeCount( + input.fallbackToGenerationCount + ); + const missCount = Math.max( + 0, + this.normalizeCount(input.missCount) || + totalCount - hitCount - filteredCount - staleCount - ambiguousCount + ); return { totalCount, hitCount, + missCount, filteredCount, - staleCount + staleCount, + ambiguousCount, + duplicateCount, + safetyRejectedCount, + fallbackToGenerationCount }; } @@ -690,15 +721,31 @@ export class RagQualityService { return { totalCount: accumulator.totalCount + record.priorSqlLane.totalCount, hitCount: accumulator.hitCount + record.priorSqlLane.hitCount, + missCount: accumulator.missCount + record.priorSqlLane.missCount, filteredCount: accumulator.filteredCount + record.priorSqlLane.filteredCount, - staleCount: accumulator.staleCount + record.priorSqlLane.staleCount + staleCount: accumulator.staleCount + record.priorSqlLane.staleCount, + ambiguousCount: + accumulator.ambiguousCount + record.priorSqlLane.ambiguousCount, + duplicateCount: + accumulator.duplicateCount + record.priorSqlLane.duplicateCount, + safetyRejectedCount: + accumulator.safetyRejectedCount + + record.priorSqlLane.safetyRejectedCount, + fallbackToGenerationCount: + accumulator.fallbackToGenerationCount + + record.priorSqlLane.fallbackToGenerationCount }; }, { totalCount: 0, hitCount: 0, + missCount: 0, filteredCount: 0, - staleCount: 0 + staleCount: 0, + ambiguousCount: 0, + duplicateCount: 0, + safetyRejectedCount: 0, + fallbackToGenerationCount: 0 } ); return { @@ -707,11 +754,16 @@ export class RagQualityService { summary.totalCount === 0 ? 0 : Number((summary.hitCount / summary.totalCount).toFixed(6)), + priorSqlMissCount: summary.missCount, priorSqlFilteredCount: summary.filteredCount, priorSqlStaleRate: summary.totalCount === 0 ? 0 - : Number((summary.staleCount / summary.totalCount).toFixed(6)) + : Number((summary.staleCount / summary.totalCount).toFixed(6)), + priorSqlAmbiguousCount: summary.ambiguousCount, + priorSqlDuplicateCount: summary.duplicateCount, + priorSqlSafetyRejectedCount: summary.safetyRejectedCount, + priorSqlFallbackToGenerationCount: summary.fallbackToGenerationCount }; } diff --git a/apps/backend/src/modules/rag/retrieval/rag-retrieval.service.ts b/apps/backend/src/modules/rag/retrieval/rag-retrieval.service.ts index 4aa5119..4a2ba82 100644 --- a/apps/backend/src/modules/rag/retrieval/rag-retrieval.service.ts +++ b/apps/backend/src/modules/rag/retrieval/rag-retrieval.service.ts @@ -16,6 +16,7 @@ import { fuseWithRrf } from "./fusion/rrf-fusion"; import { RAG_RETRIEVAL_LANES, type RagPriorSqlLaneEvidence, + type RagPriorSqlShortcutDecision, type RagRetrievalCandidate, type RagRetrievalChunkMetadata, type RagRetrievalChunkPayload, @@ -248,11 +249,16 @@ export class RagRetrievalService { graph: laneResults.graph.hits }; const fused = fuseWithRrf({ laneHits }); + const activeModelingRevision = await this.resolveActiveModelingRevision( + workspaceId, + datasourceId + ); const priorSqlSelection = this.selectTrustedPriorSqlCandidates({ candidates: fused, datasourceId, workspaceId, - allowedTables + allowedTables, + activeModelingRevision }); const priorSqlFiltered = this.filterBlockedPriorSqlCandidates( fused, @@ -713,15 +719,18 @@ export class RagRetrievalService { datasourceId: string; workspaceId?: string; allowedTables: string[]; + activeModelingRevision?: number; }): PriorSqlSelectionResult { const trustedSqlExampleCandidates = input.candidates.filter( (candidate) => candidate.chunk.metadata.domain === "sql_example" && this.isTrustedPriorSqlCandidate(candidate) ); - const selectedCandidates: RagRetrievalCandidate[] = []; + const eligibleCandidates: RagRetrievalCandidate[] = []; const blockedChunkIds = new Set(); - const degradeReasons: string[] = []; + const filteredReasons: string[] = []; + const staleReasons: string[] = []; + let staleCandidateCount = 0; const allowedTables = new Set(input.allowedTables); const workspaceId = input.workspaceId?.trim(); @@ -734,47 +743,102 @@ export class RagRetrievalService { }); if (filterReasons.length > 0) { blockedChunkIds.add(candidate.chunk_id); - degradeReasons.push(...filterReasons); + filteredReasons.push(...filterReasons); + continue; + } + + const candidateStaleReasons = this.collectPriorSqlStaleReasons({ + candidate, + activeModelingRevision: input.activeModelingRevision + }); + if (candidateStaleReasons.length > 0) { + staleCandidateCount += 1; + staleReasons.push(...candidateStaleReasons); continue; } - selectedCandidates.push({ + eligibleCandidates.push({ ...candidate, evidence: this.unique([...candidate.evidence, "prior_sql:trusted"]) }); } - if (selectedCandidates.length > 0) { - const lane: RagPriorSqlLaneEvidence = { - status: "hit", + const selectedCandidates = + eligibleCandidates.length === 1 ? [eligibleCandidates[0]] : []; + const shortcutStatus: RagPriorSqlShortcutDecision["status"] = + selectedCandidates.length === 1 + ? "hit" + : eligibleCandidates.length > 1 + ? "ambiguous" + : blockedChunkIds.size > 0 + ? "filtered" + : staleCandidateCount > 0 + ? "stale" + : "miss"; + const selectedCandidate = selectedCandidates[0]; + const shortcutReasons = this.unique([ + ...(shortcutStatus === "hit" + ? ["prior_sql_shortcut_hit"] + : []), + ...(shortcutStatus === "miss" + ? ["prior_sql_no_trusted_match"] + : []), + ...(shortcutStatus === "filtered" + ? ["prior_sql_shortcut_filtered", ...filteredReasons] + : []), + ...(shortcutStatus === "stale" + ? ["prior_sql_shortcut_stale", ...staleReasons] + : []), + ...(shortcutStatus === "ambiguous" + ? ["prior_sql_shortcut_ambiguous"] + : []) + ]); + const laneDegradeReasons = this.unique([ + ...filteredReasons, + ...staleReasons, + ...(shortcutStatus === "miss" ? ["prior_sql_no_trusted_match"] : []), + ...(shortcutStatus === "ambiguous" ? ["prior_sql_shortcut_ambiguous"] : []), + ...(shortcutStatus === "filtered" ? ["prior_sql_shortcut_filtered"] : []), + ...(shortcutStatus === "stale" ? ["prior_sql_shortcut_stale"] : []) + ]); + const shortcutDecision: RagPriorSqlShortcutDecision = + this.withPriorSqlShortcutCompatFields({ + status: shortcutStatus, + reason_codes: shortcutReasons, matched_count: trustedSqlExampleCandidates.length, - selected_count: selectedCandidates.length, + eligible_count: eligibleCandidates.length, filtered_count: blockedChunkIds.size, - ...(degradeReasons.length > 0 + stale_count: staleCandidateCount, + ambiguous_count: shortcutStatus === "ambiguous" ? eligibleCandidates.length : 0, + ...(selectedCandidate ? { - degrade_reasons: this.unique(degradeReasons) + selected_chunk_id: selectedCandidate.chunk_id, + selected_view_id: this.readPriorSqlViewId( + selectedCandidate.chunk.metadata.sourceMetadata + ), + selected_source_run_id: this.readPriorSqlSourceRunId( + selectedCandidate.chunk.metadata.sourceMetadata + ) } : {}) - }; - return { - lane: this.withPriorSqlLaneCompatFields(lane), - selectedCandidates, - blockedChunkIds - }; - } - + }); const lane: RagPriorSqlLaneEvidence = { - status: trustedSqlExampleCandidates.length > 0 ? "filtered" : "miss", + status: shortcutStatus, matched_count: trustedSqlExampleCandidates.length, - selected_count: 0, + selected_count: selectedCandidates.length, filtered_count: blockedChunkIds.size, - degrade_reasons: - trustedSqlExampleCandidates.length > 0 - ? this.unique(degradeReasons) - : ["prior_sql_no_trusted_match"] + stale_count: staleCandidateCount, + ambiguous_count: shortcutStatus === "ambiguous" ? eligibleCandidates.length : 0, + eligible_count: eligibleCandidates.length, + ...(laneDegradeReasons.length > 0 + ? { + degrade_reasons: laneDegradeReasons + } + : {}), + shortcut: shortcutDecision }; return { lane: this.withPriorSqlLaneCompatFields(lane), - selectedCandidates: [], + selectedCandidates, blockedChunkIds }; } @@ -836,6 +900,85 @@ export class RagRetrievalService { return reasons; } + private collectPriorSqlStaleReasons(input: { + candidate: RagRetrievalCandidate; + activeModelingRevision?: number; + }): string[] { + const sourceMetadata = input.candidate.chunk.metadata.sourceMetadata; + if (!this.isRecord(sourceMetadata)) { + return []; + } + const reasons: string[] = []; + if ( + this.readBooleanFlag(sourceMetadata.stale) || + this.readBooleanFlag(sourceMetadata.isStale) || + this.readBooleanFlag(sourceMetadata.priorSqlStale) || + this.readBooleanFlag(sourceMetadata.prior_sql_stale) + ) { + reasons.push("prior_sql_stale_marked"); + } + const viewDeleted = + this.readBooleanFlag(sourceMetadata.viewDeleted) || + this.readBooleanFlag(sourceMetadata.view_deleted); + const viewExists = this.readOptionalBoolean( + sourceMetadata.viewExists ?? sourceMetadata.view_exists + ); + if (viewDeleted || viewExists === false) { + reasons.push("prior_sql_stale_view_missing"); + } + const viewStatus = this.readString(sourceMetadata.viewStatus) ?? + this.readString(sourceMetadata.view_status); + if (viewStatus) { + const normalized = viewStatus.trim().toLowerCase(); + if ( + normalized === "missing" || + normalized === "deleted" || + normalized === "not_found" || + normalized === "archived" || + normalized === "inactive" || + normalized === "stale" + ) { + reasons.push("prior_sql_stale_view_status"); + } + } + + const sourceModelingRevision = this.readPositiveInteger( + sourceMetadata.modelingRevision ?? sourceMetadata.modeling_revision + ); + const currentModelingRevision = this.readPositiveInteger( + sourceMetadata.currentModelingRevision ?? sourceMetadata.current_modeling_revision + ); + if ( + sourceModelingRevision !== undefined && + currentModelingRevision !== undefined && + sourceModelingRevision !== currentModelingRevision + ) { + reasons.push("prior_sql_stale_modeling_revision_mismatch"); + } + if ( + sourceModelingRevision !== undefined && + input.activeModelingRevision !== undefined && + sourceModelingRevision !== input.activeModelingRevision + ) { + reasons.push("prior_sql_stale_modeling_revision_mismatch"); + } + + const sourceSchemaRevision = this.readPositiveInteger( + sourceMetadata.schemaRevision ?? sourceMetadata.schema_revision + ); + const currentSchemaRevision = this.readPositiveInteger( + sourceMetadata.currentSchemaRevision ?? sourceMetadata.current_schema_revision + ); + if ( + sourceSchemaRevision !== undefined && + currentSchemaRevision !== undefined && + sourceSchemaRevision !== currentSchemaRevision + ) { + reasons.push("prior_sql_stale_schema_revision_mismatch"); + } + return this.unique(reasons); + } + private readWorkspaceIdFromSourceMetadata(sourceMetadata: unknown): string | undefined { if (!this.isRecord(sourceMetadata)) { return undefined; @@ -843,6 +986,21 @@ export class RagRetrievalService { return this.readString(sourceMetadata.workspaceId) ?? this.readString(sourceMetadata.workspace_id); } + private readPriorSqlViewId(sourceMetadata: unknown): string | undefined { + if (!this.isRecord(sourceMetadata)) { + return undefined; + } + return this.readString(sourceMetadata.viewId) ?? this.readString(sourceMetadata.view_id); + } + + private readPriorSqlSourceRunId(sourceMetadata: unknown): string | undefined { + if (!this.isRecord(sourceMetadata)) { + return undefined; + } + return this.readString(sourceMetadata.sourceRunId) ?? + this.readString(sourceMetadata.source_run_id); + } + private filterBlockedPriorSqlCandidates( candidates: RagRetrievalCandidate[], blockedChunkIds: Set @@ -1476,16 +1634,80 @@ export class RagRetrievalService { lane: RagPriorSqlLaneEvidence ): RagPriorSqlLaneEvidence { const degradeReasons = lane.degrade_reasons ?? lane.degradeReasons; + const shortcut = lane.shortcut ?? lane.shortcutDecision; return { ...lane, matched_count: lane.matched_count, selected_count: lane.selected_count, filtered_count: lane.filtered_count, + ...(lane.stale_count !== undefined ? { stale_count: lane.stale_count } : {}), + ...(lane.ambiguous_count !== undefined ? { ambiguous_count: lane.ambiguous_count } : {}), + ...(lane.eligible_count !== undefined ? { eligible_count: lane.eligible_count } : {}), ...(degradeReasons ? { degrade_reasons: degradeReasons } : {}), matchedCount: lane.matched_count, selectedCount: lane.selected_count, filteredCount: lane.filtered_count, - ...(degradeReasons ? { degradeReasons } : {}) + ...(lane.stale_count !== undefined ? { staleCount: lane.stale_count } : {}), + ...(lane.ambiguous_count !== undefined ? { ambiguousCount: lane.ambiguous_count } : {}), + ...(lane.eligible_count !== undefined ? { eligibleCount: lane.eligible_count } : {}), + ...(degradeReasons ? { degradeReasons } : {}), + ...(shortcut + ? { + shortcut: this.withPriorSqlShortcutCompatFields(shortcut), + shortcutDecision: this.withPriorSqlShortcutCompatFields(shortcut) + } + : {}) + }; + } + + private withPriorSqlShortcutCompatFields( + decision: RagPriorSqlShortcutDecision + ): RagPriorSqlShortcutDecision { + const reasonCodes = decision.reason_codes ?? decision.reasonCodes ?? []; + return { + ...decision, + reason_codes: reasonCodes, + matched_count: decision.matched_count, + eligible_count: decision.eligible_count, + filtered_count: decision.filtered_count, + stale_count: decision.stale_count, + ambiguous_count: decision.ambiguous_count, + ...(decision.selected_chunk_id + ? { + selected_chunk_id: decision.selected_chunk_id + } + : {}), + ...(decision.selected_view_id + ? { + selected_view_id: decision.selected_view_id + } + : {}), + ...(decision.selected_source_run_id + ? { + selected_source_run_id: decision.selected_source_run_id + } + : {}), + reasonCodes, + matchedCount: decision.matched_count, + eligibleCount: decision.eligible_count, + filteredCount: decision.filtered_count, + staleCount: decision.stale_count, + ambiguousCount: decision.ambiguous_count, + ...(decision.selected_chunk_id + ? { + selectedChunkId: decision.selected_chunk_id + } + : {}), + ...(decision.selected_view_id + ? { + selectedViewId: decision.selected_view_id + } + : {}), + ...(decision.selected_source_run_id + ? { + selectedSourceRunId: decision.selected_source_run_id + } + : {}) }; } @@ -1597,6 +1819,34 @@ export class RagRetrievalService { return undefined; } + private readPositiveInteger(value: unknown): number | undefined { + const parsed = this.readNumber(value); + if (parsed === undefined || !Number.isInteger(parsed) || parsed <= 0) { + return undefined; + } + return parsed; + } + + private readOptionalBoolean(value: unknown): boolean | undefined { + if (typeof value === "boolean") { + return value; + } + if (typeof value === "number" && Number.isFinite(value)) { + return value > 0; + } + if (typeof value !== "string") { + return undefined; + } + const normalized = value.trim().toLowerCase(); + if (normalized === "true" || normalized === "1" || normalized === "yes") { + return true; + } + if (normalized === "false" || normalized === "0" || normalized === "no") { + return false; + } + return undefined; + } + private readStringArray(value: unknown): string[] { if (!Array.isArray(value)) { return []; diff --git a/apps/backend/src/modules/rag/retrieval/rag-retrieval.types.ts b/apps/backend/src/modules/rag/retrieval/rag-retrieval.types.ts index 6e5f68b..9a40ce0 100644 --- a/apps/backend/src/modules/rag/retrieval/rag-retrieval.types.ts +++ b/apps/backend/src/modules/rag/retrieval/rag-retrieval.types.ts @@ -142,15 +142,45 @@ export interface RagContextPack { } export interface RagPriorSqlLaneEvidence { - status: "hit" | "miss" | "filtered"; + status: "hit" | "miss" | "filtered" | "stale" | "ambiguous"; matched_count: number; selected_count: number; filtered_count: number; + stale_count?: number; + ambiguous_count?: number; + eligible_count?: number; degrade_reasons?: string[]; + shortcut?: RagPriorSqlShortcutDecision; matchedCount?: number; selectedCount?: number; filteredCount?: number; + staleCount?: number; + ambiguousCount?: number; + eligibleCount?: number; degradeReasons?: string[]; + shortcutDecision?: RagPriorSqlShortcutDecision; +} + +export interface RagPriorSqlShortcutDecision { + status: "hit" | "miss" | "filtered" | "stale" | "ambiguous"; + reason_codes: string[]; + matched_count: number; + eligible_count: number; + filtered_count: number; + stale_count: number; + ambiguous_count: number; + selected_chunk_id?: string; + selected_view_id?: string; + selected_source_run_id?: string; + reasonCodes?: string[]; + matchedCount?: number; + eligibleCount?: number; + filteredCount?: number; + staleCount?: number; + ambiguousCount?: number; + selectedChunkId?: string; + selectedViewId?: string; + selectedSourceRunId?: string; } export interface RagRetrievalBundle { diff --git a/apps/backend/test/integration/agent-saved-prior-sql-shortcut.spec.ts b/apps/backend/test/integration/agent-saved-prior-sql-shortcut.spec.ts new file mode 100644 index 0000000..7b6fef8 --- /dev/null +++ b/apps/backend/test/integration/agent-saved-prior-sql-shortcut.spec.ts @@ -0,0 +1,130 @@ +import { resolve } from "node:path"; +import { Test } from "@nestjs/testing"; +import { AppModule } from "../../src/app.module"; +import { GraphBuilderService } from "../../src/modules/conversation/agent/graph/graph.builder"; +import { SavedPriorSqlService } from "../../src/modules/knowledge/memory/saved-prior-sql.service"; + +describe("agent saved prior sql shortcut integration", () => { + beforeAll(() => { + process.env.SQLITE_PATH = resolve( + __dirname, + "../../../../data/sqlite/text2sql.db" + ); + process.env.DATABASE_URL = ""; + process.env.REDIS_URL = ""; + process.env.LLM_PROVIDER = "volcengine"; + process.env.LLM_MOCK_MODE = "true"; + }); + + it("skips generate-sql when saved prior shortcut hits and safety passes", async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + const graph = moduleRef.get(GraphBuilderService); + const savedPriorSql = moduleRef.get(SavedPriorSqlService); + + await savedPriorSql.captureFromSavedView({ + workspaceId: "ws-agent-shortcut-hit", + datasourceId: "sqlite_main", + sourceRunId: "run-agent-shortcut-source-hit", + sourceRunStatus: "executionResult", + question: "统计订单总数", + sql: "SELECT COUNT(*) AS total_orders FROM orders", + viewId: "view.chat_run.run-agent-shortcut-source-hit", + viewName: "saved_shortcut_select_one", + viewSql: "SELECT COUNT(*) AS total_orders FROM orders", + replayed: false, + savedAt: "2026-04-25T10:00:00.000Z" + }); + + const run = await graph.run({ + runId: "run-agent-shortcut-hit", + sessionId: "session-agent-shortcut-hit", + question: "统计订单总数", + datasourceId: "sqlite_main", + datasourceType: "sqlite", + contextEnvelope: { + metricDefinition: "订单总数", + timeRange: { + from: "2026-01-01", + to: "2026-01-31" + } + }, + accessContext: { + actorId: "user-agent-shortcut-hit", + workspaceId: "ws-agent-shortcut-hit", + allowedTables: ["orders"] + } + }); + + expect(run.status).not.toBe("clarification"); + expect(run.trace.steps.some((step) => step.node === "resolve-saved-prior-sql")).toBe( + true + ); + expect(run.sql?.toLowerCase()).toContain("select"); + const resolveStep = run.trace.steps.find( + (step) => step.node === "resolve-saved-prior-sql" + ); + expect(resolveStep?.status).toMatch(/success|skipped/); + + await moduleRef.close(); + }); + + it("falls back to generate-sql when saved prior shortcut is safety-rejected", async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + const graph = moduleRef.get(GraphBuilderService); + const savedPriorSql = moduleRef.get(SavedPriorSqlService); + + await savedPriorSql.captureFromSavedView({ + workspaceId: "ws-agent-shortcut-fallback", + datasourceId: "sqlite_main", + sourceRunId: "run-agent-shortcut-source-fallback", + sourceRunStatus: "executionResult", + question: "统计订单总数(安全回退测试)", + sql: "DELETE FROM orders WHERE id = 1", + viewId: "view.chat_run.run-agent-shortcut-source-fallback", + viewName: "saved_shortcut_delete_orders", + viewSql: "DELETE FROM orders WHERE id = 1", + replayed: false, + savedAt: "2026-04-25T10:01:00.000Z" + }); + + const run = await graph.run({ + runId: "run-agent-shortcut-fallback", + sessionId: "session-agent-shortcut-fallback", + question: "统计订单总数(安全回退测试)", + datasourceId: "sqlite_main", + datasourceType: "sqlite", + contextEnvelope: { + metricDefinition: "订单总数", + timeRange: { + from: "2026-01-01", + to: "2026-01-31" + } + }, + accessContext: { + actorId: "user-agent-shortcut-fallback", + workspaceId: "ws-agent-shortcut-fallback", + allowedTables: ["orders"] + } + }); + + expect(run.status).not.toBe("clarification"); + expect(run.trace.steps.some((step) => step.node === "resolve-saved-prior-sql")).toBe( + true + ); + const resolveStep = run.trace.steps.find( + (step) => step.node === "resolve-saved-prior-sql" + ); + expect(resolveStep?.status).toMatch(/success|skipped/); + expect( + run.trace.steps.some((step) => step.node === "safety-check") + ).toBe(true); + + await moduleRef.close(); + }); +}); diff --git a/apps/backend/test/integration/rag-quality.spec.ts b/apps/backend/test/integration/rag-quality.spec.ts index b8c152c..14495bb 100644 --- a/apps/backend/test/integration/rag-quality.spec.ts +++ b/apps/backend/test/integration/rag-quality.spec.ts @@ -41,8 +41,13 @@ describe("rag quality gate integration", () => { expect(snapshot.priorSqlLane).toEqual({ sampleSize: 0, priorSqlHitRate: 0, + priorSqlMissCount: 0, priorSqlFilteredCount: 0, - priorSqlStaleRate: 0 + priorSqlStaleRate: 0, + priorSqlAmbiguousCount: 0, + priorSqlDuplicateCount: 0, + priorSqlSafetyRejectedCount: 0, + priorSqlFallbackToGenerationCount: 0 }); await moduleRef.close(); @@ -91,8 +96,13 @@ describe("rag quality gate integration", () => { priorSqlLane: { totalCount: 20, hitCount: 15, + missCount: 2, filteredCount: 2, - staleCount: 1 + staleCount: 1, + ambiguousCount: 1, + duplicateCount: 1, + safetyRejectedCount: 1, + fallbackToGenerationCount: 1 } }); quality.recordEvaluation({ @@ -106,8 +116,13 @@ describe("rag quality gate integration", () => { priorSqlLane: { totalCount: 10, hitCount: 5, + missCount: 1, filteredCount: 1, - staleCount: 2 + staleCount: 2, + ambiguousCount: 1, + duplicateCount: 0, + safetyRejectedCount: 1, + fallbackToGenerationCount: 1 } }); @@ -115,8 +130,13 @@ describe("rag quality gate integration", () => { expect(snapshot.priorSqlLane).toEqual({ sampleSize: 30, priorSqlHitRate: 0.666667, + priorSqlMissCount: 3, priorSqlFilteredCount: 3, - priorSqlStaleRate: 0.1 + priorSqlStaleRate: 0.1, + priorSqlAmbiguousCount: 2, + priorSqlDuplicateCount: 1, + priorSqlSafetyRejectedCount: 2, + priorSqlFallbackToGenerationCount: 2 }); expect(snapshot.gatePass).toBe(true); diff --git a/apps/backend/test/integration/rag-retrieval.service.spec.ts b/apps/backend/test/integration/rag-retrieval.service.spec.ts index be1f29b..5b2cb57 100644 --- a/apps/backend/test/integration/rag-retrieval.service.spec.ts +++ b/apps/backend/test/integration/rag-retrieval.service.spec.ts @@ -367,6 +367,132 @@ describe("rag retrieval service integration", () => { await moduleRef.close(); }); + it("marks trusted prior SQL as ambiguous when multiple high-confidence candidates remain", async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + const indexRepository = moduleRef.get(RagIndexRepository); + const indexBuilder = moduleRef.get(RagIndexBuilderService); + const retrievalService = moduleRef.get(RagRetrievalService); + + const datasourceId = "ds-rag-retrieval-prior-sql-ambiguous"; + const workspaceId = "ws-rag-prior-ambiguous"; + indexRepository.seedChunksForDatasource(datasourceId, [ + { + id: "chunk-prior-ambiguous-schema", + datasourceId, + domain: "schema", + content: "table orders(id, amount, status)" + }, + { + id: "chunk-prior-ambiguous-sql-1", + datasourceId, + domain: "sql_example", + content: "Question: paid orders\\nSQL:\\nSELECT SUM(amount) FROM orders WHERE status = 'paid'", + metadata: JSON.stringify({ + trusted: true, + priorSql: true, + workspaceId, + tableNames: ["orders"], + columnNames: ["amount", "status"] + }) + }, + { + id: "chunk-prior-ambiguous-sql-2", + datasourceId, + domain: "sql_example", + content: "Question: paid order gmv\\nSQL:\\nSELECT AVG(amount) FROM orders WHERE status = 'paid'", + metadata: JSON.stringify({ + trusted: true, + priorSql: true, + workspaceId, + tableNames: ["orders"], + columnNames: ["amount", "status"] + }) + } + ]); + await indexBuilder.buildAndActivate({ + datasourceId, + sourceVersion: "source-rag-retrieval-prior-sql-ambiguous-v1", + createdByRunId: "run-rag-retrieval-prior-sql-ambiguous-build-v1", + activatedByRunId: "run-rag-retrieval-prior-sql-ambiguous-build-v1" + }); + + const response = await retrievalService.retrieve({ + query: "orders paid gmv", + datasourceId, + workspaceId, + allowedTables: ["orders"], + runId: "run-rag-retrieval-prior-sql-ambiguous-v1" + }); + + const priorSqlLane = response.retrieval_bundle.prior_sql_lane; + expect(priorSqlLane?.status).toBe("ambiguous"); + expect(priorSqlLane?.selected_count).toBe(0); + expect(priorSqlLane?.eligible_count).toBe(2); + expect(priorSqlLane?.shortcut?.status).toBe("ambiguous"); + + await moduleRef.close(); + }); + + it("marks trusted prior SQL as stale when stale flags exist in source metadata", async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + const indexRepository = moduleRef.get(RagIndexRepository); + const indexBuilder = moduleRef.get(RagIndexBuilderService); + const retrievalService = moduleRef.get(RagRetrievalService); + + const datasourceId = "ds-rag-retrieval-prior-sql-stale"; + const workspaceId = "ws-rag-prior-stale"; + indexRepository.seedChunksForDatasource(datasourceId, [ + { + id: "chunk-prior-stale-schema", + datasourceId, + domain: "schema", + content: "table orders(id, amount, status)" + }, + { + id: "chunk-prior-stale-sql-1", + datasourceId, + domain: "sql_example", + content: "Question: paid orders\\nSQL:\\nSELECT SUM(amount) FROM orders WHERE status = 'paid'", + metadata: JSON.stringify({ + trusted: true, + priorSql: true, + stale: true, + workspaceId, + tableNames: ["orders"], + columnNames: ["amount", "status"] + }) + } + ]); + await indexBuilder.buildAndActivate({ + datasourceId, + sourceVersion: "source-rag-retrieval-prior-sql-stale-v1", + createdByRunId: "run-rag-retrieval-prior-sql-stale-build-v1", + activatedByRunId: "run-rag-retrieval-prior-sql-stale-build-v1" + }); + + const response = await retrievalService.retrieve({ + query: "orders paid gmv", + datasourceId, + workspaceId, + allowedTables: ["orders"], + runId: "run-rag-retrieval-prior-sql-stale-v1" + }); + + const priorSqlLane = response.retrieval_bundle.prior_sql_lane; + expect(priorSqlLane?.status).toBe("stale"); + expect(priorSqlLane?.selected_count).toBe(0); + expect(priorSqlLane?.stale_count).toBe(1); + expect(priorSqlLane?.shortcut?.status).toBe("stale"); + + await moduleRef.close(); + }); + it("uses conservative table-first/field-second pruning on wide tables and safely degrades when evidence is weak", async () => { const moduleRef = await Test.createTestingModule({ imports: [AppModule] diff --git a/apps/backend/test/integration/save-view-from-run.spec.ts b/apps/backend/test/integration/save-view-from-run.spec.ts index 2429488..edc6d90 100644 --- a/apps/backend/test/integration/save-view-from-run.spec.ts +++ b/apps/backend/test/integration/save-view-from-run.spec.ts @@ -1,9 +1,10 @@ import { ChatRepository } from "../../src/modules/data/persistence/chat.repository"; import { SaveViewFromRunUsecase } from "../../src/modules/conversation/chat/application/save-view-from-run.usecase"; +import type { KnowledgeMemoryContract } from "../../src/modules/knowledge/contracts/knowledge-memory.contract"; import { ModelingGraphRepository } from "../../src/modules/platform/data/persistence/modeling-graph.repository"; import { ModelingGraphValidator } from "../../src/modules/platform/data/persistence/modeling-graph.validator"; -const buildUsecase = () => { +const buildUsecase = (knowledgeMemoryContract?: KnowledgeMemoryContract) => { const chatRepository = new ChatRepository({ databaseUrl: "" } as never); @@ -14,7 +15,8 @@ const buildUsecase = () => { const usecase = new SaveViewFromRunUsecase( chatRepository, modelingGraphRepository, - modelingGraphValidator + modelingGraphValidator, + knowledgeMemoryContract ); return { usecase, @@ -243,4 +245,77 @@ describe("save view from run integration", () => { code: "RUN_NOT_FOUND" }); }); + + it("keeps save-as-view successful when saved prior sql capture throws", async () => { + const captureFromSavedView = jest + .fn() + .mockRejectedValue(new Error("replay write failed")); + const { usecase, chatRepository, modelingGraphRepository } = buildUsecase({ + promotion: { + promoteFromRun: jest.fn(), + getRecord: jest.fn(), + listCompensations: jest.fn(), + buildCandidateIdForRun: jest.fn(), + applyFeedback: jest.fn() + } as never, + savedPriorSql: { + captureFromSavedView, + getRecord: jest.fn(), + listRecords: jest.fn(), + buildPriorId: jest + .fn() + .mockReturnValue("saved_prior_sql.test-prior-id") + } + } as KnowledgeMemoryContract); + + await chatRepository.createSession({ + id: "session-1", + datasource: "ds-1", + workspaceId: "ws-1", + title: "test", + createdAt: "2026-04-23T00:00:00.000Z" + }); + await chatRepository.persistRun({ + runId: "run-1", + sessionId: "session-1", + question: "recent orders", + status: "executionResult", + provider: "openai", + sql: "SELECT * FROM orders LIMIT 10", + trace: { + runId: "run-1", + provider: "openai", + retryCount: 0, + steps: [] + }, + createdAt: "2026-04-23T01:00:00.000Z" + }); + + const result = await usecase.execute({ + runId: "run-1", + name: "orders_recent_10", + actorId: "user-admin" + }); + + expect(result.replayed).toBe(false); + expect(result.savedPriorSql).toEqual( + expect.objectContaining({ + outcome: "capture_failed", + priorId: "saved_prior_sql.test-prior-id", + reason: "saved_prior_sql_capture_exception" + }) + ); + expect(captureFromSavedView).toHaveBeenCalledTimes(1); + + const snapshot = await modelingGraphRepository.getLatestScopeState({ + workspaceId: "ws-1", + datasourceId: "ds-1" + }); + expect(snapshot.draft?.graphPayload.views).toEqual([ + expect.objectContaining({ + id: "view.chat_run.run-1", + name: "orders_recent_10" + }) + ]); + }); }); diff --git a/apps/backend/test/integration/saved-prior-sql-capture.spec.ts b/apps/backend/test/integration/saved-prior-sql-capture.spec.ts new file mode 100644 index 0000000..2c46138 --- /dev/null +++ b/apps/backend/test/integration/saved-prior-sql-capture.spec.ts @@ -0,0 +1,208 @@ +import type { WriteRagReplayInput } from "../../src/modules/knowledge/rag/observability/rag-replay.repository"; +import { SavedPriorSqlService } from "../../src/modules/knowledge/memory/saved-prior-sql.service"; +import type { RagDocumentBuildResult } from "../../src/modules/rag/ingestion/rag-document.factory"; + +const createService = (override?: { + writeReplay?: (input: WriteRagReplayInput) => Promise; +}) => { + const writeReplay = + override?.writeReplay ?? + (async (input: WriteRagReplayInput) => ({ + runId: input.runId, + replayKey: input.replayKey, + datasourceId: input.datasourceId, + stage: input.stage, + payload: JSON.stringify(input.payload), + createdAt: input.createdAt ?? new Date().toISOString() + })); + const replayRepository = { + writeReplay: jest.fn(writeReplay) + }; + const buildResult: RagDocumentBuildResult = { + document: { + id: "doc-prior-1", + datasourceId: "ds-1", + domain: "sql_example", + sourceType: "sql_example", + sourceRef: "saved_prior_sql:prior-1", + sourceVersion: "saved_prior_sql:prior-1:v1", + contentChecksum: "checksum-1", + title: "Saved Prior SQL", + content: "Question: q\nSQL: select 1", + tableNames: ["orders"], + columnNames: ["id"], + metadata: JSON.stringify({ + trusted: true, + verified: true, + priorSql: true + }) + }, + chunks: [ + { + id: "chunk-prior-1", + documentId: "doc-prior-1", + datasourceId: "ds-1", + domain: "sql_example", + chunkProfile: "sql_example", + chunkOrder: 0, + content: "Question: q\nSQL: select 1", + contentChecksum: "checksum-1", + tableNames: ["orders"], + columnNames: ["id"], + metadata: JSON.stringify({ + trusted: true, + verified: true, + priorSql: true + }) + } + ] + }; + const documentFactory = { + create: jest.fn().mockReturnValue(buildResult) + }; + const documentRepository = { + upsertDocumentWithChunks: jest.fn().mockResolvedValue({ + documentId: "doc-prior-1", + insertedDocument: true, + insertedChunkCount: 1 + }) + }; + const indexRepository = { + upsertChunksForDatasource: jest.fn() + }; + const buildRagIndexJob = { + run: jest.fn().mockResolvedValue({ + indexVersionId: "idx-prior-1", + datasourceId: "ds-1", + status: "active", + entryCount: 1, + archivedChannels: ["lexical", "dense"], + denseMode: "placeholder_vector_string" + }) + }; + const service = new SavedPriorSqlService( + replayRepository as never, + documentFactory as never, + documentRepository as never, + indexRepository as never, + buildRagIndexJob as never + ); + return { + service, + replayRepository, + documentRepository, + buildRagIndexJob + }; +}; + +describe("saved prior sql capture integration", () => { + it("captures trusted prior SQL once and preserves idempotency on duplicate capture", async () => { + const { service, replayRepository, documentRepository, buildRagIndexJob } = + createService(); + const first = await service.captureFromSavedView({ + workspaceId: "ws-1", + datasourceId: "ds-1", + sourceRunId: "run-1", + sourceRunStatus: "executionResult", + sourceRunCreatedAt: "2026-04-25T08:00:00.000Z", + question: "recent paid orders", + sql: "SELECT o.id, o.status FROM orders o JOIN users u ON u.id = o.user_id WHERE o.status = 'paid'", + viewId: "view.chat_run.run-1", + viewName: "orders_paid_recent", + viewSql: "SELECT o.id, o.status FROM orders o JOIN users u ON u.id = o.user_id WHERE o.status = 'paid'", + replayed: false, + savedAt: "2026-04-25T09:00:00.000Z" + }); + + expect(first.outcome).toBe("captured"); + expect(first.record).toEqual( + expect.objectContaining({ + workspaceId: "ws-1", + datasourceId: "ds-1", + sourceRunId: "run-1", + viewId: "view.chat_run.run-1", + question: "recent paid orders", + sql: expect.stringContaining("FROM orders"), + savedAt: "2026-04-25T09:00:00.000Z", + metadata: { + trusted: true, + verified: true, + priorSql: true + } + }) + ); + expect(first.record?.tableNames).toEqual( + expect.arrayContaining(["orders", "users"]) + ); + expect(first.record?.columnNames).toEqual( + expect.arrayContaining(["id", "status"]) + ); + expect(replayRepository.writeReplay).toHaveBeenCalledTimes(1); + expect(documentRepository.upsertDocumentWithChunks).toHaveBeenCalledTimes(1); + expect(buildRagIndexJob.run).toHaveBeenCalledTimes(1); + + const second = await service.captureFromSavedView({ + workspaceId: "ws-1", + datasourceId: "ds-1", + sourceRunId: "run-1", + sourceRunStatus: "executionResult", + question: "recent paid orders", + sql: "SELECT o.id, o.status FROM orders o JOIN users u ON u.id = o.user_id WHERE o.status = 'paid'", + viewId: "view.chat_run.run-1", + viewName: "orders_paid_recent", + viewSql: "SELECT o.id, o.status FROM orders o JOIN users u ON u.id = o.user_id WHERE o.status = 'paid'", + replayed: false + }); + + expect(second.outcome).toBe("duplicate"); + expect(second.reason).toBe("prior_already_captured"); + expect(second.priorId).toBe(first.priorId); + expect(replayRepository.writeReplay).toHaveBeenCalledTimes(2); + expect(service.listRecords()).toHaveLength(1); + }); + + it("marks replayed save request as duplicate no-op", async () => { + const { service, documentRepository, buildRagIndexJob } = createService(); + const result = await service.captureFromSavedView({ + workspaceId: "ws-1", + datasourceId: "ds-1", + sourceRunId: "run-1", + sourceRunStatus: "executionResult", + question: "recent paid orders", + sql: "SELECT * FROM orders", + viewId: "view.chat_run.run-1", + viewName: "orders_recent", + viewSql: "SELECT * FROM orders", + replayed: true + }); + + expect(result.outcome).toBe("duplicate"); + expect(result.reason).toBe("save_replayed"); + expect(result.record).toBeUndefined(); + expect(service.listRecords()).toHaveLength(0); + expect(documentRepository.upsertDocumentWithChunks).toHaveBeenCalledTimes(0); + expect(buildRagIndexJob.run).toHaveBeenCalledTimes(0); + }); + + it("skips capture for ineligible source run status", async () => { + const { service, documentRepository, buildRagIndexJob } = createService(); + const result = await service.captureFromSavedView({ + workspaceId: "ws-1", + datasourceId: "ds-1", + sourceRunId: "run-1", + sourceRunStatus: "failed", + question: "recent paid orders", + sql: "SELECT * FROM orders", + viewId: "view.chat_run.run-1", + viewName: "orders_recent", + viewSql: "SELECT * FROM orders", + replayed: false + }); + + expect(result.outcome).toBe("skipped_ineligible"); + expect(result.reason).toBe("source_run_not_execution_result"); + expect(service.getRecord(result.priorId)).toBeUndefined(); + expect(documentRepository.upsertDocumentWithChunks).toHaveBeenCalledTimes(0); + expect(buildRagIndexJob.run).toHaveBeenCalledTimes(0); + }); +}); diff --git a/apps/backend/test/integration/saved-prior-sql-ingestion.spec.ts b/apps/backend/test/integration/saved-prior-sql-ingestion.spec.ts new file mode 100644 index 0000000..fc73fff --- /dev/null +++ b/apps/backend/test/integration/saved-prior-sql-ingestion.spec.ts @@ -0,0 +1,110 @@ +import { resolve } from "node:path"; +import { Test } from "@nestjs/testing"; +import { AppModule } from "../../src/app.module"; +import { SavedPriorSqlService } from "../../src/modules/knowledge/memory/saved-prior-sql.service"; +import { RagIndexBuilderService } from "../../src/modules/rag/index/rag-index-builder.service"; +import { RagIndexRepository } from "../../src/modules/rag/index/rag-index.repository"; +import { RagRetrievalService } from "../../src/modules/knowledge/rag/retrieval/rag-retrieval.service"; + +describe("saved prior sql ingestion integration", () => { + beforeAll(() => { + process.env.SQLITE_PATH = resolve( + __dirname, + "../../../../data/sqlite/text2sql.db" + ); + process.env.DATABASE_URL = ""; + process.env.REDIS_URL = ""; + process.env.LLM_PROVIDER = "volcengine"; + process.env.LLM_MOCK_MODE = "true"; + }); + + it("upserts saved prior SQL into active index and keeps duplicate capture idempotent", async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + const indexRepository = moduleRef.get(RagIndexRepository); + const indexBuilder = moduleRef.get(RagIndexBuilderService); + const savedPriorService = moduleRef.get(SavedPriorSqlService); + const retrievalService = moduleRef.get(RagRetrievalService); + + const datasourceId = "ds-saved-prior-ingestion"; + const workspaceId = "ws-saved-prior-ingestion"; + + indexRepository.seedChunksForDatasource(datasourceId, [ + { + id: "chunk-ingestion-schema-orders", + datasourceId, + domain: "schema", + content: "table orders(id, amount, status)", + metadata: JSON.stringify({ + tableNames: ["orders"], + columnNames: ["id", "amount", "status"] + }) + } + ]); + + await indexBuilder.buildAndActivate({ + datasourceId, + sourceVersion: "source-saved-prior-ingestion-bootstrap-v1", + createdByRunId: "run-saved-prior-ingestion-bootstrap-v1", + activatedByRunId: "run-saved-prior-ingestion-bootstrap-v1" + }); + + const first = await savedPriorService.captureFromSavedView({ + workspaceId, + datasourceId, + sourceRunId: "run-saved-prior-source-v1", + sourceRunStatus: "executionResult", + sourceRunCreatedAt: "2026-04-25T08:00:00.000Z", + question: "orders paid gmv", + sql: "SELECT SUM(amount) FROM orders WHERE status = 'paid'", + viewId: "view.chat_run.run-saved-prior-source-v1", + viewName: "orders_paid_gmv", + viewSql: "SELECT SUM(amount) FROM orders WHERE status = 'paid'", + tableNames: ["orders"], + columnNames: ["amount", "status"], + replayed: false, + savedAt: "2026-04-25T08:00:00.000Z" + }); + + expect(first.outcome).toBe("captured"); + + const response = await retrievalService.retrieve({ + query: "orders paid gmv", + datasourceId, + workspaceId, + allowedTables: ["orders"], + runId: "run-saved-prior-retrieve-v1" + }); + + expect(response.retrieval_bundle.prior_sql_lane?.status).toBe("hit"); + expect(response.retrieval_bundle.prior_sql_lane?.selected_count).toBe(1); + expect(response.retrieval_bundle.candidates[0]?.chunk.metadata.domain).toBe("sql_example"); + + const second = await savedPriorService.captureFromSavedView({ + workspaceId, + datasourceId, + sourceRunId: "run-saved-prior-source-v1", + sourceRunStatus: "executionResult", + question: "orders paid gmv", + sql: "SELECT SUM(amount) FROM orders WHERE status = 'paid'", + viewId: "view.chat_run.run-saved-prior-source-v1", + viewName: "orders_paid_gmv", + viewSql: "SELECT SUM(amount) FROM orders WHERE status = 'paid'", + tableNames: ["orders"], + columnNames: ["amount", "status"], + replayed: false + }); + + expect(second.outcome).toBe("duplicate"); + + const activeVersion = await indexRepository.getActiveVersion(datasourceId); + expect(activeVersion).toBeDefined(); + const activeEntries = await indexRepository.listEntriesByVersion(activeVersion?.id ?? ""); + const priorChunkIds = activeEntries.filter((item) => item.domain === "sql_example"); + expect(priorChunkIds).toHaveLength(1); + + await moduleRef.close(); + }); +}); diff --git a/apps/backend/test/integration/saved-prior-sql-quality.spec.ts b/apps/backend/test/integration/saved-prior-sql-quality.spec.ts new file mode 100644 index 0000000..8a0ddf4 --- /dev/null +++ b/apps/backend/test/integration/saved-prior-sql-quality.spec.ts @@ -0,0 +1,60 @@ +import { resolve } from "node:path"; +import { Test } from "@nestjs/testing"; +import { AppModule } from "../../src/app.module"; +import { RagQualityService } from "../../src/modules/rag/quality/rag-quality.service"; + +describe("saved prior sql quality diagnostics integration", () => { + beforeAll(() => { + process.env.SQLITE_PATH = resolve( + __dirname, + "../../../../data/sqlite/text2sql.db" + ); + process.env.DATABASE_URL = ""; + process.env.REDIS_URL = ""; + process.env.LLM_PROVIDER = "volcengine"; + process.env.LLM_MOCK_MODE = "true"; + }); + + it("aggregates saved prior shortcut diagnostic counters", async () => { + const moduleRef = await Test.createTestingModule({ + imports: [AppModule] + }).compile(); + + const quality = moduleRef.get(RagQualityService); + quality.reset(); + + quality.recordEvaluation({ + runId: "run-saved-prior-quality-v1", + datasourceId: "ds-saved-prior-quality", + sampleSize: 48, + recallAt20: 0.88, + mrrAt10: 0.72, + retrievalRerankP95Ms: 640, + degradeRate: 0.03, + priorSqlLane: { + totalCount: 20, + hitCount: 12, + missCount: 3, + filteredCount: 2, + staleCount: 1, + ambiguousCount: 1, + duplicateCount: 1, + safetyRejectedCount: 2, + fallbackToGenerationCount: 2 + } + }); + + const snapshot = quality.snapshot(); + expect(snapshot.priorSqlLane.sampleSize).toBe(20); + expect(snapshot.priorSqlLane.priorSqlHitRate).toBe(0.6); + expect(snapshot.priorSqlLane.priorSqlMissCount).toBe(3); + expect(snapshot.priorSqlLane.priorSqlFilteredCount).toBe(2); + expect(snapshot.priorSqlLane.priorSqlStaleRate).toBe(0.05); + expect(snapshot.priorSqlLane.priorSqlAmbiguousCount).toBe(1); + expect(snapshot.priorSqlLane.priorSqlDuplicateCount).toBe(1); + expect(snapshot.priorSqlLane.priorSqlSafetyRejectedCount).toBe(2); + expect(snapshot.priorSqlLane.priorSqlFallbackToGenerationCount).toBe(2); + + await moduleRef.close(); + }); +}); diff --git a/apps/backend/test/unit/delivery-contract.mapper.spec.ts b/apps/backend/test/unit/delivery-contract.mapper.spec.ts index 831e84d..e3b229c 100644 --- a/apps/backend/test/unit/delivery-contract.mapper.spec.ts +++ b/apps/backend/test/unit/delivery-contract.mapper.spec.ts @@ -357,6 +357,51 @@ describe("DeliveryContractMapper", () => { expect(delivery.evidence?.modelingRevision).toBe(12); }); + it("maps saved prior SQL shortcut evidence from resolve+safety trace steps", () => { + const mapper = new DeliveryContractMapper(new SandboxRuntimeService()); + const run = createBaseRun({ + trace: { + runId: "run-delivery-unit", + provider: "volcengine", + retryCount: 0, + steps: [ + { + node: "resolve-saved-prior-sql", + status: "success", + at: "2026-04-25T00:00:00.100Z", + outputSummary: JSON.stringify({ + status: "hit", + reasonCodes: ["prior_sql_shortcut_hit"], + selectedChunkId: "chunk-saved-prior-1", + selectedViewId: "view.chat_run.run-1", + selectedSourceRunId: "run-1" + }) + }, + { + node: "safety-check", + status: "success", + at: "2026-04-25T00:00:00.200Z" + } + ] + } as SqlRun["trace"] + }); + + const delivery = mapper.map({ + run, + replayRecords: [] + }); + + expect(delivery.evidence?.savedPriorSql).toEqual({ + status: "hit", + shortcutUsed: true, + reasonCodes: ["prior_sql_shortcut_hit"], + selectedChunkId: "chunk-saved-prior-1", + selectedViewId: "view.chat_run.run-1", + selectedSourceRunId: "run-1", + safetyResult: "passed" + }); + }); + it("falls back to semantic step summary for revision and binding evidence", () => { const mapper = new DeliveryContractMapper(new SandboxRuntimeService()); const run = createBaseRun({ diff --git a/apps/backend/test/unit/langgraph-runtime.spec.ts b/apps/backend/test/unit/langgraph-runtime.spec.ts index 51e744e..fd4989a 100644 --- a/apps/backend/test/unit/langgraph-runtime.spec.ts +++ b/apps/backend/test/unit/langgraph-runtime.spec.ts @@ -93,6 +93,12 @@ describe("langgraph runtime", () => { summary: "stub" }) }, + resolveSavedPriorSqlNode: { + run: () => ({ + status: "miss" as const, + reasonCodes: ["prior_sql_no_trusted_match"] + }) + }, generateSqlNode: { run: async () => ({ provider: "mock-provider", @@ -187,6 +193,12 @@ describe("langgraph runtime", () => { summary: "stub" }) }, + resolveSavedPriorSqlNode: { + run: () => ({ + status: "miss" as const, + reasonCodes: ["prior_sql_no_trusted_match"] + }) + }, generateSqlNode: { run: async () => ({ provider: "mock-provider", @@ -237,6 +249,7 @@ describe("langgraph runtime", () => { "build-intent-plan", "build-semantic-query", "build-physical-plan", + "resolve-saved-prior-sql", "generate-sql", "safety-check", "execute-sql", @@ -245,8 +258,8 @@ describe("langgraph runtime", () => { expect(output.trace.steps[0]?.sequence).toBe(1); expect(output.trace.steps[0]?.stepId).toBe("run-1:clarify:1"); expect(output.trace.steps[0]?.lifecycle).toBe("skipped"); - expect(output.trace.steps[5]?.sequence).toBe(6); - expect(output.trace.steps[5]?.lifecycle).toBe("completed"); + expect(output.trace.steps[6]?.sequence).toBe(7); + expect(output.trace.steps[6]?.lifecycle).toBe("completed"); expect(output.trace.clarificationDecision?.decision).toBe("continue"); }); @@ -302,6 +315,12 @@ describe("langgraph runtime", () => { summary: "stub" }) }, + resolveSavedPriorSqlNode: { + run: () => ({ + status: "miss" as const, + reasonCodes: ["prior_sql_no_trusted_match"] + }) + }, generateSqlNode: { run: async () => { throw new Error("llm failed"); @@ -343,12 +362,13 @@ describe("langgraph runtime", () => { "build-intent-plan", "build-semantic-query", "build-physical-plan", + "resolve-saved-prior-sql", "generate-sql" ]); - expect(output.trace.steps[5]?.status).toBe("failed"); - expect(output.trace.steps[5]?.sequence).toBe(6); - expect(output.trace.steps[5]?.stepId).toBe("run-2:generate-sql:6"); - expect(output.trace.steps[5]?.lifecycle).toBe("failed"); - expect(output.trace.steps[5]?.errorSummary).toContain("llm failed"); + expect(output.trace.steps[6]?.status).toBe("failed"); + expect(output.trace.steps[6]?.sequence).toBe(7); + expect(output.trace.steps[6]?.stepId).toBe("run-2:generate-sql:7"); + expect(output.trace.steps[6]?.lifecycle).toBe("failed"); + expect(output.trace.steps[6]?.errorSummary).toContain("llm failed"); }); }); diff --git a/apps/backend/test/unit/resolve-saved-prior-sql.node.spec.ts b/apps/backend/test/unit/resolve-saved-prior-sql.node.spec.ts new file mode 100644 index 0000000..572f722 --- /dev/null +++ b/apps/backend/test/unit/resolve-saved-prior-sql.node.spec.ts @@ -0,0 +1,175 @@ +import { ResolveSavedPriorSqlNode } from "../../src/modules/conversation/agent/nodes/resolve-saved-prior-sql.node"; + +describe("ResolveSavedPriorSqlNode", () => { + it("returns shortcut SQL when lane is hit and selected chunk contains sql", () => { + const node = new ResolveSavedPriorSqlNode(); + const result = node.run({ + question: "统计订单总数", + retrievalBundle: { + query: "统计订单总数", + run_id: "run-1", + datasource_id: "ds-1", + status: "ready", + degrade_reasons: [], + lane_results: { + lexical: { + lane: "lexical", + status: "ok", + timeout_ms: 10, + elapsed_ms: 5, + hits: [] + }, + dense: { + lane: "dense", + status: "ok", + timeout_ms: 10, + elapsed_ms: 5, + hits: [] + }, + graph: { + lane: "graph", + status: "ok", + timeout_ms: 10, + elapsed_ms: 5, + hits: [] + } + }, + candidates: [ + { + chunk_id: "chunk-prior-1", + source_lane: "lexical", + score: 1, + evidence: ["prior_sql:trusted"], + lane_scores: { + lexical: 1 + }, + lane_ranks: { + lexical: 1 + }, + chunk: { + chunk_id: "chunk-prior-1", + content: "Question: 统计订单总数\n\nSQL:\nSELECT COUNT(*) FROM orders", + metadata: { + datasourceId: "ds-1", + indexVersionId: "idx-1", + chunkId: "chunk-prior-1", + domain: "sql_example", + tableNames: ["orders"], + columnNames: ["id"], + sourceMetadata: { + trusted: true, + priorSql: true, + sql: "SELECT COUNT(*) FROM orders", + viewId: "view.chat_run.run-1", + sourceRunId: "run-1" + } + } + } + } + ], + selected_context: [], + prior_sql_lane: { + status: "hit", + matched_count: 1, + selected_count: 1, + filtered_count: 0, + shortcut: { + status: "hit", + reason_codes: ["prior_sql_shortcut_hit"], + matched_count: 1, + eligible_count: 1, + filtered_count: 0, + stale_count: 0, + ambiguous_count: 0, + selected_chunk_id: "chunk-prior-1", + selected_view_id: "view.chat_run.run-1", + selected_source_run_id: "run-1" + } + } + } + }); + + expect(result.status).toBe("hit"); + expect(result.sql).toBe("SELECT COUNT(*) FROM orders"); + expect(result.selectedChunkId).toBe("chunk-prior-1"); + expect(result.selectedViewId).toBe("view.chat_run.run-1"); + expect(result.selectedSourceRunId).toBe("run-1"); + }); + + it("falls back to chunk content when structured sql field is missing", () => { + const node = new ResolveSavedPriorSqlNode(); + const result = node.run({ + question: "统计订单总数", + retrievalBundle: { + query: "统计订单总数", + run_id: "run-1", + datasource_id: "ds-1", + status: "ready", + degrade_reasons: [], + lane_results: { + lexical: { + lane: "lexical", + status: "ok", + timeout_ms: 10, + elapsed_ms: 5, + hits: [] + }, + dense: { + lane: "dense", + status: "ok", + timeout_ms: 10, + elapsed_ms: 5, + hits: [] + }, + graph: { + lane: "graph", + status: "ok", + timeout_ms: 10, + elapsed_ms: 5, + hits: [] + } + }, + candidates: [ + { + chunk_id: "chunk-prior-1", + source_lane: "lexical", + score: 1, + evidence: ["prior_sql:trusted"], + lane_scores: { + lexical: 1 + }, + lane_ranks: { + lexical: 1 + }, + chunk: { + chunk_id: "chunk-prior-1", + content: "Question: 统计订单总数", + metadata: { + datasourceId: "ds-1", + indexVersionId: "idx-1", + chunkId: "chunk-prior-1", + domain: "sql_example", + tableNames: ["orders"], + columnNames: ["id"], + sourceMetadata: { + trusted: true, + priorSql: true + } + } + } + } + ], + selected_context: [], + prior_sql_lane: { + status: "hit", + matched_count: 1, + selected_count: 1, + filtered_count: 0 + } + } + }); + + expect(result.status).toBe("hit"); + expect(result.sql).toBe("Question: 统计订单总数"); + }); +}); diff --git a/apps/frontend/src/components/chat/rag-delivery-panel.tsx b/apps/frontend/src/components/chat/rag-delivery-panel.tsx index bb2478c..d97b8bd 100644 --- a/apps/frontend/src/components/chat/rag-delivery-panel.tsx +++ b/apps/frontend/src/components/chat/rag-delivery-panel.tsx @@ -142,6 +142,42 @@ function resolvePromptTemplateSummary(delivery?: DeliveryContract): { }; } +function resolveSavedPriorSqlSummary(delivery?: DeliveryContract): { + summary: string; + detail: string; +} { + const savedPriorSql = delivery?.evidence?.savedPriorSql; + if (!savedPriorSql) { + return { + summary: "未命中 saved prior", + detail: "未记录 saved prior SQL 复用信息。" + }; + } + if (savedPriorSql.status === "hit" && savedPriorSql.safetyResult === "passed") { + return { + summary: "shortcut hit", + detail: "已复用保存的 SQL,并已通过安全校验。" + }; + } + if (savedPriorSql.status === "hit" && savedPriorSql.safetyResult === "fallback_generated") { + return { + summary: "shortcut fallback", + detail: "命中保存 SQL,但安全校验拒绝,已回退到常规 SQL 生成。" + }; + } + if (savedPriorSql.status === "hit" && savedPriorSql.safetyResult === "rejected") { + return { + summary: "shortcut rejected", + detail: "命中保存 SQL,但安全校验拒绝,未执行该 SQL。" + }; + } + const reasons = savedPriorSql.reasonCodes?.join(",") ?? "unknown"; + return { + summary: `shortcut ${savedPriorSql.status}`, + detail: `saved prior 状态:${savedPriorSql.status}(${reasons})。` + }; +} + function renderSelectedContextState( delivery: DeliveryContract ): { state: SelectedContextState; node: JSX.Element } { @@ -204,6 +240,7 @@ export function RagDeliveryPanel({ delivery, runId }: RagDeliveryPanelProps) { const evidence = delivery?.evidence; const artifact = delivery?.artifact; const promptTemplateSummary = resolvePromptTemplateSummary(delivery); + const savedPriorSqlSummary = resolveSavedPriorSqlSummary(delivery); const runLifecycleKey = runId?.trim() || evidence?.runId?.trim() || "__unknown-run__"; const semanticVersionText = evidence?.semanticVersion ?? "版本不可用(字段缺失)"; const semanticLockStatusText = @@ -333,7 +370,20 @@ export function RagDeliveryPanel({ delivery, runId }: RagDeliveryPanelProps) {

selected_context 状态:{selectedContextState.state}

模板命中摘要:{promptTemplateSummary.summary}

模板降级原因:{promptTemplateSummary.fallbackReasonText}

+

Saved Prior SQL:{savedPriorSqlSummary.summary}

+ + {savedPriorSqlSummary.detail} + {selectedContextState.node} {(evidence?.degradeReasons?.length ?? 0) > 0 ? (

diff --git a/apps/frontend/src/components/chat/run-visibility-mapper.ts b/apps/frontend/src/components/chat/run-visibility-mapper.ts index bfe3367..fbca173 100644 --- a/apps/frontend/src/components/chat/run-visibility-mapper.ts +++ b/apps/frontend/src/components/chat/run-visibility-mapper.ts @@ -16,6 +16,7 @@ export type RunVisibilityThinkingStep = ExecutionTraceStep & { }; type JsonRecord = Record; +type SavedPriorSqlLayer = NonNullable; function isRecord(value: unknown): value is JsonRecord { return typeof value === "object" && value !== null && !Array.isArray(value); @@ -186,6 +187,88 @@ function normalizeEvidence(value: unknown): DeliveryEvidenceLayer | undefined { value.skillContextSummary ?? value.skill_context_summary ?? value.skill_context ); const evidenceStale = readBoolean(value.evidenceStale) ?? readBoolean(value.evidence_stale); + const savedPriorSqlRaw = isRecord(value.savedPriorSql) + ? value.savedPriorSql + : isRecord(value.saved_prior_sql) + ? value.saved_prior_sql + : undefined; + const savedPriorStatusRaw = readString(savedPriorSqlRaw?.status); + const savedPriorStatus: SavedPriorSqlLayer["status"] | undefined = + savedPriorStatusRaw === "hit" || + savedPriorStatusRaw === "miss" || + savedPriorStatusRaw === "filtered" || + savedPriorStatusRaw === "stale" || + savedPriorStatusRaw === "ambiguous" + ? savedPriorStatusRaw + : undefined; + const savedPriorSafetyRaw = readString( + savedPriorSqlRaw?.safetyResult ?? savedPriorSqlRaw?.safety_result + ); + const savedPriorSafetyResult: SavedPriorSqlLayer["safetyResult"] | undefined = + savedPriorSafetyRaw === "passed" || + savedPriorSafetyRaw === "rejected" || + savedPriorSafetyRaw === "fallback_generated" + ? savedPriorSafetyRaw + : undefined; + const savedPriorShortcutUsed = readBoolean( + savedPriorSqlRaw?.shortcutUsed ?? savedPriorSqlRaw?.shortcut_used + ); + const savedPriorReasonCodes = unique([ + ...readStringArray(savedPriorSqlRaw?.reasonCodes), + ...readStringArray(savedPriorSqlRaw?.reason_codes) + ]); + const savedPriorSql: SavedPriorSqlLayer | undefined = + savedPriorStatus && savedPriorShortcutUsed !== undefined + ? { + status: savedPriorStatus, + shortcutUsed: savedPriorShortcutUsed, + ...(savedPriorReasonCodes.length > 0 + ? { reasonCodes: savedPriorReasonCodes } + : {}), + ...(readString( + savedPriorSqlRaw?.selectedChunkId ?? savedPriorSqlRaw?.selected_chunk_id + ) + ? { + selectedChunkId: readString( + savedPriorSqlRaw?.selectedChunkId ?? + savedPriorSqlRaw?.selected_chunk_id + ) + } + : {}), + ...(readString( + savedPriorSqlRaw?.selectedViewId ?? savedPriorSqlRaw?.selected_view_id + ) + ? { + selectedViewId: readString( + savedPriorSqlRaw?.selectedViewId ?? + savedPriorSqlRaw?.selected_view_id + ) + } + : {}), + ...(readString( + savedPriorSqlRaw?.selectedViewName ?? savedPriorSqlRaw?.selected_view_name + ) + ? { + selectedViewName: readString( + savedPriorSqlRaw?.selectedViewName ?? + savedPriorSqlRaw?.selected_view_name + ) + } + : {}), + ...(readString( + savedPriorSqlRaw?.selectedSourceRunId ?? + savedPriorSqlRaw?.selected_source_run_id + ) + ? { + selectedSourceRunId: readString( + savedPriorSqlRaw?.selectedSourceRunId ?? + savedPriorSqlRaw?.selected_source_run_id + ) + } + : {}), + ...(savedPriorSafetyResult ? { safetyResult: savedPriorSafetyResult } : {}) + } + : undefined; const normalized: DeliveryEvidenceLayer = { runId, @@ -198,6 +281,7 @@ function normalizeEvidence(value: unknown): DeliveryEvidenceLayer | undefined { ...(semanticLockStatus ? { semanticLockStatus } : {}), ...(semanticDegradeReason ? { semanticDegradeReason } : {}), ...(skillContextSummary ? { skillContextSummary } : {}), + ...(savedPriorSql ? { savedPriorSql } : {}), ...(evidenceStale !== undefined ? { evidenceStale } : {}) }; diff --git a/apps/frontend/tests/unit/chat-rag-sync-stream-consistency.spec.tsx b/apps/frontend/tests/unit/chat-rag-sync-stream-consistency.spec.tsx index f912cd1..78862f9 100644 --- a/apps/frontend/tests/unit/chat-rag-sync-stream-consistency.spec.tsx +++ b/apps/frontend/tests/unit/chat-rag-sync-stream-consistency.spec.tsx @@ -82,6 +82,36 @@ describe("chat rag sync/stream consistency", () => { expect(resolved?.answer.provider).toBe("mock-provider"); }); + it("normalizes saved prior SQL evidence from snake_case stream payload", () => { + const normalized = normalizeDeliveryContract({ + answer: { + text: "stream-answer", + status: "executionResult", + provider: "mock-stream" + }, + evidence: { + run_id: "run-sync", + saved_prior_sql: { + status: "hit", + shortcut_used: true, + reason_codes: ["prior_sql_shortcut_hit"], + selected_view_id: "view.chat_run.run-sync", + selected_source_run_id: "run-sync", + safety_result: "passed" + } + } + }); + + expect(normalized?.evidence?.savedPriorSql).toEqual({ + status: "hit", + shortcutUsed: true, + reasonCodes: ["prior_sql_shortcut_hit"], + selectedViewId: "view.chat_run.run-sync", + selectedSourceRunId: "run-sync", + safetyResult: "passed" + }); + }); + it("prevents terminal state regression to loading on duplicate and out-of-order events", () => { let status = transitionRunVisibilityStatus(undefined, "loading"); status = transitionRunVisibilityStatus(status, "success"); diff --git a/apps/frontend/tests/unit/rag-delivery-panel.spec.tsx b/apps/frontend/tests/unit/rag-delivery-panel.spec.tsx index c4d4e78..f543684 100644 --- a/apps/frontend/tests/unit/rag-delivery-panel.spec.tsx +++ b/apps/frontend/tests/unit/rag-delivery-panel.spec.tsx @@ -216,4 +216,35 @@ describe("RagDeliveryPanel", () => { ).toBeInTheDocument(); expect(screen.getByText("模板降级原因:not_triggered")).toBeInTheDocument(); }); + + it("renders saved prior SQL shortcut evidence and safety status", async () => { + const user = userEvent.setup(); + render( + + ); + + const evidenceTrigger = screen.getByRole("button", { + name: "切换 Evidence 区块" + }); + if (evidenceTrigger.getAttribute("aria-expanded") !== "true") { + await user.click(evidenceTrigger); + } + + expect(screen.getByText("Saved Prior SQL:shortcut hit")).toBeInTheDocument(); + expect( + screen.getByText("已复用保存的 SQL,并已通过安全校验。") + ).toBeInTheDocument(); + }); }); diff --git a/graphify-out/GRAPH_REPORT.md b/graphify-out/GRAPH_REPORT.md index f8ce71f..4dc1b31 100644 --- a/graphify-out/GRAPH_REPORT.md +++ b/graphify-out/GRAPH_REPORT.md @@ -1,12 +1,12 @@ # Graph Report - /Users/lienli/Documents/GitHub/text2sql (2026-04-25) ## Corpus Check -- 673 files · ~1,006,016 words +- 681 files · ~1,028,789 words - Verdict: corpus is large enough that graph structure adds value. ## Summary -- 3421 nodes · 6510 edges · 479 communities detected -- Extraction: 79% EXTRACTED · 21% INFERRED · 0% AMBIGUOUS · INFERRED: 1377 edges (avg confidence: 0.8) +- 3481 nodes · 6638 edges · 486 communities detected +- Extraction: 79% EXTRACTED · 21% INFERRED · 0% AMBIGUOUS · INFERRED: 1395 edges (avg confidence: 0.8) - Token cost: 0 input · 0 output ## Community Hubs (Navigation) @@ -489,18 +489,25 @@ - [[_COMMUNITY_Community 476|Community 476]] - [[_COMMUNITY_Community 477|Community 477]] - [[_COMMUNITY_Community 478|Community 478]] +- [[_COMMUNITY_Community 479|Community 479]] +- [[_COMMUNITY_Community 480|Community 480]] +- [[_COMMUNITY_Community 481|Community 481]] +- [[_COMMUNITY_Community 482|Community 482]] +- [[_COMMUNITY_Community 483|Community 483]] +- [[_COMMUNITY_Community 484|Community 484]] +- [[_COMMUNITY_Community 485|Community 485]] ## God Nodes (most connected - your core abstractions) 1. `ok()` - 86 edges 2. `WorkspaceModelingService` - 77 edges -3. `RagRetrievalService` - 63 edges +3. `RagRetrievalService` - 69 edges 4. `GlossaryService` - 59 edges 5. `isRecord()` - 55 edges 6. `toAdminApiError()` - 51 edges 7. `WorkspaceDatasourcePolicyRepository` - 45 edges 8. `ChatRepository` - 43 edges -9. `AppConfigService` - 41 edges -10. `DeliveryContractMapper` - 41 edges +9. `DeliveryContractMapper` - 42 edges +10. `AppConfigService` - 41 edges ## Surprising Connections (you probably didn't know these) - `loadPreviewForDetails()` --calls--> `getWorkspaceModelingPreview()` [INFERRED] @@ -518,79 +525,79 @@ ### Community 0 - "Community 0" Cohesion: 0.03 -Nodes (15): ChatController, ChatPolicyGuardService, ChatRepository, ChatRunPersistenceService, ChatService, EvalService, ExecuteMessageUsecase, ExecuteSqlNode (+7 more) +Nodes (15): ChatPolicyGuardService, ChatRepository, ChatRunPersistenceService, ChatService, DatasourceAccessPolicyService, EvalService, ExecuteMessageUsecase, ExecuteSqlNode (+7 more) ### Community 1 - "Community 1" -Cohesion: 0.02 -Nodes (42): extractLatestUserText(), mapToThreadMessages(), BuildRagIndexJob, parseSseEvents(), buildContextEnvelopeFromDraft(), parseEntityMappings(), splitByDelimiters(), trimOrUndefined() (+34 more) +Cohesion: 0.03 +Nodes (45): extractLatestUserText(), mapToThreadMessages(), parseSseEvents(), buildContextEnvelopeFromDraft(), parseEntityMappings(), splitByDelimiters(), trimOrUndefined(), DeliveryContractMapper (+37 more) ### Community 2 - "Community 2" -Cohesion: 0.02 -Nodes (39): AppConfigService, DatasourceRegistryService, countTracedRuns(), main(), parseArgs(), computeLangsmithCoverage(), bootstrap(), buildGraph() (+31 more) +Cohesion: 0.03 +Nodes (17): AppConfigService, DatasourceRegistryService, countTracedRuns(), main(), parseArgs(), computeLangsmithCoverage(), bootstrap(), readWorkspaceIdFromQuery() (+9 more) ### Community 3 - "Community 3" Cohesion: 0.03 -Nodes (10): AuditLogRepository, DataBootstrapService, PlatformDataBootstrapModule, FakePgClient, DatasourceRepository, DatasourceService, DatasourceWorkflowService, PostgresExecutorService (+2 more) +Nodes (12): ok(), ChatController, DatasourceController, EvalController, GlossaryController, MemoryController, SettingsController, UserController (+4 more) ### Community 4 - "Community 4" -Cohesion: 0.04 -Nodes (10): ModelingSchemaChangeRepository, toScopeKey(), SaveViewFromRunUsecase, normalizeName(), SchemaChangeDetectorService, WorkspaceDatasourceController, normalizeName(), WorkspaceModelingSchemaChangeDetectorService (+2 more) - -### Community 5 - "Community 5" Cohesion: 0.05 Nodes (104): addWorkspaceDatasourceBindings(), addWorkspaceMembers(), AdminApiError, commitModelingSetup(), composeApiUrl(), createGlossaryAnchor(), createGlossaryTerm(), createPromptTemplate() (+96 more) -### Community 6 - "Community 6" +### Community 5 - "Community 5" Cohesion: 0.04 -Nodes (11): ok(), DatasourceController, EvalController, GlossaryController, MemoryController, SettingsController, UserController, WorkspaceController (+3 more) +Nodes (9): ModelingSchemaChangeRepository, toScopeKey(), normalizeName(), SchemaChangeDetectorService, WorkspaceDatasourceController, normalizeName(), WorkspaceModelingSchemaChangeDetectorService, normalizeTableName() (+1 more) + +### Community 6 - "Community 6" +Cohesion: 0.05 +Nodes (8): BuildRagIndexJob, asActor(), asActor(), GlossaryService, RagIndexBuilderService, RagIndexRepository, withActor(), asAdmin() ### Community 7 - "Community 7" Cohesion: 0.04 Nodes (9): LlmConfigRepository, toModelHealthStatus(), toProviderCode(), toProviderSyncStatus(), ModelRerankerAdapter, PromptTemplateService, ProviderCatalogService, ProviderRouterService (+1 more) ### Community 8 - "Community 8" -Cohesion: 0.06 -Nodes (6): asActor(), asActor(), GlossaryService, RagIndexRepository, withActor(), asAdmin() - -### Community 9 - "Community 9" Cohesion: 0.04 Nodes (65): handleKeyDown(), isRecord(), nextFieldId(), normalizeFunctionGroupHints(), resolveCalculatedFieldSaveError(), save(), toForm(), upsertField() (+57 more) -### Community 10 - "Community 10" +### Community 9 - "Community 9" Cohesion: 0.04 Nodes (57): BuildSemanticQueryNode, buildConversationKnowledgeAllowlistSet(), collectFiles(), compactLine(), createLineStarts(), extractImportReferences(), findRepoRoot(), indexToLineColumn() (+49 more) +### Community 10 - "Community 10" +Cohesion: 0.05 +Nodes (7): DataBootstrapService, PlatformDataBootstrapModule, FakePgClient, DatasourceRepository, DatasourceService, PostgresExecutorService, RelationshipDryRunService + ### Community 11 - "Community 11" -Cohesion: 0.04 -Nodes (9): ChatPostRunHooksService, HealthController, MysqlExecutorService, RagIngestionMetricsService, RagQualityController, RagQualityService, SemanticSpineShadowService, SqliteExecutorService (+1 more) +Cohesion: 0.06 +Nodes (7): LaneTimeoutError, RagRetrievalService, appendUnique(), fuseWithRrf(), laneOrder(), normalizeRankConstant(), stableSortLaneHits() ### Community 12 - "Community 12" -Cohesion: 0.06 -Nodes (58): createIdempotencyKey(), resolveDatasourceWorkflowApiError(), applySessionView(), dedupeSessions(), extractErrorCode(), init(), loadMessages(), mergeSessionMessages() (+50 more) +Cohesion: 0.04 +Nodes (8): HealthController, MysqlExecutorService, RagIngestionMetricsService, RagQualityController, RagQualityService, SemanticSpineShadowService, SqliteExecutorService, SqliteQueryService ### Community 13 - "Community 13" -Cohesion: 0.05 -Nodes (8): GraphAccelerationCircuitBreaker, GraphService, QueryExecutorRouterService, RelationshipPublishGateFacade, RowFilterRewriteService, SafetyCheckNode, SqlSafetyGuard, SqlTableAccessGuardService +Cohesion: 0.06 +Nodes (58): createIdempotencyKey(), resolveDatasourceWorkflowApiError(), applySessionView(), dedupeSessions(), extractErrorCode(), init(), loadMessages(), mergeSessionMessages() (+50 more) ### Community 14 - "Community 14" Cohesion: 0.08 -Nodes (7): normalizeValue(), WorkspaceAdminGuard, toMembershipKey(), toWorkspaceMemberRole(), toWorkspaceStatus(), WorkspaceRepository, WorkspaceService +Nodes (7): asNonEmptyString(), toBindingKey(), toRuleKey(), toTablePermissionKey(), toTablePermissionSetKey(), WorkspaceDatasourcePolicyRepository, WorkspaceDatasourceService ### Community 15 - "Community 15" -Cohesion: 0.05 -Nodes (4): MemoryPromotionPolicy, MemoryPromotionService, RagReplayRepository, RagRerankService +Cohesion: 0.08 +Nodes (7): normalizeValue(), WorkspaceAdminGuard, toMembershipKey(), toWorkspaceMemberRole(), toWorkspaceStatus(), WorkspaceRepository, WorkspaceService ### Community 16 - "Community 16" -Cohesion: 0.06 -Nodes (14): appendPlanningWarnings(), buildExplicitPinningEvidence(), createLangGraphNodeHandlers(), createNodeRuntime(), extractQualifiedColumns(), getCallbacks(), normalizeIdentifier(), summarize() (+6 more) +Cohesion: 0.05 +Nodes (5): AuditLogRepository, ChatPostRunHooksService, DatasourceWorkflowService, MemoryPromotionPolicy, MemoryPromotionService ### Community 17 - "Community 17" -Cohesion: 0.06 -Nodes (8): getChunkProfileConfig(), RagBudgetPolicy, RagChunkingService, sha256(), RagDocumentFactory, sha256(), RagEventConsumerService, SemanticRegistryService +Cohesion: 0.05 +Nodes (18): GraphBuilderService, appendPlanningWarnings(), buildExplicitPinningEvidence(), createLangGraphNodeHandlers(), createNodeRuntime(), extractQualifiedColumns(), getCallbacks(), normalizeIdentifier() (+10 more) ### Community 18 - "Community 18" -Cohesion: 0.09 -Nodes (7): DatasourceAccessPolicyService, asNonEmptyString(), toBindingKey(), toRuleKey(), toTablePermissionKey(), toTablePermissionSetKey(), WorkspaceDatasourcePolicyRepository +Cohesion: 0.05 +Nodes (7): GraphAccelerationCircuitBreaker, GraphService, QueryExecutorRouterService, RowFilterRewriteService, SafetyCheckNode, SqlSafetyGuard, SqlTableAccessGuardService ### Community 19 - "Community 19" Cohesion: 0.06 @@ -601,269 +608,269 @@ Cohesion: 0.08 Nodes (10): toPlatformUserStatus(), UserRepository, UserService, onConfirmBatchDelete(), onConfirmDeleteOne(), onConfirmResetPassword(), onSubmitEditor(), onToggleStatus() (+2 more) ### Community 21 - "Community 21" -Cohesion: 0.08 -Nodes (7): DeliveryContractMapper, createDeliverySandboxPolicy(), isSandboxFilesystemWriteAllowed(), isSandboxNetworkAllowed(), isSandboxProcessSpawnAllowed(), normalizeString(), SandboxRuntimeService +Cohesion: 0.07 +Nodes (3): ChatDeliveryEnrichmentService, RagReplayRepository, RagRerankService ### Community 22 - "Community 22" +Cohesion: 0.07 +Nodes (4): RagDocumentRepository, RelationshipPublishGateFacade, SaveViewFromRunUsecase, SavedPriorSqlService + +### Community 23 - "Community 23" Cohesion: 0.1 Nodes (6): ModelingGraphRepository, toScopeKey(), ModelingGraphValidator, nonEmpty(), normalizeId(), WorkspaceRelationshipService -### Community 23 - "Community 23" +### Community 24 - "Community 24" +Cohesion: 0.08 +Nodes (7): getChunkProfileConfig(), RagBudgetPolicy, RagChunkingService, sha256(), RagDocumentFactory, sha256(), SemanticRegistryService + +### Community 25 - "Community 25" Cohesion: 0.09 Nodes (19): BuildIntentPlanNode, buildRuleReasonCodes(), ClarificationFusionPolicy, mapRuleDecisionToEvidence(), unique(), ClarifyNode, buildDecision(), buildFallbackSlotFillingDecision() (+11 more) -### Community 24 - "Community 24" +### Community 26 - "Community 26" Cohesion: 0.11 Nodes (36): ApiClientRequestError, composeApiUrl(), createDatasource(), createSession(), DatasourceApiError, deleteSession(), getMessages(), getRun() (+28 more) -### Community 25 - "Community 25" +### Community 27 - "Community 27" Cohesion: 0.09 Nodes (35): createRunId(), withActor(), formatGovernanceError(), patchModel(), readRunIdFromQuery(), readWorkspaceIdFromQuery(), refreshModels(), resolveWorkspaceId() (+27 more) -### Community 26 - "Community 26" -Cohesion: 0.11 -Nodes (5): GraphBuilderService, createInitialLangGraphState(), normalizeAccessContext(), normalizeTraceContext(), LangsmithTraceService +### Community 28 - "Community 28" +Cohesion: 0.13 +Nodes (1): SemanticSpineRepository -### Community 27 - "Community 27" +### Community 29 - "Community 29" Cohesion: 0.12 Nodes (21): buildOperationNotice(), closeDialog(), closeDrawer(), createIdempotencyKey(), handleDelete(), handleToggleTerm(), load(), loadTerms() (+13 more) -### Community 28 - "Community 28" +### Community 30 - "Community 30" Cohesion: 0.12 Nodes (11): GeminiAdapter, resolveGeminiUrl(), isTimeoutAbortError(), LlmGatewayService, toErrorMessage(), LlmModelFactory, resolveProviderBaseUrl(), checkHealth() (+3 more) -### Community 29 - "Community 29" +### Community 31 - "Community 31" +Cohesion: 0.12 +Nodes (17): buildGraph(), collectPersistedPositionByFlowNodeId(), collectPositionPatchFromFlowNodes(), fromFlowNodeId(), isFinitePosition(), normalizeColumnKey(), normalizeNodeSectionEntries(), normalizeTableKey() (+9 more) + +### Community 32 - "Community 32" Cohesion: 0.25 Nodes (19): ensureRunLoaded(), isRecord(), mergeRunThinkingSteps(), normalizeArtifact(), normalizeDeliveryContract(), normalizeEvidence(), normalizeRunForVisibility(), normalizeSelectedContext() (+11 more) -### Community 30 - "Community 30" +### Community 33 - "Community 33" Cohesion: 0.13 Nodes (7): formatCell(), normalizeNullableText(), normalizeTableKey(), openEditDialog(), resolveModelRelationships(), resolveRelationshipCounterpartTable(), submitMetadataUpdate() -### Community 31 - "Community 31" -Cohesion: 0.25 -Nodes (1): ChatDeliveryEnrichmentService - -### Community 32 - "Community 32" +### Community 34 - "Community 34" Cohesion: 0.24 Nodes (1): RagAuditReplayService -### Community 33 - "Community 33" +### Community 35 - "Community 35" Cohesion: 0.22 Nodes (2): ClarificationSemanticEvaluatorService, SemanticEvaluatorTimeoutError -### Community 34 - "Community 34" +### Community 36 - "Community 36" Cohesion: 0.26 Nodes (10): createIdempotencyKey(), isConflictError(), isRecord(), normalizeTableNames(), readNumberCandidate(), resolveConflictPolicyVersion(), resolveConflictSummary(), setsEqual() (+2 more) -### Community 35 - "Community 35" +### Community 37 - "Community 37" Cohesion: 0.29 Nodes (1): FileDatasourceExecutorService -### Community 36 - "Community 36" +### Community 38 - "Community 38" Cohesion: 0.28 Nodes (4): GraphAccelerationAdapter, GraphAccelerationError, GraphAccelerationTimeoutError, GraphAccelerationUnsupportedOperatorError -### Community 37 - "Community 37" +### Community 39 - "Community 39" Cohesion: 0.26 Nodes (1): SemanticSpineShadowGateService -### Community 38 - "Community 38" +### Community 40 - "Community 40" Cohesion: 0.19 Nodes (2): GateMetricsService, TraceService -### Community 39 - "Community 39" +### Community 41 - "Community 41" +Cohesion: 0.27 +Nodes (6): createDeliverySandboxPolicy(), isSandboxFilesystemWriteAllowed(), isSandboxNetworkAllowed(), isSandboxProcessSpawnAllowed(), normalizeString(), SandboxRuntimeService + +### Community 42 - "Community 42" Cohesion: 0.36 Nodes (1): SkillRegistryService -### Community 40 - "Community 40" +### Community 43 - "Community 43" Cohesion: 0.25 Nodes (3): JsonFileSemanticSpineRepositoryDouble, SemanticSpineContractError, validateSemanticObject() -### Community 41 - "Community 41" +### Community 44 - "Community 44" Cohesion: 0.29 Nodes (2): BuildPhysicalPlanNode, PlannerCacheService -### Community 42 - "Community 42" +### Community 45 - "Community 45" +Cohesion: 0.27 +Nodes (6): handleOpenChange(), includesFailClosedHint(), normalizeAlerts(), resolveAutoExpandedSection(), resolveFailClosedAlerts(), sectionSeverityScore() + +### Community 46 - "Community 46" +Cohesion: 0.42 +Nodes (1): ResolveSavedPriorSqlNode + +### Community 47 - "Community 47" Cohesion: 0.25 Nodes (0): -### Community 43 - "Community 43" +### Community 48 - "Community 48" Cohesion: 0.25 Nodes (0): -### Community 44 - "Community 44" +### Community 49 - "Community 49" Cohesion: 0.29 Nodes (3): InMemorySemanticSpineRepositoryDouble, SemanticSpineContractError, validateSemanticObject() -### Community 45 - "Community 45" +### Community 50 - "Community 50" Cohesion: 0.29 Nodes (0): -### Community 46 - "Community 46" +### Community 51 - "Community 51" Cohesion: 0.29 Nodes (0): -### Community 47 - "Community 47" +### Community 52 - "Community 52" Cohesion: 0.33 Nodes (2): providerStatusLabel(), providerStatusVariant() -### Community 48 - "Community 48" -Cohesion: 0.38 -Nodes (4): createModelingFlowFieldHandleId(), createModelingFlowRelationshipHandleId(), ModelingFlowNode(), normalizeHandleToken() - -### Community 49 - "Community 49" -Cohesion: 0.48 -Nodes (6): computeElkLayout(), ElkLayoutTimeoutError, sortEdges(), sortNodes(), toElapsedMs(), withTimeout() - -### Community 50 - "Community 50" +### Community 53 - "Community 53" Cohesion: 0.57 Nodes (2): IngestionSourceAdapter, uniqueNonEmpty() -### Community 51 - "Community 51" +### Community 54 - "Community 54" Cohesion: 0.29 Nodes (1): KnowledgeChatSupportFacade -### Community 52 - "Community 52" +### Community 55 - "Community 55" Cohesion: 0.33 Nodes (0): -### Community 53 - "Community 53" +### Community 56 - "Community 56" Cohesion: 0.33 Nodes (0): -### Community 54 - "Community 54" +### Community 57 - "Community 57" Cohesion: 0.33 Nodes (0): -### Community 55 - "Community 55" +### Community 58 - "Community 58" Cohesion: 0.33 Nodes (0): -### Community 56 - "Community 56" +### Community 59 - "Community 59" Cohesion: 0.4 Nodes (3): buildReadySnapshot(), buildSnakeCaseSnapshot(), SemanticSpineRepositoryStub -### Community 57 - "Community 57" +### Community 60 - "Community 60" Cohesion: 0.33 Nodes (1): PolicyEvaluatorService -### Community 58 - "Community 58" +### Community 61 - "Community 61" Cohesion: 0.33 Nodes (4): RelationshipBridgeDto, RelationshipBridgeEndpointDto, RelationshipGraphEdgeDto, ReplaceWorkspaceRelationshipGraphDto -### Community 59 - "Community 59" +### Community 62 - "Community 62" Cohesion: 0.4 Nodes (1): ResizeObserver -### Community 60 - "Community 60" +### Community 63 - "Community 63" Cohesion: 0.4 Nodes (0): -### Community 61 - "Community 61" +### Community 64 - "Community 64" Cohesion: 0.4 Nodes (0): -### Community 62 - "Community 62" +### Community 65 - "Community 65" Cohesion: 0.4 Nodes (0): -### Community 63 - "Community 63" +### Community 66 - "Community 66" Cohesion: 0.5 Nodes (2): CarouselNext(), useCarousel() -### Community 64 - "Community 64" +### Community 67 - "Community 67" Cohesion: 0.6 Nodes (4): nextEdgeId(), RelationshipEdgeEditorDialog(), toEdge(), toForm() -### Community 65 - "Community 65" +### Community 68 - "Community 68" Cohesion: 0.4 Nodes (0): -### Community 66 - "Community 66" +### Community 69 - "Community 69" Cohesion: 0.4 Nodes (0): -### Community 67 - "Community 67" +### Community 70 - "Community 70" Cohesion: 0.5 Nodes (2): statusLabel(), statusVariant() -### Community 68 - "Community 68" +### Community 71 - "Community 71" Cohesion: 0.8 Nodes (4): normalizeConfidence(), normalizeEndpoint(), normalizeRelationshipGraphEdge(), normalizeRequired() -### Community 69 - "Community 69" +### Community 72 - "Community 72" Cohesion: 0.4 Nodes (4): UpsertDatasourceWorkflowDto, WorkflowDatasourcePayloadDto, WorkflowWorkspaceCreateDto, WorkflowWorkspacePayloadDto -### Community 70 - "Community 70" +### Community 73 - "Community 73" Cohesion: 0.5 Nodes (0): -### Community 71 - "Community 71" +### Community 74 - "Community 74" Cohesion: 0.5 Nodes (0): -### Community 72 - "Community 72" +### Community 75 - "Community 75" Cohesion: 0.5 Nodes (0): -### Community 73 - "Community 73" +### Community 76 - "Community 76" Cohesion: 0.5 Nodes (1): TimeoutEvaluator -### Community 74 - "Community 74" +### Community 77 - "Community 77" Cohesion: 0.5 Nodes (1): FakePgClient -### Community 75 - "Community 75" +### Community 78 - "Community 78" Cohesion: 0.83 Nodes (3): buildModelingRepository(), buildModelingService(), buildRelationshipService() -### Community 76 - "Community 76" +### Community 79 - "Community 79" Cohesion: 0.5 Nodes (1): RagModule -### Community 77 - "Community 77" +### Community 80 - "Community 80" Cohesion: 0.5 Nodes (1): ListPromptTemplatesQueryDto -### Community 78 - "Community 78" +### Community 81 - "Community 81" Cohesion: 0.5 Nodes (1): UpsertModelingSetupDto -### Community 79 - "Community 79" +### Community 82 - "Community 82" Cohesion: 0.5 Nodes (1): CreateGlossaryTermDto -### Community 80 - "Community 80" +### Community 83 - "Community 83" Cohesion: 0.5 Nodes (1): UpdateGlossaryTermDto -### Community 81 - "Community 81" +### Community 84 - "Community 84" Cohesion: 0.5 Nodes (3): ContextEnvelopeDto, ContextEnvelopeEntityMappingDto, ContextEnvelopeTimeRangeDto -### Community 82 - "Community 82" +### Community 85 - "Community 85" Cohesion: 0.5 Nodes (0): -### Community 83 - "Community 83" +### Community 86 - "Community 86" Cohesion: 1.0 Nodes (2): createDatasourceAndOpenSetupWizard(), openCreateToStep2() -### Community 84 - "Community 84" -Cohesion: 0.67 -Nodes (0): - -### Community 85 - "Community 85" -Cohesion: 0.67 -Nodes (0): - -### Community 86 - "Community 86" -Cohesion: 0.67 -Nodes (0): - ### Community 87 - "Community 87" Cohesion: 0.67 Nodes (0): @@ -905,8 +912,8 @@ Cohesion: 0.67 Nodes (0): ### Community 97 - "Community 97" -Cohesion: 1.0 -Nodes (2): resolveBadgeLabel(), resolveBadgeVariant() +Cohesion: 0.67 +Nodes (0): ### Community 98 - "Community 98" Cohesion: 0.67 @@ -917,8 +924,8 @@ Cohesion: 0.67 Nodes (0): ### Community 100 - "Community 100" -Cohesion: 0.67 -Nodes (0): +Cohesion: 1.0 +Nodes (2): resolveBadgeLabel(), resolveBadgeVariant() ### Community 101 - "Community 101" Cohesion: 0.67 @@ -930,143 +937,143 @@ Nodes (0): ### Community 103 - "Community 103" Cohesion: 0.67 -Nodes (1): DomainError +Nodes (0): ### Community 104 - "Community 104" Cohesion: 0.67 -Nodes (1): OpenRouterAdapter +Nodes (0): ### Community 105 - "Community 105" Cohesion: 0.67 -Nodes (1): KimiAdapter +Nodes (0): ### Community 106 - "Community 106" Cohesion: 0.67 -Nodes (1): VolcengineAdapter +Nodes (1): DomainError ### Community 107 - "Community 107" Cohesion: 0.67 -Nodes (1): MinimaxAdapter +Nodes (1): OpenRouterAdapter ### Community 108 - "Community 108" Cohesion: 0.67 -Nodes (1): TencentHunyuanAdapter +Nodes (1): KimiAdapter ### Community 109 - "Community 109" Cohesion: 0.67 -Nodes (1): SiliconflowAdapter +Nodes (1): VolcengineAdapter ### Community 110 - "Community 110" Cohesion: 0.67 -Nodes (1): DeepSeekAdapter +Nodes (1): MinimaxAdapter ### Community 111 - "Community 111" Cohesion: 0.67 -Nodes (1): OpenAiAdapter +Nodes (1): TencentHunyuanAdapter ### Community 112 - "Community 112" Cohesion: 0.67 -Nodes (1): TongyiAdapter +Nodes (1): SiliconflowAdapter ### Community 113 - "Community 113" Cohesion: 0.67 -Nodes (1): MemoryModule +Nodes (1): DeepSeekAdapter ### Community 114 - "Community 114" Cohesion: 0.67 -Nodes (1): ChatModule +Nodes (1): OpenAiAdapter ### Community 115 - "Community 115" Cohesion: 0.67 -Nodes (1): SendMessageDto +Nodes (1): TongyiAdapter ### Community 116 - "Community 116" Cohesion: 0.67 -Nodes (1): ListSessionsDto +Nodes (1): MemoryModule ### Community 117 - "Community 117" Cohesion: 0.67 -Nodes (1): CreateSessionDto +Nodes (1): ChatModule ### Community 118 - "Community 118" Cohesion: 0.67 -Nodes (1): RenameSessionDto +Nodes (1): SendMessageDto ### Community 119 - "Community 119" Cohesion: 0.67 -Nodes (1): AdminOnlyGuard +Nodes (1): ListSessionsDto ### Community 120 - "Community 120" Cohesion: 0.67 -Nodes (1): PlatformChatRuntimeFacade +Nodes (1): CreateSessionDto ### Community 121 - "Community 121" Cohesion: 0.67 -Nodes (1): GlossaryModule +Nodes (1): RenameSessionDto ### Community 122 - "Community 122" Cohesion: 0.67 -Nodes (1): RelationshipImpactSimulatorService +Nodes (1): AdminOnlyGuard ### Community 123 - "Community 123" Cohesion: 0.67 -Nodes (1): SemanticRegistryModule +Nodes (1): PlatformChatRuntimeFacade ### Community 124 - "Community 124" Cohesion: 0.67 -Nodes (1): GovernanceChatAccessFacade +Nodes (1): GlossaryModule ### Community 125 - "Community 125" Cohesion: 0.67 -Nodes (2): PreviewDatasourcePayloadDto, PreviewDatasourceTablesDto +Nodes (1): RelationshipImpactSimulatorService ### Community 126 - "Community 126" Cohesion: 0.67 -Nodes (1): UpdatePromptTemplateDto +Nodes (1): SemanticRegistryModule ### Community 127 - "Community 127" Cohesion: 0.67 -Nodes (1): CreatePromptTemplateDto +Nodes (1): GovernanceChatAccessFacade ### Community 128 - "Community 128" Cohesion: 0.67 -Nodes (1): UpsertModelingGraphDto +Nodes (2): PreviewDatasourcePayloadDto, PreviewDatasourceTablesDto ### Community 129 - "Community 129" Cohesion: 0.67 -Nodes (1): ListWorkspaceDatasourceTablePermissionsDto +Nodes (1): UpdatePromptTemplateDto ### Community 130 - "Community 130" Cohesion: 0.67 -Nodes (1): ResolveModelingSchemaChangeDto +Nodes (1): CreatePromptTemplateDto ### Community 131 - "Community 131" Cohesion: 0.67 -Nodes (1): ReplaceWorkspaceDatasourceTablePermissionsDto +Nodes (1): UpsertModelingGraphDto ### Community 132 - "Community 132" Cohesion: 0.67 -Nodes (1): ListUsersDto +Nodes (1): ListWorkspaceDatasourceTablePermissionsDto ### Community 133 - "Community 133" Cohesion: 0.67 -Nodes (1): ListGlossaryTermsQueryDto +Nodes (1): ResolveModelingSchemaChangeDto ### Community 134 - "Community 134" Cohesion: 0.67 -Nodes (1): RollbackGlossaryAnchorDto +Nodes (1): ReplaceWorkspaceDatasourceTablePermissionsDto ### Community 135 - "Community 135" -Cohesion: 1.0 -Nodes (0): +Cohesion: 0.67 +Nodes (1): ListUsersDto ### Community 136 - "Community 136" -Cohesion: 1.0 -Nodes (0): +Cohesion: 0.67 +Nodes (1): ListGlossaryTermsQueryDto ### Community 137 - "Community 137" -Cohesion: 1.0 -Nodes (0): +Cohesion: 0.67 +Nodes (1): RollbackGlossaryAnchorDto ### Community 138 - "Community 138" Cohesion: 1.0 @@ -1090,7 +1097,7 @@ Nodes (0): ### Community 143 - "Community 143" Cohesion: 1.0 -Nodes (1): ELK +Nodes (0): ### Community 144 - "Community 144" Cohesion: 1.0 @@ -1102,7 +1109,7 @@ Nodes (0): ### Community 146 - "Community 146" Cohesion: 1.0 -Nodes (0): +Nodes (1): ELK ### Community 147 - "Community 147" Cohesion: 1.0 @@ -1386,7 +1393,7 @@ Nodes (0): ### Community 217 - "Community 217" Cohesion: 1.0 -Nodes (1): AppModule +Nodes (0): ### Community 218 - "Community 218" Cohesion: 1.0 @@ -1394,207 +1401,207 @@ Nodes (0): ### Community 219 - "Community 219" Cohesion: 1.0 -Nodes (1): LlmModule +Nodes (0): ### Community 220 - "Community 220" Cohesion: 1.0 -Nodes (1): ApplyMemoryFeedbackDto +Nodes (1): AppModule ### Community 221 - "Community 221" Cohesion: 1.0 -Nodes (1): AppConfigModule +Nodes (0): ### Community 222 - "Community 222" Cohesion: 1.0 -Nodes (1): PlatformModule +Nodes (1): LlmModule ### Community 223 - "Community 223" Cohesion: 1.0 -Nodes (1): PlatformLlmModule +Nodes (1): ApplyMemoryFeedbackDto ### Community 224 - "Community 224" Cohesion: 1.0 -Nodes (1): PlatformConfigModule +Nodes (1): AppConfigModule ### Community 225 - "Community 225" Cohesion: 1.0 -Nodes (1): PlatformObservabilityModule +Nodes (1): PlatformModule ### Community 226 - "Community 226" Cohesion: 1.0 -Nodes (1): PlatformDataAccessModule +Nodes (1): PlatformLlmModule ### Community 227 - "Community 227" Cohesion: 1.0 -Nodes (1): PlatformDataQueryModule +Nodes (1): PlatformConfigModule ### Community 228 - "Community 228" Cohesion: 1.0 -Nodes (1): PlatformDataModule +Nodes (1): PlatformObservabilityModule ### Community 229 - "Community 229" Cohesion: 1.0 -Nodes (1): PlatformDataPersistenceModule +Nodes (1): PlatformDataAccessModule ### Community 230 - "Community 230" Cohesion: 1.0 -Nodes (1): ObservabilityModule +Nodes (1): PlatformDataQueryModule ### Community 231 - "Community 231" Cohesion: 1.0 -Nodes (1): SystemModule +Nodes (1): PlatformDataModule ### Community 232 - "Community 232" Cohesion: 1.0 -Nodes (1): KnowledgeModule +Nodes (1): PlatformDataPersistenceModule ### Community 233 - "Community 233" Cohesion: 1.0 -Nodes (1): SemanticSpineModule +Nodes (1): ObservabilityModule ### Community 234 - "Community 234" Cohesion: 1.0 -Nodes (1): SkillRegistryModule +Nodes (1): SystemModule ### Community 235 - "Community 235" Cohesion: 1.0 -Nodes (1): GovernanceModule +Nodes (1): KnowledgeModule ### Community 236 - "Community 236" Cohesion: 1.0 -Nodes (1): DatasourceModule +Nodes (1): SemanticSpineModule ### Community 237 - "Community 237" Cohesion: 1.0 -Nodes (1): CreateDatasourceDto +Nodes (1): SkillRegistryModule ### Community 238 - "Community 238" Cohesion: 1.0 -Nodes (1): UploadFileDatasourceDto +Nodes (1): GovernanceModule ### Community 239 - "Community 239" Cohesion: 1.0 -Nodes (1): UpdateDatasourceDto +Nodes (1): DatasourceModule ### Community 240 - "Community 240" Cohesion: 1.0 -Nodes (1): SettingsModule +Nodes (1): CreateDatasourceDto ### Community 241 - "Community 241" Cohesion: 1.0 -Nodes (1): BatchUpdateModelStatusDto +Nodes (1): UploadFileDatasourceDto ### Community 242 - "Community 242" Cohesion: 1.0 -Nodes (1): UpdateModelStatusDto +Nodes (1): UpdateDatasourceDto ### Community 243 - "Community 243" Cohesion: 1.0 -Nodes (1): RefreshProviderModelsDto +Nodes (1): SettingsModule ### Community 244 - "Community 244" Cohesion: 1.0 -Nodes (1): CreateProviderDto +Nodes (1): BatchUpdateModelStatusDto ### Community 245 - "Community 245" Cohesion: 1.0 -Nodes (1): GovernanceAccessModule +Nodes (1): UpdateModelStatusDto ### Community 246 - "Community 246" Cohesion: 1.0 -Nodes (1): WorkspaceModule +Nodes (1): RefreshProviderModelsDto ### Community 247 - "Community 247" Cohesion: 1.0 -Nodes (1): RemoveWorkspaceMembersDto +Nodes (1): CreateProviderDto ### Community 248 - "Community 248" Cohesion: 1.0 -Nodes (1): RenameWorkspaceDto +Nodes (1): GovernanceAccessModule ### Community 249 - "Community 249" Cohesion: 1.0 -Nodes (1): GetModelingPreviewDto +Nodes (1): WorkspaceModule ### Community 250 - "Community 250" Cohesion: 1.0 -Nodes (1): UpdateWorkspaceMemberRoleDto +Nodes (1): RemoveWorkspaceMembersDto ### Community 251 - "Community 251" Cohesion: 1.0 -Nodes (1): DetectModelingSchemaChangeDto +Nodes (1): RenameWorkspaceDto ### Community 252 - "Community 252" Cohesion: 1.0 -Nodes (1): ListWorkspaceMembersDto +Nodes (1): GetModelingPreviewDto ### Community 253 - "Community 253" Cohesion: 1.0 -Nodes (1): DeployModelingGraphDto +Nodes (1): UpdateWorkspaceMemberRoleDto ### Community 254 - "Community 254" Cohesion: 1.0 -Nodes (1): AddWorkspaceMemberDto +Nodes (1): DetectModelingSchemaChangeDto ### Community 255 - "Community 255" Cohesion: 1.0 -Nodes (1): CreateWorkspaceDto +Nodes (1): ListWorkspaceMembersDto ### Community 256 - "Community 256" Cohesion: 1.0 -Nodes (1): UserModule +Nodes (1): DeployModelingGraphDto ### Community 257 - "Community 257" Cohesion: 1.0 -Nodes (1): BatchDeleteUsersDto +Nodes (1): AddWorkspaceMemberDto ### Community 258 - "Community 258" Cohesion: 1.0 -Nodes (1): UpdateUserStatusDto +Nodes (1): CreateWorkspaceDto ### Community 259 - "Community 259" Cohesion: 1.0 -Nodes (1): UpdateUserDto +Nodes (1): UserModule ### Community 260 - "Community 260" Cohesion: 1.0 -Nodes (1): UserVariableDto +Nodes (1): BatchDeleteUsersDto ### Community 261 - "Community 261" Cohesion: 1.0 -Nodes (1): CreateUserDto +Nodes (1): UpdateUserStatusDto ### Community 262 - "Community 262" Cohesion: 1.0 -Nodes (1): EvalModule +Nodes (1): UpdateUserDto ### Community 263 - "Community 263" Cohesion: 1.0 -Nodes (1): RunEvaluationDto +Nodes (1): UserVariableDto ### Community 264 - "Community 264" Cohesion: 1.0 -Nodes (1): ConversationModule +Nodes (1): CreateUserDto ### Community 265 - "Community 265" Cohesion: 1.0 -Nodes (1): SaveViewFromRunDto +Nodes (1): EvalModule ### Community 266 - "Community 266" Cohesion: 1.0 -Nodes (1): AgentModule +Nodes (1): RunEvaluationDto ### Community 267 - "Community 267" Cohesion: 1.0 -Nodes (0): +Nodes (1): ConversationModule ### Community 268 - "Community 268" Cohesion: 1.0 -Nodes (0): +Nodes (1): SaveViewFromRunDto ### Community 269 - "Community 269" Cohesion: 1.0 -Nodes (0): +Nodes (1): AgentModule ### Community 270 - "Community 270" Cohesion: 1.0 @@ -2432,20 +2439,48 @@ Nodes (0): Cohesion: 1.0 Nodes (0): +### Community 479 - "Community 479" +Cohesion: 1.0 +Nodes (0): + +### Community 480 - "Community 480" +Cohesion: 1.0 +Nodes (0): + +### Community 481 - "Community 481" +Cohesion: 1.0 +Nodes (0): + +### Community 482 - "Community 482" +Cohesion: 1.0 +Nodes (0): + +### Community 483 - "Community 483" +Cohesion: 1.0 +Nodes (0): + +### Community 484 - "Community 484" +Cohesion: 1.0 +Nodes (0): + +### Community 485 - "Community 485" +Cohesion: 1.0 +Nodes (0): + ## Knowledge Gaps - **80 isolated node(s):** `ELK`, `ElkLayoutTimeoutError`, `AppModule`, `LlmModule`, `ApplyMemoryFeedbackDto` (+75 more) These have ≤1 connection - possible missing edges or undocumented components. -- **Thin community `Community 135`** (2 nodes): `pickSelectOption()`, `settings-modeling-relationship-editor.spec.tsx` +- **Thin community `Community 138`** (2 nodes): `pickSelectOption()`, `settings-modeling-relationship-editor.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 136`** (2 nodes): `openCreateToStep2()`, `data-sources-page.spec.tsx` +- **Thin community `Community 139`** (2 nodes): `openCreateToStep2()`, `data-sources-page.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 137`** (2 nodes): `SectionHarness()`, `rag-delivery-section.spec.tsx` +- **Thin community `Community 140`** (2 nodes): `SectionHarness()`, `rag-delivery-section.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 138`** (2 nodes): `waitForWorkspaceDatasourceReady()`, `settings-modeling-workspace.spec.tsx` +- **Thin community `Community 141`** (2 nodes): `waitForWorkspaceDatasourceReady()`, `settings-modeling-workspace.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 139`** (2 nodes): `createDelivery()`, `rag-delivery-panel.spec.tsx` +- **Thin community `Community 142`** (2 nodes): `createDelivery()`, `rag-delivery-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 140`** (2 nodes): `[ +- **Thin community `Community 143`** (2 nodes): `[ { id: "model.orders", tableName: "orders", @@ -2456,690 +2491,698 @@ Nodes (0): } ]()`, `settings-modeling-details-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 141`** (2 nodes): `onUnhandledRejection()`, `chat-panel.spec.tsx` +- **Thin community `Community 144`** (2 nodes): `onUnhandledRejection()`, `chat-panel.spec.tsx` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 145`** (2 nodes): `createSettingsView()`, `settings-mobile-smoke.spec.tsx` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 146`** (2 nodes): `ELK`, `elkjs.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 142`** (2 nodes): `createSettingsView()`, `settings-mobile-smoke.spec.tsx` +- **Thin community `Community 147`** (2 nodes): `RootLayout()`, `layout.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 143`** (2 nodes): `ELK`, `elkjs.d.ts` +- **Thin community `Community 148`** (2 nodes): `ChatPage()`, `page.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 144`** (2 nodes): `RootLayout()`, `layout.tsx` +- **Thin community `Community 149`** (2 nodes): `AppShell()`, `app-shell.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 145`** (2 nodes): `ChatPage()`, `page.tsx` +- **Thin community `Community 150`** (2 nodes): `AspectRatio()`, `aspect-ratio.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 146`** (2 nodes): `AppShell()`, `app-shell.tsx` +- **Thin community `Community 151`** (2 nodes): `StateBlock()`, `state-block.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 147`** (2 nodes): `AspectRatio()`, `aspect-ratio.tsx` +- **Thin community `Community 152`** (2 nodes): `DirectionProvider()`, `direction.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 148`** (2 nodes): `StateBlock()`, `state-block.tsx` +- **Thin community `Community 153`** (2 nodes): `cn()`, `tabs.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 149`** (2 nodes): `DirectionProvider()`, `direction.tsx` +- **Thin community `Community 154`** (2 nodes): `ButtonGroup()`, `button-group.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 150`** (2 nodes): `cn()`, `tabs.tsx` +- **Thin community `Community 155`** (2 nodes): `cn()`, `card.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 151`** (2 nodes): `ButtonGroup()`, `button-group.tsx` +- **Thin community `Community 156`** (2 nodes): `InputGroup()`, `input-group.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 152`** (2 nodes): `cn()`, `card.tsx` +- **Thin community `Community 157`** (2 nodes): `cn()`, `input-otp.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 153`** (2 nodes): `InputGroup()`, `input-group.tsx` +- **Thin community `Community 158`** (2 nodes): `HoverCard()`, `hover-card.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 154`** (2 nodes): `cn()`, `input-otp.tsx` +- **Thin community `Community 159`** (2 nodes): `cn()`, `field.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 155`** (2 nodes): `HoverCard()`, `hover-card.tsx` +- **Thin community `Community 160`** (2 nodes): `Label()`, `label.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 156`** (2 nodes): `cn()`, `field.tsx` +- **Thin community `Community 161`** (2 nodes): `cn()`, `empty.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 157`** (2 nodes): `Label()`, `label.tsx` +- **Thin community `Community 162`** (2 nodes): `TooltipContent()`, `tooltip.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 158`** (2 nodes): `cn()`, `empty.tsx` +- **Thin community `Community 163`** (2 nodes): `cn()`, `alert.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 159`** (2 nodes): `TooltipContent()`, `tooltip.tsx` +- **Thin community `Community 164`** (2 nodes): `Switch()`, `switch.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 160`** (2 nodes): `cn()`, `alert.tsx` +- **Thin community `Community 165`** (2 nodes): `cn()`, `command.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 161`** (2 nodes): `Switch()`, `switch.tsx` +- **Thin community `Community 166`** (2 nodes): `ToggleGroup()`, `toggle-group.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 162`** (2 nodes): `cn()`, `command.tsx` +- **Thin community `Community 167`** (2 nodes): `Avatar()`, `avatar.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 163`** (2 nodes): `ToggleGroup()`, `toggle-group.tsx` +- **Thin community `Community 168`** (2 nodes): `cn()`, `kbd.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 164`** (2 nodes): `Avatar()`, `avatar.tsx` +- **Thin community `Community 169`** (2 nodes): `SectionHeader()`, `section-header.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 165`** (2 nodes): `cn()`, `kbd.tsx` +- **Thin community `Community 170`** (2 nodes): `Badge()`, `badge.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 166`** (2 nodes): `SectionHeader()`, `section-header.tsx` +- **Thin community `Community 171`** (2 nodes): `cn()`, `table.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 167`** (2 nodes): `Badge()`, `badge.tsx` +- **Thin community `Community 172`** (2 nodes): `Separator()`, `separator.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 168`** (2 nodes): `cn()`, `table.tsx` +- **Thin community `Community 173`** (2 nodes): `Checkbox()`, `checkbox.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 169`** (2 nodes): `Separator()`, `separator.tsx` +- **Thin community `Community 174`** (2 nodes): `Spinner()`, `spinner.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 170`** (2 nodes): `Checkbox()`, `checkbox.tsx` +- **Thin community `Community 175`** (2 nodes): `Collapsible()`, `collapsible.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 171`** (2 nodes): `Spinner()`, `spinner.tsx` +- **Thin community `Community 176`** (2 nodes): `cn()`, `textarea.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 172`** (2 nodes): `Collapsible()`, `collapsible.tsx` +- **Thin community `Community 177`** (2 nodes): `Input()`, `input.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 173`** (2 nodes): `cn()`, `textarea.tsx` +- **Thin community `Community 178`** (2 nodes): `Skeleton()`, `skeleton.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 174`** (2 nodes): `Input()`, `input.tsx` +- **Thin community `Community 179`** (2 nodes): `workspace-editor-dialog.tsx`, `WorkspaceEditorDialog()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 175`** (2 nodes): `Skeleton()`, `skeleton.tsx` +- **Thin community `Community 180`** (2 nodes): `handleKeyDown()`, `modeling-context-drawer.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 176`** (2 nodes): `workspace-editor-dialog.tsx`, `WorkspaceEditorDialog()` +- **Thin community `Community 181`** (2 nodes): `resolveBlockingReasonGuidance()`, `modeling-deploy-panel.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 177`** (2 nodes): `handleKeyDown()`, `modeling-context-drawer.tsx` +- **Thin community `Community 182`** (2 nodes): `TextPart()`, `assistant-message.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 178`** (2 nodes): `resolveBlockingReasonGuidance()`, `modeling-deploy-panel.tsx` +- **Thin community `Community 183`** (2 nodes): `resolveMessageRunId()`, `assistant-thread.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 179`** (2 nodes): `TextPart()`, `assistant-message.tsx` +- **Thin community `Community 184`** (2 nodes): `ModelSelector()`, `model-selector.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 180`** (2 nodes): `resolveMessageRunId()`, `assistant-thread.tsx` +- **Thin community `Community 185`** (2 nodes): `SessionActions()`, `session-actions.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 181`** (2 nodes): `ModelSelector()`, `model-selector.tsx` +- **Thin community `Community 186`** (2 nodes): `SqlDebugDetails()`, `sql-debug-details.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 182`** (2 nodes): `SessionActions()`, `session-actions.tsx` +- **Thin community `Community 187`** (2 nodes): `SqlRunSummary()`, `sql-run-summary.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 183`** (2 nodes): `SqlDebugDetails()`, `sql-debug-details.tsx` +- **Thin community `Community 188`** (2 nodes): `isNumericValue()`, `sql-result-table.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 184`** (2 nodes): `SqlRunSummary()`, `sql-run-summary.tsx` +- **Thin community `Community 189`** (2 nodes): `statusVariant()`, `sql-execution-timeline.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 185`** (2 nodes): `isNumericValue()`, `sql-result-table.tsx` +- **Thin community `Community 190`** (2 nodes): `useIsMobile()`, `use-mobile.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 186`** (2 nodes): `statusVariant()`, `sql-execution-timeline.tsx` +- **Thin community `Community 191`** (2 nodes): `utils.ts`, `cn()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 187`** (2 nodes): `useIsMobile()`, `use-mobile.ts` +- **Thin community `Community 192`** (2 nodes): `createService()`, `sql-generation.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 188`** (2 nodes): `utils.ts`, `cn()` +- **Thin community `Community 193`** (2 nodes): `createService()`, `semantic-registry.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 189`** (2 nodes): `createService()`, `sql-generation.service.spec.ts` +- **Thin community `Community 194`** (2 nodes): `createBaseRun()`, `delivery-contract.mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 190`** (2 nodes): `createService()`, `semantic-registry.service.spec.ts` +- **Thin community `Community 195`** (2 nodes): `createRequest()`, `request-actor.middleware.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 191`** (2 nodes): `createBaseRun()`, `delivery-contract.mapper.spec.ts` +- **Thin community `Community 196`** (2 nodes): `workspace-admin.guard.spec.ts`, `createContext()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 192`** (2 nodes): `createRequest()`, `request-actor.middleware.spec.ts` +- **Thin community `Community 197`** (2 nodes): `reset()`, `langsmith-config.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 193`** (2 nodes): `workspace-admin.guard.spec.ts`, `createContext()` +- **Thin community `Community 198`** (2 nodes): `createNode()`, `build-semantic-query.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 194`** (2 nodes): `reset()`, `langsmith-config.spec.ts` +- **Thin community `Community 199`** (2 nodes): `createContext()`, `admin-only.guard.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 195`** (2 nodes): `createNode()`, `build-semantic-query.node.spec.ts` +- **Thin community `Community 200`** (2 nodes): `resetAppEnv()`, `config.module.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 196`** (2 nodes): `createContext()`, `admin-only.guard.spec.ts` +- **Thin community `Community 201`** (2 nodes): `writeRepoFile()`, `capability-boundary-check.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 197`** (2 nodes): `resetAppEnv()`, `config.module.spec.ts` +- **Thin community `Community 202`** (2 nodes): `createPolicy()`, `clarification-fusion.policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 198`** (2 nodes): `writeRepoFile()`, `capability-boundary-check.spec.ts` +- **Thin community `Community 203`** (2 nodes): `buildLongSchemaText()`, `rag-chunking.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 199`** (2 nodes): `createPolicy()`, `clarification-fusion.policy.spec.ts` +- **Thin community `Community 204`** (2 nodes): `datasource()`, `query-executor-router.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 200`** (2 nodes): `buildLongSchemaText()`, `rag-chunking.service.spec.ts` +- **Thin community `Community 205`** (2 nodes): `createNode()`, `build-intent-plan.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 201`** (2 nodes): `datasource()`, `query-executor-router.spec.ts` +- **Thin community `Community 206`** (2 nodes): `createBaseRun()`, `sandbox-failover.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 202`** (2 nodes): `createNode()`, `build-intent-plan.node.spec.ts` +- **Thin community `Community 207`** (2 nodes): `buildUsecase()`, `save-view-from-run.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 203`** (2 nodes): `createBaseRun()`, `sandbox-failover.spec.ts` +- **Thin community `Community 208`** (2 nodes): `workspace-relationship-api.spec.ts`, `buildService()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 204`** (2 nodes): `buildUsecase()`, `save-view-from-run.spec.ts` +- **Thin community `Community 209`** (2 nodes): `buildArtifact()`, `sandbox-isolation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 205`** (2 nodes): `workspace-relationship-api.spec.ts`, `buildService()` +- **Thin community `Community 210`** (2 nodes): `createRun()`, `rag-audit-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 206`** (2 nodes): `buildArtifact()`, `sandbox-isolation.spec.ts` +- **Thin community `Community 211`** (2 nodes): `datasource()`, `query-executor-router-table-permissions.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 207`** (2 nodes): `createRun()`, `rag-audit-replay.spec.ts` +- **Thin community `Community 212`** (2 nodes): `sleep()`, `rag-datasource-gate-metrics.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 208`** (2 nodes): `datasource()`, `query-executor-router-table-permissions.spec.ts` +- **Thin community `Community 213`** (2 nodes): `buildSnapshot()`, `semantic-spine-compiler.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 209`** (2 nodes): `sleep()`, `rag-datasource-gate-metrics.spec.ts` +- **Thin community `Community 214`** (2 nodes): `workspace-modeling-setup.spec.ts`, `buildService()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 210`** (2 nodes): `buildSnapshot()`, `semantic-spine-compiler.spec.ts` +- **Thin community `Community 215`** (2 nodes): `createRun()`, `memory-promotion.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 211`** (2 nodes): `workspace-modeling-setup.spec.ts`, `buildService()` +- **Thin community `Community 216`** (2 nodes): `buildSamples()`, `glossary-selected-context-gate.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 212`** (2 nodes): `createRun()`, `memory-promotion.spec.ts` +- **Thin community `Community 217`** (2 nodes): `readErrorMessage()`, `user-workspace-authz.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 213`** (2 nodes): `buildSamples()`, `glossary-selected-context-gate.spec.ts` +- **Thin community `Community 218`** (2 nodes): `readErrorMessage()`, `rule-group-authz.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 214`** (2 nodes): `readErrorMessage()`, `user-workspace-authz.spec.ts` +- **Thin community `Community 219`** (2 nodes): `workspace-datasource-authz.spec.ts`, `readErrorMessage()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 215`** (2 nodes): `readErrorMessage()`, `rule-group-authz.spec.ts` +- **Thin community `Community 220`** (2 nodes): `AppModule`, `app.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 216`** (2 nodes): `workspace-datasource-authz.spec.ts`, `readErrorMessage()` +- **Thin community `Community 221`** (2 nodes): `requestIdMiddleware()`, `request-id.middleware.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 217`** (2 nodes): `AppModule`, `app.module.ts` +- **Thin community `Community 222`** (2 nodes): `LlmModule`, `llm.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 218`** (2 nodes): `requestIdMiddleware()`, `request-id.middleware.ts` +- **Thin community `Community 223`** (2 nodes): `ApplyMemoryFeedbackDto`, `apply-memory-feedback.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 219`** (2 nodes): `LlmModule`, `llm.module.ts` +- **Thin community `Community 224`** (2 nodes): `AppConfigModule`, `config.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 220`** (2 nodes): `ApplyMemoryFeedbackDto`, `apply-memory-feedback.dto.ts` +- **Thin community `Community 225`** (2 nodes): `PlatformModule`, `platform.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 221`** (2 nodes): `AppConfigModule`, `config.module.ts` +- **Thin community `Community 226`** (2 nodes): `PlatformLlmModule`, `llm.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 222`** (2 nodes): `PlatformModule`, `platform.module.ts` +- **Thin community `Community 227`** (2 nodes): `PlatformConfigModule`, `config.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 223`** (2 nodes): `PlatformLlmModule`, `llm.module.ts` +- **Thin community `Community 228`** (2 nodes): `PlatformObservabilityModule`, `observability.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 224`** (2 nodes): `PlatformConfigModule`, `config.module.ts` +- **Thin community `Community 229`** (2 nodes): `PlatformDataAccessModule`, `access.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 225`** (2 nodes): `PlatformObservabilityModule`, `observability.module.ts` +- **Thin community `Community 230`** (2 nodes): `PlatformDataQueryModule`, `query.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 226`** (2 nodes): `PlatformDataAccessModule`, `access.module.ts` +- **Thin community `Community 231`** (2 nodes): `PlatformDataModule`, `data.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 227`** (2 nodes): `PlatformDataQueryModule`, `query.module.ts` +- **Thin community `Community 232`** (2 nodes): `PlatformDataPersistenceModule`, `persistence.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 228`** (2 nodes): `PlatformDataModule`, `data.module.ts` +- **Thin community `Community 233`** (2 nodes): `ObservabilityModule`, `observability.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 229`** (2 nodes): `PlatformDataPersistenceModule`, `persistence.module.ts` +- **Thin community `Community 234`** (2 nodes): `SystemModule`, `system.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 230`** (2 nodes): `ObservabilityModule`, `observability.module.ts` +- **Thin community `Community 235`** (2 nodes): `KnowledgeModule`, `knowledge.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 231`** (2 nodes): `SystemModule`, `system.module.ts` +- **Thin community `Community 236`** (2 nodes): `SemanticSpineModule`, `semantic-spine.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 232`** (2 nodes): `KnowledgeModule`, `knowledge.module.ts` +- **Thin community `Community 237`** (2 nodes): `SkillRegistryModule`, `skill-registry.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 233`** (2 nodes): `SemanticSpineModule`, `semantic-spine.module.ts` +- **Thin community `Community 238`** (2 nodes): `GovernanceModule`, `governance.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 234`** (2 nodes): `SkillRegistryModule`, `skill-registry.module.ts` +- **Thin community `Community 239`** (2 nodes): `DatasourceModule`, `datasource.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 235`** (2 nodes): `GovernanceModule`, `governance.module.ts` +- **Thin community `Community 240`** (2 nodes): `CreateDatasourceDto`, `create-datasource.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 236`** (2 nodes): `DatasourceModule`, `datasource.module.ts` +- **Thin community `Community 241`** (2 nodes): `UploadFileDatasourceDto`, `upload-file-datasource.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 237`** (2 nodes): `CreateDatasourceDto`, `create-datasource.dto.ts` +- **Thin community `Community 242`** (2 nodes): `UpdateDatasourceDto`, `update-datasource.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 238`** (2 nodes): `UploadFileDatasourceDto`, `upload-file-datasource.dto.ts` +- **Thin community `Community 243`** (2 nodes): `SettingsModule`, `settings.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 239`** (2 nodes): `UpdateDatasourceDto`, `update-datasource.dto.ts` +- **Thin community `Community 244`** (2 nodes): `BatchUpdateModelStatusDto`, `batch-update-model-status.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 240`** (2 nodes): `SettingsModule`, `settings.module.ts` +- **Thin community `Community 245`** (2 nodes): `UpdateModelStatusDto`, `update-model-status.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 241`** (2 nodes): `BatchUpdateModelStatusDto`, `batch-update-model-status.dto.ts` +- **Thin community `Community 246`** (2 nodes): `RefreshProviderModelsDto`, `refresh-provider-models.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 242`** (2 nodes): `UpdateModelStatusDto`, `update-model-status.dto.ts` +- **Thin community `Community 247`** (2 nodes): `CreateProviderDto`, `create-provider.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 243`** (2 nodes): `RefreshProviderModelsDto`, `refresh-provider-models.dto.ts` +- **Thin community `Community 248`** (2 nodes): `GovernanceAccessModule`, `access.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 244`** (2 nodes): `CreateProviderDto`, `create-provider.dto.ts` +- **Thin community `Community 249`** (2 nodes): `workspace.module.ts`, `WorkspaceModule` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 245`** (2 nodes): `GovernanceAccessModule`, `access.module.ts` +- **Thin community `Community 250`** (2 nodes): `RemoveWorkspaceMembersDto`, `remove-workspace-members.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 246`** (2 nodes): `workspace.module.ts`, `WorkspaceModule` +- **Thin community `Community 251`** (2 nodes): `RenameWorkspaceDto`, `rename-workspace.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 247`** (2 nodes): `RemoveWorkspaceMembersDto`, `remove-workspace-members.dto.ts` +- **Thin community `Community 252`** (2 nodes): `GetModelingPreviewDto`, `get-modeling-preview.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 248`** (2 nodes): `RenameWorkspaceDto`, `rename-workspace.dto.ts` +- **Thin community `Community 253`** (2 nodes): `UpdateWorkspaceMemberRoleDto`, `update-workspace-member-role.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 249`** (2 nodes): `GetModelingPreviewDto`, `get-modeling-preview.dto.ts` +- **Thin community `Community 254`** (2 nodes): `DetectModelingSchemaChangeDto`, `detect-modeling-schema-change.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 250`** (2 nodes): `UpdateWorkspaceMemberRoleDto`, `update-workspace-member-role.dto.ts` +- **Thin community `Community 255`** (2 nodes): `ListWorkspaceMembersDto`, `list-workspace-members.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 251`** (2 nodes): `DetectModelingSchemaChangeDto`, `detect-modeling-schema-change.dto.ts` +- **Thin community `Community 256`** (2 nodes): `DeployModelingGraphDto`, `deploy-modeling-graph.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 252`** (2 nodes): `ListWorkspaceMembersDto`, `list-workspace-members.dto.ts` +- **Thin community `Community 257`** (2 nodes): `AddWorkspaceMemberDto`, `add-workspace-member.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 253`** (2 nodes): `DeployModelingGraphDto`, `deploy-modeling-graph.dto.ts` +- **Thin community `Community 258`** (2 nodes): `CreateWorkspaceDto`, `create-workspace.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 254`** (2 nodes): `AddWorkspaceMemberDto`, `add-workspace-member.dto.ts` +- **Thin community `Community 259`** (2 nodes): `UserModule`, `user.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 255`** (2 nodes): `CreateWorkspaceDto`, `create-workspace.dto.ts` +- **Thin community `Community 260`** (2 nodes): `BatchDeleteUsersDto`, `batch-delete-users.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 256`** (2 nodes): `UserModule`, `user.module.ts` +- **Thin community `Community 261`** (2 nodes): `UpdateUserStatusDto`, `update-user-status.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 257`** (2 nodes): `BatchDeleteUsersDto`, `batch-delete-users.dto.ts` +- **Thin community `Community 262`** (2 nodes): `UpdateUserDto`, `update-user.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 258`** (2 nodes): `UpdateUserStatusDto`, `update-user-status.dto.ts` +- **Thin community `Community 263`** (2 nodes): `UserVariableDto`, `user-variable.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 259`** (2 nodes): `UpdateUserDto`, `update-user.dto.ts` +- **Thin community `Community 264`** (2 nodes): `CreateUserDto`, `create-user.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 260`** (2 nodes): `UserVariableDto`, `user-variable.dto.ts` +- **Thin community `Community 265`** (2 nodes): `EvalModule`, `eval.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 261`** (2 nodes): `CreateUserDto`, `create-user.dto.ts` +- **Thin community `Community 266`** (2 nodes): `RunEvaluationDto`, `run-evaluation.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 262`** (2 nodes): `EvalModule`, `eval.module.ts` +- **Thin community `Community 267`** (2 nodes): `ConversationModule`, `conversation.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 263`** (2 nodes): `RunEvaluationDto`, `run-evaluation.dto.ts` +- **Thin community `Community 268`** (2 nodes): `SaveViewFromRunDto`, `save-view-from-run.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 264`** (2 nodes): `ConversationModule`, `conversation.module.ts` +- **Thin community `Community 269`** (2 nodes): `AgentModule`, `agent.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 265`** (2 nodes): `SaveViewFromRunDto`, `save-view-from-run.dto.ts` +- **Thin community `Community 270`** (1 nodes): `semantic-spine.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 266`** (2 nodes): `AgentModule`, `agent.module.ts` +- **Thin community `Community 271`** (1 nodes): `modeling.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 267`** (1 nodes): `semantic-spine.ts` +- **Thin community `Community 272`** (1 nodes): `api.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 268`** (1 nodes): `modeling.ts` +- **Thin community `Community 273`** (1 nodes): `api.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 269`** (1 nodes): `api.d.ts` +- **Thin community `Community 274`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 270`** (1 nodes): `api.ts` +- **Thin community `Community 275`** (1 nodes): `index.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 271`** (1 nodes): `index.ts` +- **Thin community `Community 276`** (1 nodes): `api.contract.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 272`** (1 nodes): `index.d.ts` +- **Thin community `Community 277`** (1 nodes): `next-env.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 273`** (1 nodes): `api.contract.spec.ts` +- **Thin community `Community 278`** (1 nodes): `vitest.config.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 274`** (1 nodes): `next-env.d.ts` +- **Thin community `Community 279`** (1 nodes): `assistant-thinking-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 275`** (1 nodes): `vitest.config.ts` +- **Thin community `Community 280`** (1 nodes): `session-sidebar.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 276`** (1 nodes): `assistant-thinking-panel.spec.tsx` +- **Thin community `Community 281`** (1 nodes): `run-visibility-mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 277`** (1 nodes): `session-sidebar.spec.tsx` +- **Thin community `Community 282`** (1 nodes): `chat-rag-sync-stream-consistency.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 278`** (1 nodes): `run-visibility-mapper.spec.ts` +- **Thin community `Community 283`** (1 nodes): `settings-modeling-page-context.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 279`** (1 nodes): `chat-rag-sync-stream-consistency.spec.tsx` +- **Thin community `Community 284`** (1 nodes): `settings-relationship-publish-gate.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 280`** (1 nodes): `settings-modeling-page-context.spec.tsx` +- **Thin community `Community 285`** (1 nodes): `settings-users-management.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 281`** (1 nodes): `settings-relationship-publish-gate.spec.tsx` +- **Thin community `Community 286`** (1 nodes): `settings-modeling-model-drawer.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 282`** (1 nodes): `settings-users-management.spec.tsx` +- **Thin community `Community 287`** (1 nodes): `admin-api-client-modeling-contract.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 283`** (1 nodes): `settings-modeling-model-drawer.spec.tsx` +- **Thin community `Community 288`** (1 nodes): `settings-modeling-deploy-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 284`** (1 nodes): `admin-api-client-modeling-contract.spec.ts` +- **Thin community `Community 289`** (1 nodes): `settings-modeling-schema-change-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 285`** (1 nodes): `settings-modeling-deploy-panel.spec.tsx` +- **Thin community `Community 290`** (1 nodes): `settings-modeling-sidebar-tree.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 286`** (1 nodes): `settings-modeling-schema-change-panel.spec.tsx` +- **Thin community `Community 291`** (1 nodes): `settings-modeling-deploy.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 287`** (1 nodes): `settings-modeling-sidebar-tree.spec.tsx` +- **Thin community `Community 292`** (1 nodes): `settings-workspace-management.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 288`** (1 nodes): `settings-modeling-deploy.spec.tsx` +- **Thin community `Community 293`** (1 nodes): `chat-context-envelope-input.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 289`** (1 nodes): `settings-workspace-management.spec.tsx` +- **Thin community `Community 294`** (1 nodes): `chat-sql-preview.integration.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 290`** (1 nodes): `chat-context-envelope-input.spec.tsx` +- **Thin community `Community 295`** (1 nodes): `settings-workspace-datasource-table-permissions.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 291`** (1 nodes): `chat-sql-preview.integration.spec.tsx` +- **Thin community `Community 296`** (1 nodes): `settings-modeling-view-sync.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 292`** (1 nodes): `settings-workspace-datasource-table-permissions.spec.tsx` +- **Thin community `Community 297`** (1 nodes): `settings-retired-governance-management.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 293`** (1 nodes): `settings-modeling-view-sync.spec.tsx` +- **Thin community `Community 298`** (1 nodes): `sql-preview.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 294`** (1 nodes): `settings-retired-governance-management.spec.tsx` +- **Thin community `Community 299`** (1 nodes): `settings-relationship-modeling-canvas.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 295`** (1 nodes): `sql-preview.spec.tsx` +- **Thin community `Community 300`** (1 nodes): `settings-modeling-schema-change.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 296`** (1 nodes): `settings-relationship-modeling-canvas.spec.tsx` +- **Thin community `Community 301`** (1 nodes): `app-shell.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 297`** (1 nodes): `settings-modeling-schema-change.spec.tsx` +- **Thin community `Community 302`** (1 nodes): `settings-workspace-table-permission-entry.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 298`** (1 nodes): `app-shell.spec.tsx` +- **Thin community `Community 303`** (1 nodes): `settings-modeling-calculated-field-editor.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 299`** (1 nodes): `settings-workspace-table-permission-entry.spec.tsx` +- **Thin community `Community 304`** (1 nodes): `settings-modeling-complete-parity.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 300`** (1 nodes): `settings-modeling-calculated-field-editor.spec.tsx` +- **Thin community `Community 305`** (1 nodes): `chat-save-as-view.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 301`** (1 nodes): `settings-modeling-complete-parity.spec.tsx` +- **Thin community `Community 306`** (1 nodes): `rag-foundation-status-card.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 302`** (1 nodes): `chat-save-as-view.spec.tsx` +- **Thin community `Community 307`** (1 nodes): `settings-modeling-metadata-editor.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 303`** (1 nodes): `rag-foundation-status-card.spec.tsx` +- **Thin community `Community 308`** (1 nodes): `settings-modeling-flow-node-edge.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 304`** (1 nodes): `settings-modeling-metadata-editor.spec.tsx` +- **Thin community `Community 309`** (1 nodes): `chat-demo-flow.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 305`** (1 nodes): `settings-modeling-flow-node-edge.spec.tsx` +- **Thin community `Community 310`** (1 nodes): `chat-mobile-smoke.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 306`** (1 nodes): `chat-demo-flow.spec.tsx` +- **Thin community `Community 311`** (1 nodes): `platform-pages-smoke.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 307`** (1 nodes): `chat-mobile-smoke.spec.tsx` +- **Thin community `Community 312`** (1 nodes): `page.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 308`** (1 nodes): `platform-pages-smoke.spec.tsx` +- **Thin community `Community 313`** (1 nodes): `sql-preview.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 309`** (1 nodes): `page.tsx` +- **Thin community `Community 314`** (1 nodes): `steps.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 310`** (1 nodes): `sql-preview.tsx` +- **Thin community `Community 315`** (1 nodes): `slider.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 311`** (1 nodes): `steps.tsx` +- **Thin community `Community 316`** (1 nodes): `progress.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 312`** (1 nodes): `slider.tsx` +- **Thin community `Community 317`** (1 nodes): `sonner.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 313`** (1 nodes): `progress.tsx` +- **Thin community `Community 318`** (1 nodes): `button.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 314`** (1 nodes): `sonner.tsx` +- **Thin community `Community 319`** (1 nodes): `toggle.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 315`** (1 nodes): `button.tsx` +- **Thin community `Community 320`** (1 nodes): `workspace-datasource-table-permissions-dialog.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 316`** (1 nodes): `toggle.tsx` +- **Thin community `Community 321`** (1 nodes): `relationship-publish-panel.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 317`** (1 nodes): `workspace-datasource-table-permissions-dialog.tsx` +- **Thin community `Community 322`** (1 nodes): `modeling-flow-toolbar.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 318`** (1 nodes): `relationship-publish-panel.tsx` +- **Thin community `Community 323`** (1 nodes): `modeling-schema-change-panel.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 319`** (1 nodes): `modeling-flow-toolbar.tsx` +- **Thin community `Community 324`** (1 nodes): `elk-layout.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 320`** (1 nodes): `modeling-schema-change-panel.tsx` +- **Thin community `Community 325`** (1 nodes): `sql-inline-panel.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 321`** (1 nodes): `elk-layout.types.ts` +- **Thin community `Community 326`** (1 nodes): `message-list.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 322`** (1 nodes): `sql-inline-panel.tsx` +- **Thin community `Community 327`** (1 nodes): `assistant-composer.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 323`** (1 nodes): `message-list.tsx` +- **Thin community `Community 328`** (1 nodes): `workspace-selector-inline.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 324`** (1 nodes): `assistant-composer.tsx` +- **Thin community `Community 329`** (1 nodes): `jest.config.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 325`** (1 nodes): `workspace-selector-inline.tsx` +- **Thin community `Community 330`** (1 nodes): `jest.setup.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 326`** (1 nodes): `jest.config.ts` +- **Thin community `Community 331`** (1 nodes): `sandbox-policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 327`** (1 nodes): `jest.setup.ts` +- **Thin community `Community 332`** (1 nodes): `sql-table-access-guard.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 328`** (1 nodes): `sandbox-policy.spec.ts` +- **Thin community `Community 333`** (1 nodes): `redis-buffer.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 329`** (1 nodes): `sql-table-access-guard.spec.ts` +- **Thin community `Community 334`** (1 nodes): `llm-gateway.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 330`** (1 nodes): `redis-buffer.service.spec.ts` +- **Thin community `Community 335`** (1 nodes): `chat.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 331`** (1 nodes): `llm-gateway.service.spec.ts` +- **Thin community `Community 336`** (1 nodes): `openai-compatible.client.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 332`** (1 nodes): `chat.service.spec.ts` +- **Thin community `Community 337`** (1 nodes): `slot-filling-context.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 333`** (1 nodes): `openai-compatible.client.spec.ts` +- **Thin community `Community 338`** (1 nodes): `sql-prompt.builder.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 334`** (1 nodes): `slot-filling-context.spec.ts` +- **Thin community `Community 339`** (1 nodes): `resolve-saved-prior-sql.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 335`** (1 nodes): `sql-prompt.builder.spec.ts` +- **Thin community `Community 340`** (1 nodes): `tool-execution-guard.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 336`** (1 nodes): `tool-execution-guard.spec.ts` +- **Thin community `Community 341`** (1 nodes): `clarify.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 337`** (1 nodes): `clarify.node.spec.ts` +- **Thin community `Community 342`** (1 nodes): `planner-version-lock.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 338`** (1 nodes): `planner-version-lock.service.spec.ts` +- **Thin community `Community 343`** (1 nodes): `planner-cache.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 339`** (1 nodes): `planner-cache.service.spec.ts` +- **Thin community `Community 344`** (1 nodes): `tool-events.mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 340`** (1 nodes): `tool-events.mapper.spec.ts` +- **Thin community `Community 345`** (1 nodes): `tool-registry.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 341`** (1 nodes): `tool-registry.service.spec.ts` +- **Thin community `Community 346`** (1 nodes): `semantic-spine-context-pack.mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 342`** (1 nodes): `semantic-spine-context-pack.mapper.spec.ts` +- **Thin community `Community 347`** (1 nodes): `llm-model-factory.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 343`** (1 nodes): `llm-model-factory.spec.ts` +- **Thin community `Community 348`** (1 nodes): `rag-event-consumer.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 344`** (1 nodes): `rag-event-consumer.spec.ts` +- **Thin community `Community 349`** (1 nodes): `bootstrap.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 345`** (1 nodes): `bootstrap.spec.ts` +- **Thin community `Community 350`** (1 nodes): `skill-registry.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 346`** (1 nodes): `skill-registry.service.spec.ts` +- **Thin community `Community 351`** (1 nodes): `rrf-fusion.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 347`** (1 nodes): `rrf-fusion.spec.ts` +- **Thin community `Community 352`** (1 nodes): `sql-output-extractor.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 348`** (1 nodes): `sql-output-extractor.spec.ts` +- **Thin community `Community 353`** (1 nodes): `safety-check.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 349`** (1 nodes): `safety-check.node.spec.ts` +- **Thin community `Community 354`** (1 nodes): `knowledge-facade-contract.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 350`** (1 nodes): `knowledge-facade-contract.spec.ts` +- **Thin community `Community 355`** (1 nodes): `chat-langsmith-trace.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 351`** (1 nodes): `chat-langsmith-trace.spec.ts` +- **Thin community `Community 356`** (1 nodes): `query-executors.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 352`** (1 nodes): `query-executors.spec.ts` +- **Thin community `Community 357`** (1 nodes): `audit-log.repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 353`** (1 nodes): `audit-log.repository.spec.ts` +- **Thin community `Community 358`** (1 nodes): `rag-foundation-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 354`** (1 nodes): `rag-foundation-replay.spec.ts` +- **Thin community `Community 359`** (1 nodes): `rag-retrieval.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 355`** (1 nodes): `rag-retrieval.service.spec.ts` +- **Thin community `Community 360`** (1 nodes): `user-workspace-repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 356`** (1 nodes): `user-workspace-repository.spec.ts` +- **Thin community `Community 361`** (1 nodes): `rag-foundation-observability.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 357`** (1 nodes): `rag-foundation-observability.spec.ts` +- **Thin community `Community 362`** (1 nodes): `rag-incremental-refresh.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 358`** (1 nodes): `rag-incremental-refresh.spec.ts` +- **Thin community `Community 363`** (1 nodes): `graph-acceleration-fallback.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 359`** (1 nodes): `graph-acceleration-fallback.spec.ts` +- **Thin community `Community 364`** (1 nodes): `agent-rag-main-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 360`** (1 nodes): `agent-rag-main-flow.spec.ts` +- **Thin community `Community 365`** (1 nodes): `data-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 361`** (1 nodes): `data-flow.spec.ts` +- **Thin community `Community 366`** (1 nodes): `rule-group-schema.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 362`** (1 nodes): `rule-group-schema.spec.ts` +- **Thin community `Community 367`** (1 nodes): `workspace-modeling-deploy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 363`** (1 nodes): `workspace-modeling-deploy.spec.ts` +- **Thin community `Community 368`** (1 nodes): `session-repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 364`** (1 nodes): `session-repository.spec.ts` +- **Thin community `Community 369`** (1 nodes): `trace-lineage.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 365`** (1 nodes): `trace-lineage.spec.ts` +- **Thin community `Community 370`** (1 nodes): `rag-rerank.integration.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 366`** (1 nodes): `rag-rerank.integration.spec.ts` +- **Thin community `Community 371`** (1 nodes): `relationship-impact-simulation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 367`** (1 nodes): `relationship-impact-simulation.spec.ts` +- **Thin community `Community 372`** (1 nodes): `rag-multi-datasource-orchestrator.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 368`** (1 nodes): `rag-multi-datasource-orchestrator.spec.ts` +- **Thin community `Community 373`** (1 nodes): `rag-run-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 369`** (1 nodes): `rag-run-replay.spec.ts` +- **Thin community `Community 374`** (1 nodes): `workspace-modeling-schema-change.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 370`** (1 nodes): `workspace-modeling-schema-change.spec.ts` +- **Thin community `Community 375`** (1 nodes): `skill-registry-rag.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 371`** (1 nodes): `skill-registry-rag.spec.ts` +- **Thin community `Community 376`** (1 nodes): `semantic-registry.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 372`** (1 nodes): `semantic-registry.spec.ts` +- **Thin community `Community 377`** (1 nodes): `chat-run-template-trace.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 373`** (1 nodes): `chat-run-template-trace.spec.ts` +- **Thin community `Community 378`** (1 nodes): `workspace-datasource-table-permissions-schema.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 374`** (1 nodes): `workspace-datasource-table-permissions-schema.spec.ts` +- **Thin community `Community 379`** (1 nodes): `policy-evaluator.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 375`** (1 nodes): `policy-evaluator.spec.ts` +- **Thin community `Community 380`** (1 nodes): `rag-cache-version-invalidation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 376`** (1 nodes): `rag-cache-version-invalidation.spec.ts` +- **Thin community `Community 381`** (1 nodes): `rag-index-builder.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 377`** (1 nodes): `rag-index-builder.spec.ts` +- **Thin community `Community 382`** (1 nodes): `prisma-graph-baseline.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 378`** (1 nodes): `prisma-graph-baseline.spec.ts` +- **Thin community `Community 383`** (1 nodes): `agent-rag-degrade-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 379`** (1 nodes): `agent-rag-degrade-flow.spec.ts` +- **Thin community `Community 384`** (1 nodes): `langsmith-coverage.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 380`** (1 nodes): `langsmith-coverage.spec.ts` +- **Thin community `Community 385`** (1 nodes): `session-management.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 381`** (1 nodes): `session-management.spec.ts` +- **Thin community `Community 386`** (1 nodes): `planner-cache-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 382`** (1 nodes): `planner-cache-replay.spec.ts` +- **Thin community `Community 387`** (1 nodes): `planner-version-lock.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 383`** (1 nodes): `planner-version-lock.spec.ts` +- **Thin community `Community 388`** (1 nodes): `agent-context-pack.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 384`** (1 nodes): `agent-context-pack.spec.ts` +- **Thin community `Community 389`** (1 nodes): `graph-acceleration-circuit-breaker.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 385`** (1 nodes): `graph-acceleration-circuit-breaker.spec.ts` +- **Thin community `Community 390`** (1 nodes): `agent-sql-prompt-template-runtime.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 386`** (1 nodes): `agent-sql-prompt-template-runtime.spec.ts` +- **Thin community `Community 391`** (1 nodes): `agent-main-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 387`** (1 nodes): `agent-main-flow.spec.ts` +- **Thin community `Community 392`** (1 nodes): `datasource-repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 388`** (1 nodes): `datasource-repository.spec.ts` +- **Thin community `Community 393`** (1 nodes): `rag-performance-budget.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 389`** (1 nodes): `rag-performance-budget.spec.ts` +- **Thin community `Community 394`** (1 nodes): `agent-relationship-correction-loop.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 390`** (1 nodes): `agent-relationship-correction-loop.spec.ts` +- **Thin community `Community 395`** (1 nodes): `chat-context-envelope-accuracy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 391`** (1 nodes): `chat-context-envelope-accuracy.spec.ts` +- **Thin community `Community 396`** (1 nodes): `rag-index-activation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 392`** (1 nodes): `rag-index-activation.spec.ts` +- **Thin community `Community 397`** (1 nodes): `datasource-access-policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 393`** (1 nodes): `datasource-access-policy.spec.ts` +- **Thin community `Community 398`** (1 nodes): `eval-langsmith-trace.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 394`** (1 nodes): `eval-langsmith-trace.spec.ts` +- **Thin community `Community 399`** (1 nodes): `rag-quality.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 395`** (1 nodes): `rag-quality.spec.ts` +- **Thin community `Community 400`** (1 nodes): `eval-report.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 396`** (1 nodes): `eval-report.spec.ts` +- **Thin community `Community 401`** (1 nodes): `semantic-spine-shadow-gate.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 397`** (1 nodes): `semantic-spine-shadow-gate.spec.ts` +- **Thin community `Community 402`** (1 nodes): `agent-saved-prior-sql-shortcut.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 398`** (1 nodes): `chat-persistence-retry.spec.ts` +- **Thin community `Community 403`** (1 nodes): `chat-persistence-retry.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 399`** (1 nodes): `r3-gate-rehearsal.spec.ts` +- **Thin community `Community 404`** (1 nodes): `saved-prior-sql-ingestion.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 400`** (1 nodes): `r3-semantic-registry-migration.spec.ts` +- **Thin community `Community 405`** (1 nodes): `r3-gate-rehearsal.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 401`** (1 nodes): `workspace-service.spec.ts` +- **Thin community `Community 406`** (1 nodes): `r3-semantic-registry-migration.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 402`** (1 nodes): `rag-document-factory.spec.ts` +- **Thin community `Community 407`** (1 nodes): `workspace-service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 403`** (1 nodes): `r6-gate-readiness.spec.ts` +- **Thin community `Community 408`** (1 nodes): `rag-document-factory.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 404`** (1 nodes): `user-service.spec.ts` +- **Thin community `Community 409`** (1 nodes): `r6-gate-readiness.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 405`** (1 nodes): `provider-fallback.spec.ts` +- **Thin community `Community 410`** (1 nodes): `user-service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 406`** (1 nodes): `workspace-api.spec.ts` +- **Thin community `Community 411`** (1 nodes): `provider-fallback.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 407`** (1 nodes): `r6-release-rollback-rehearsal.e2e-spec.ts` +- **Thin community `Community 412`** (1 nodes): `saved-prior-sql-quality.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 408`** (1 nodes): `datasource-visibility-api.spec.ts` +- **Thin community `Community 413`** (1 nodes): `workspace-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 409`** (1 nodes): `workspace-datasource-api.spec.ts` +- **Thin community `Community 414`** (1 nodes): `r6-release-rollback-rehearsal.e2e-spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 410`** (1 nodes): `graph-acceleration-chaos.e2e-spec.ts` +- **Thin community `Community 415`** (1 nodes): `datasource-visibility-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 411`** (1 nodes): `clarification-hybrid-balance.acceptance.spec.ts` +- **Thin community `Community 416`** (1 nodes): `workspace-datasource-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 412`** (1 nodes): `datasource-workflow-api.spec.ts` +- **Thin community `Community 417`** (1 nodes): `graph-acceleration-chaos.e2e-spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 413`** (1 nodes): `rag-multi-datasource-isolation.spec.ts` +- **Thin community `Community 418`** (1 nodes): `clarification-hybrid-balance.acceptance.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 414`** (1 nodes): `settings-api.spec.ts` +- **Thin community `Community 419`** (1 nodes): `datasource-workflow-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 415`** (1 nodes): `workspace-datasource-audit.spec.ts` +- **Thin community `Community 420`** (1 nodes): `rag-multi-datasource-isolation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 416`** (1 nodes): `chat-api.spec.ts` +- **Thin community `Community 421`** (1 nodes): `settings-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 417`** (1 nodes): `rule-group-api.spec.ts` +- **Thin community `Community 422`** (1 nodes): `workspace-datasource-audit.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 418`** (1 nodes): `chat-table-permissions-policy.spec.ts` +- **Thin community `Community 423`** (1 nodes): `chat-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 419`** (1 nodes): `rag-r6-mixed-load.spec.ts` +- **Thin community `Community 424`** (1 nodes): `rule-group-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 420`** (1 nodes): `rag-cache-budget-load.spec.ts` +- **Thin community `Community 425`** (1 nodes): `chat-table-permissions-policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 421`** (1 nodes): `browser.ts` +- **Thin community `Community 426`** (1 nodes): `rag-r6-mixed-load.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 422`** (1 nodes): `client.ts` +- **Thin community `Community 427`** (1 nodes): `rag-cache-budget-load.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 423`** (1 nodes): `models.ts` +- **Thin community `Community 428`** (1 nodes): `browser.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 424`** (1 nodes): `commonInputTypes.ts` +- **Thin community `Community 429`** (1 nodes): `client.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 425`** (1 nodes): `enums.ts` +- **Thin community `Community 430`** (1 nodes): `models.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 426`** (1 nodes): `prismaNamespace.ts` +- **Thin community `Community 431`** (1 nodes): `commonInputTypes.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 427`** (1 nodes): `prismaNamespaceBrowser.ts` +- **Thin community `Community 432`** (1 nodes): `enums.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 428`** (1 nodes): `WorkspaceDatasourceTablePermission.ts` +- **Thin community `Community 433`** (1 nodes): `prismaNamespace.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 429`** (1 nodes): `WorkspaceDatasourceTablePermissionSet.ts` +- **Thin community `Community 434`** (1 nodes): `prismaNamespaceBrowser.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 430`** (1 nodes): `ModelCatalog.ts` +- **Thin community `Community 435`** (1 nodes): `WorkspaceDatasourceTablePermission.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 431`** (1 nodes): `RagDocument.ts` +- **Thin community `Community 436`** (1 nodes): `WorkspaceDatasourceTablePermissionSet.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 432`** (1 nodes): `WorkspaceDatasourceBinding.ts` +- **Thin community `Community 437`** (1 nodes): `ModelCatalog.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 433`** (1 nodes): `AgentAuditLog.ts` +- **Thin community `Community 438`** (1 nodes): `RagDocument.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 434`** (1 nodes): `SqlRun.ts` +- **Thin community `Community 439`** (1 nodes): `WorkspaceDatasourceBinding.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 435`** (1 nodes): `Workspace.ts` +- **Thin community `Community 440`** (1 nodes): `AgentAuditLog.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 436`** (1 nodes): `WorkspaceMember.ts` +- **Thin community `Community 441`** (1 nodes): `SqlRun.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 437`** (1 nodes): `RagChunkIndexEntry.ts` +- **Thin community `Community 442`** (1 nodes): `Workspace.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 438`** (1 nodes): `SemanticEdge.ts` +- **Thin community `Community 443`** (1 nodes): `WorkspaceMember.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 439`** (1 nodes): `GraphSnapshot.ts` +- **Thin community `Community 444`** (1 nodes): `RagChunkIndexEntry.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 440`** (1 nodes): `RagIndexVersion.ts` +- **Thin community `Community 445`** (1 nodes): `SemanticEdge.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 441`** (1 nodes): `GlossaryTerm.ts` +- **Thin community `Community 446`** (1 nodes): `GraphSnapshot.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 442`** (1 nodes): `SemanticMemory.ts` +- **Thin community `Community 447`** (1 nodes): `RagIndexVersion.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 443`** (1 nodes): `Datasource.ts` +- **Thin community `Community 448`** (1 nodes): `GlossaryTerm.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 444`** (1 nodes): `ProviderConfig.ts` +- **Thin community `Community 449`** (1 nodes): `SemanticMemory.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 445`** (1 nodes): `WorkspaceModelingGraphRevision.ts` +- **Thin community `Community 450`** (1 nodes): `Datasource.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 446`** (1 nodes): `SemanticSpineSnapshot.ts` +- **Thin community `Community 451`** (1 nodes): `ProviderConfig.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 447`** (1 nodes): `Message.ts` +- **Thin community `Community 452`** (1 nodes): `WorkspaceModelingGraphRevision.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 448`** (1 nodes): `Session.ts` +- **Thin community `Community 453`** (1 nodes): `SemanticSpineSnapshot.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 449`** (1 nodes): `RagChunk.ts` +- **Thin community `Community 454`** (1 nodes): `Message.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 450`** (1 nodes): `SemanticRegistryTerm.ts` +- **Thin community `Community 455`** (1 nodes): `Session.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 451`** (1 nodes): `GlossaryAnchor.ts` +- **Thin community `Community 456`** (1 nodes): `RagChunk.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 452`** (1 nodes): `PlatformUser.ts` +- **Thin community `Community 457`** (1 nodes): `SemanticRegistryTerm.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 453`** (1 nodes): `SemanticRegistryVersion.ts` +- **Thin community `Community 458`** (1 nodes): `GlossaryAnchor.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 454`** (1 nodes): `RagRunReplay.ts` +- **Thin community `Community 459`** (1 nodes): `PlatformUser.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 455`** (1 nodes): `EvaluationReport.ts` +- **Thin community `Community 460`** (1 nodes): `SemanticRegistryVersion.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 456`** (1 nodes): `PromptTemplate.ts` +- **Thin community `Community 461`** (1 nodes): `RagRunReplay.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 457`** (1 nodes): `express.d.ts` +- **Thin community `Community 462`** (1 nodes): `EvaluationReport.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 458`** (1 nodes): `llm-gateway.interface.ts` +- **Thin community `Community 463`** (1 nodes): `PromptTemplate.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 459`** (1 nodes): `provider-capabilities.ts` +- **Thin community `Community 464`** (1 nodes): `express.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 460`** (1 nodes): `provider-adapter.interface.ts` +- **Thin community `Community 465`** (1 nodes): `llm-gateway.interface.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 461`** (1 nodes): `index.ts` +- **Thin community `Community 466`** (1 nodes): `provider-capabilities.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 462`** (1 nodes): `index.ts` +- **Thin community `Community 467`** (1 nodes): `provider-adapter.interface.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 463`** (1 nodes): `modeling-graph.types.ts` +- **Thin community `Community 468`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 464`** (1 nodes): `index.ts` +- **Thin community `Community 469`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 465`** (1 nodes): `langsmith.types.ts` +- **Thin community `Community 470`** (1 nodes): `modeling-graph.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 466`** (1 nodes): `rag-retrieval.types.ts` +- **Thin community `Community 471`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 467`** (1 nodes): `knowledge-rag.contract.ts` +- **Thin community `Community 472`** (1 nodes): `langsmith.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 468`** (1 nodes): `knowledge-memory.contract.ts` +- **Thin community `Community 473`** (1 nodes): `rag-retrieval.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 469`** (1 nodes): `knowledge-facade.contract.ts` +- **Thin community `Community 474`** (1 nodes): `knowledge-rag.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 470`** (1 nodes): `knowledge-semantic-registry.contract.ts` +- **Thin community `Community 475`** (1 nodes): `knowledge-memory.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 471`** (1 nodes): `knowledge-glossary.contract.ts` +- **Thin community `Community 476`** (1 nodes): `knowledge-facade.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 472`** (1 nodes): `rag-retrieval.types.ts` +- **Thin community `Community 477`** (1 nodes): `knowledge-semantic-registry.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 473`** (1 nodes): `modeling-graph.validator.ts` +- **Thin community `Community 478`** (1 nodes): `knowledge-glossary.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 474`** (1 nodes): `modeling-graph.types.ts` +- **Thin community `Community 479`** (1 nodes): `rag-retrieval.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 475`** (1 nodes): `semantic-spine.types.ts` +- **Thin community `Community 480`** (1 nodes): `modeling-graph.validator.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 476`** (1 nodes): `index.ts` +- **Thin community `Community 481`** (1 nodes): `modeling-graph.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 477`** (1 nodes): `query-executor.interface.ts` +- **Thin community `Community 482`** (1 nodes): `semantic-spine.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 478`** (1 nodes): `agent.types.ts` +- **Thin community `Community 483`** (1 nodes): `index.ts` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 484`** (1 nodes): `query-executor.interface.ts` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 485`** (1 nodes): `agent.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. ## Suggested Questions _Questions this graph is uniquely positioned to answer:_ -- **Why does `ok()` connect `Community 6` to `Community 0`, `Community 11`, `Community 4`?** - _High betweenness centrality (0.027) - this node is a cross-community bridge._ -- **Why does `DatasourceService` connect `Community 3` to `Community 0`, `Community 18`?** - _High betweenness centrality (0.020) - this node is a cross-community bridge._ +- **Why does `ok()` connect `Community 3` to `Community 0`, `Community 12`, `Community 5`?** + _High betweenness centrality (0.044) - this node is a cross-community bridge._ +- **Why does `DatasourceService` connect `Community 10` to `Community 0`?** + _High betweenness centrality (0.022) - this node is a cross-community bridge._ - **Are the 85 inferred relationships involving `ok()` (e.g. with `.applyFeedback()` and `.createSession()`) actually correct?** _`ok()` has 85 INFERRED edges - model-reasoned connections that need verification._ - **What connects `ELK`, `ElkLayoutTimeoutError`, `AppModule` to the rest of the system?** @@ -3147,6 +3190,6 @@ _Questions this graph is uniquely positioned to answer:_ - **Should `Community 0` be split into smaller, more focused modules?** _Cohesion score 0.03 - nodes in this community are weakly interconnected._ - **Should `Community 1` be split into smaller, more focused modules?** - _Cohesion score 0.02 - nodes in this community are weakly interconnected._ + _Cohesion score 0.03 - nodes in this community are weakly interconnected._ - **Should `Community 2` be split into smaller, more focused modules?** - _Cohesion score 0.02 - nodes in this community are weakly interconnected._ \ No newline at end of file + _Cohesion score 0.03 - nodes in this community are weakly interconnected._ \ No newline at end of file diff --git a/graphify-out/graph.html b/graphify-out/graph.html index e1b8914..0624df1 100644 --- a/graphify-out/graph.html +++ b/graphify-out/graph.html @@ -50,12 +50,12 @@

Node Info

Communities

-
3421 nodes · 6510 edges · 479 communities
+
3481 nodes · 6638 edges · 486 communities
" + } + } + } + }); + + render( + + ); + + const summaryTab = screen.getByRole("tab", { name: /summary/i }); + const chartTab = screen.getByRole("tab", { name: /chart/i }); + const tableTab = screen.getByRole("tab", { name: /table/i }); + const sqlTab = screen.getByRole("tab", { name: /SQL 分区/i }); + + expect(summaryTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("近三个月收入持续增长。")).toBeInTheDocument(); + expect(screen.queryByText(/xss/i)).not.toBeInTheDocument(); + + await user.click(chartTab); + expect(chartTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByTestId("chatbi-bar-chart")).toBeInTheDocument(); + + await user.click(tableTab); + expect(screen.getByRole("columnheader", { name: "month" })).toBeInTheDocument(); + expect(screen.getByRole("columnheader", { name: "revenue" })).toBeInTheDocument(); + + await user.click(sqlTab); + expect(sqlTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText(/SQL 为次级证据层/i)).toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "打开运行详情" })); + expect(onRequestSqlDetails).toHaveBeenCalledTimes(1); + }); + + it("renders metric chart for metric artifacts", async () => { + const user = userEvent.setup(); + const run = createMockRun({ + delivery: { + answer: { + text: "本月 GMV 为 12,340。", + status: "executionResult", + provider: "mock" + }, + artifact: { + rowCount: 1, + hasError: false, + summary: { + text: "本月 GMV 为 12,340。" + }, + table: { + columns: ["label", "value"], + rowCount: 1, + rowsPreview: [{ label: "GMV", value: 12340 }], + previewRowCount: 1 + }, + chart: { + type: "metric", + mappings: { + label: "label", + value: "value" + }, + meta: { + title: "核心指标", + unit: "元" + } + }, + display: "metric", + validation: { + status: "valid" + } + } + } + }); + + render(); + + await user.click(screen.getByRole("tab", { name: /chart/i })); + expect(screen.getByTestId("chatbi-metric-card")).toBeInTheDocument(); + expect(screen.getByText("12,340")).toBeInTheDocument(); + expect(screen.getByText("元")).toBeInTheDocument(); + }); + + it("shows readable downgrade notice and skips empty chart render for table fallback", async () => { + const user = userEvent.setup(); + const run = createMockRun({ + delivery: { + answer: { + text: "结果已按表格展示。", + status: "executionResult", + provider: "mock" + }, + artifact: { + rowCount: 2, + hasError: false, + table: { + columns: ["region", "revenue"], + rowCount: 2, + rowsPreview: [ + { region: "JP", revenue: 100 }, + { region: "US", revenue: 80 } + ], + previewRowCount: 2 + }, + chart: { + type: "bar", + mappings: { + x: "region", + y: "revenue" + } + }, + display: "table", + fallback: { + display: "table", + reason: "字段稀疏,不满足图表渲染条件", + fromType: "bar" + }, + validation: { + status: "fallback" + } + } + } + }); + + render(); + + await user.click(screen.getByRole("tab", { name: /chart/i })); + expect(screen.getByText(/图表已降级为表格/i)).toBeInTheDocument(); + expect(screen.queryByTestId("chatbi-chart-canvas")).not.toBeInTheDocument(); + + await user.click(screen.getByRole("button", { name: "查看 Table 分区" })); + expect(screen.getByRole("columnheader", { name: "region" })).toBeInTheDocument(); + }); +}); diff --git a/apps/frontend/tests/unit/chatbi-result-tabs.spec.tsx b/apps/frontend/tests/unit/chatbi-result-tabs.spec.tsx new file mode 100644 index 0000000..8726405 --- /dev/null +++ b/apps/frontend/tests/unit/chatbi-result-tabs.spec.tsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import { + ChatBIResultTabs, + type ChatBIResultTabValue +} from "@/components/chat/chatbi-result-tabs"; +import { TabsContent } from "@/components/ui/tabs"; + +function ChatBIResultTabsHarness() { + const [value, setValue] = useState("summary"); + + return ( + + Summary pane + Chart pane + Table pane + SQL pane + + ); +} + +describe("ChatBIResultTabs", () => { + it("renders four icon tabs and switches sections with click", async () => { + const user = userEvent.setup(); + render(); + + const summaryTab = screen.getByRole("tab", { name: /summary/i }); + const chartTab = screen.getByRole("tab", { name: /chart/i }); + const tableTab = screen.getByRole("tab", { name: /table/i }); + const sqlTab = screen.getByRole("tab", { name: /SQL 分区/i }); + + expect(summaryTab).toHaveAttribute("aria-selected", "true"); + expect(chartTab).toHaveAttribute("aria-selected", "false"); + expect(tableTab).toHaveAttribute("aria-selected", "false"); + expect(sqlTab).toHaveAttribute("aria-selected", "false"); + + await user.click(chartTab); + expect(chartTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("Chart pane")).toBeInTheDocument(); + + await user.click(sqlTab); + expect(sqlTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("SQL pane")).toBeInTheDocument(); + }); + + it("supports Enter/Space keyboard activation with aria semantics", async () => { + const user = userEvent.setup(); + render(); + + const tableTab = screen.getByRole("tab", { name: /table/i }); + const sqlTab = screen.getByRole("tab", { name: /SQL 分区/i }); + + expect(tableTab).toHaveAttribute("aria-controls"); + expect(sqlTab).toHaveAttribute("aria-controls"); + + tableTab.focus(); + await user.keyboard("{Enter}"); + expect(tableTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("Table pane")).toBeInTheDocument(); + + sqlTab.focus(); + await user.keyboard("[Space]"); + expect(sqlTab).toHaveAttribute("aria-selected", "true"); + expect(screen.getByText("SQL pane")).toBeInTheDocument(); + }); +}); diff --git a/apps/frontend/tests/unit/run-visibility-mapper.spec.ts b/apps/frontend/tests/unit/run-visibility-mapper.spec.ts index 4c9d29d..4b8cd34 100644 --- a/apps/frontend/tests/unit/run-visibility-mapper.spec.ts +++ b/apps/frontend/tests/unit/run-visibility-mapper.spec.ts @@ -53,6 +53,197 @@ describe("run-visibility-mapper", () => { }); }); + it("normalizes extended ChatBI artifact fields while keeping legacy row preview fields", () => { + const normalized = normalizeDeliveryContract({ + answer: { + text: "收入总体上升。", + status: "executionResult", + provider: "mock-provider" + }, + evidence: { + run_id: "run-chatbi-1" + }, + artifact: { + sql: "select month, revenue from sales", + rowCount: 3, + hasError: false, + summary: { + text: "近三个月收入持续增长。", + highlights: ["+23%"] + }, + table: { + columns: ["month", "revenue"], + rowCount: 3, + rowsPreview: [ + { month: "2026-01", revenue: 100 }, + { month: "2026-02", revenue: 120 } + ] + }, + chart: { + type: "line", + mappings: { + x: "month", + y: "revenue" + } + }, + display: { + type: "line" + }, + validation: { + status: "valid" + }, + fallback: { + reason: "none" + }, + visualIntent: { + source: "llm", + normalizedIntent: { + chartType: "line", + x: "month", + y: "revenue" + } + } + } + }); + + const artifact = normalized?.artifact as Record | undefined; + expect(normalized?.evidence?.runId).toBe("run-chatbi-1"); + expect(artifact?.rowCount).toBe(3); + expect(artifact?.hasError).toBe(false); + expect((artifact?.summary as Record)?.text).toBe( + "近三个月收入持续增长。" + ); + expect((artifact?.display as Record)?.type).toBe("line"); + expect((artifact?.chart as Record)?.type).toBe("line"); + expect((artifact?.table as Record)?.rowCount).toBe(3); + expect((artifact?.visualIntent as Record)?.source).toBe("llm"); + }); + + it("keeps legacy artifact behavior stable", () => { + const normalized = normalizeDeliveryContract({ + answer: { + text: "legacy artifact", + status: "executionResult", + provider: "mock-provider" + }, + artifact: { + sql: "select 1", + columns: ["value"], + rowCount: 1, + rowsPreview: [{ value: 1 }], + hasError: false + } + }); + + const artifact = normalized?.artifact as Record | undefined; + expect(artifact?.sql).toBe("select 1"); + expect(artifact?.rowCount).toBe(1); + expect(artifact?.hasError).toBe(false); + expect(artifact?.display).toBeUndefined(); + expect(artifact?.summary).toBeUndefined(); + expect(artifact?.chart).toBeUndefined(); + }); + + it("drops malformed visualIntent/summary payload without crashing", () => { + const normalized = normalizeDeliveryContract({ + answer: { + text: "malformed payload", + status: "executionResult", + provider: "mock-provider" + }, + artifact: { + rowCount: 0, + hasError: false, + summary: () => "invalid-summary", + visualIntent: "not-an-object" + } + }); + + const artifact = normalized?.artifact as Record | undefined; + expect(normalized).toBeDefined(); + expect(artifact?.summary).toBeUndefined(); + expect(artifact?.visualIntent).toBeUndefined(); + }); + + it("downgrades to table display when chart mappings are missing", () => { + const normalized = normalizeDeliveryContract({ + answer: { + text: "chart fallback", + status: "executionResult", + provider: "mock-provider" + }, + artifact: { + rowCount: 2, + hasError: false, + table: { + columns: ["region", "revenue"], + rowCount: 2, + rowsPreview: [ + { region: "JP", revenue: 100 }, + { region: "US", revenue: 80 } + ] + }, + chart: { + type: "bar" + }, + display: { + type: "bar" + } + } + }); + + const artifact = normalized?.artifact as Record | undefined; + expect(artifact?.chart).toBeUndefined(); + expect((artifact?.display as Record)?.type).toBe("table"); + expect((artifact?.fallback as Record)?.reason).toBe( + "missing_chart_mappings" + ); + }); + + it("keeps run-first merge semantics and only fills new artifact fields from stream", () => { + const resolved = resolveVisibleDelivery({ + runDelivery: { + answer: { + text: "run-answer", + status: "executionResult", + provider: "run-provider" + }, + artifact: { + rowCount: 1, + hasError: false + } + }, + streamDelivery: { + answer: { + text: "stream-answer", + status: "executionResult", + provider: "stream-provider" + }, + artifact: { + rowCount: 99, + hasError: false, + summary: { + text: "stream-summary" + }, + table: { + columns: ["kpi"], + rowCount: 99, + rowsPreview: [{ kpi: "revenue" }] + }, + display: { + type: "table" + } + } + } + }); + + const artifact = resolved?.artifact as Record | undefined; + expect(resolved?.answer.provider).toBe("run-provider"); + expect(artifact?.rowCount).toBe(1); + expect((artifact?.summary as Record)?.text).toBe("stream-summary"); + expect((artifact?.table as Record)?.rowCount).toBe(99); + }); + it("keeps terminal state monotonic and never regresses to loading", () => { expect(transitionRunVisibilityStatus("success", "loading")).toBe("success"); expect(transitionRunVisibilityStatus("error", "loading")).toBe("error"); diff --git a/graphify-out/GRAPH_REPORT.md b/graphify-out/GRAPH_REPORT.md index 4dc1b31..cafdaf1 100644 --- a/graphify-out/GRAPH_REPORT.md +++ b/graphify-out/GRAPH_REPORT.md @@ -1,12 +1,12 @@ # Graph Report - /Users/lienli/Documents/GitHub/text2sql (2026-04-25) ## Corpus Check -- 681 files · ~1,028,789 words +- 697 files · ~1,056,512 words - Verdict: corpus is large enough that graph structure adds value. ## Summary -- 3481 nodes · 6638 edges · 486 communities detected -- Extraction: 79% EXTRACTED · 21% INFERRED · 0% AMBIGUOUS · INFERRED: 1395 edges (avg confidence: 0.8) +- 3616 nodes · 7005 edges · 488 communities detected +- Extraction: 79% EXTRACTED · 21% INFERRED · 0% AMBIGUOUS · INFERRED: 1501 edges (avg confidence: 0.8) - Token cost: 0 input · 0 output ## Community Hubs (Navigation) @@ -496,6 +496,8 @@ - [[_COMMUNITY_Community 483|Community 483]] - [[_COMMUNITY_Community 484|Community 484]] - [[_COMMUNITY_Community 485|Community 485]] +- [[_COMMUNITY_Community 486|Community 486]] +- [[_COMMUNITY_Community 487|Community 487]] ## God Nodes (most connected - your core abstractions) 1. `ok()` - 86 edges @@ -528,20 +530,20 @@ Cohesion: 0.03 Nodes (15): ChatPolicyGuardService, ChatRepository, ChatRunPersistenceService, ChatService, DatasourceAccessPolicyService, EvalService, ExecuteMessageUsecase, ExecuteSqlNode (+7 more) ### Community 1 - "Community 1" -Cohesion: 0.03 -Nodes (45): extractLatestUserText(), mapToThreadMessages(), parseSseEvents(), buildContextEnvelopeFromDraft(), parseEntityMappings(), splitByDelimiters(), trimOrUndefined(), DeliveryContractMapper (+37 more) +Cohesion: 0.02 +Nodes (42): extractLatestUserText(), mapToThreadMessages(), ChartBiGroundingGuard, ChartBiResultProfiler, buildContextEnvelopeFromDraft(), parseEntityMappings(), splitByDelimiters(), trimOrUndefined() (+34 more) ### Community 2 - "Community 2" Cohesion: 0.03 -Nodes (17): AppConfigService, DatasourceRegistryService, countTracedRuns(), main(), parseArgs(), computeLangsmithCoverage(), bootstrap(), readWorkspaceIdFromQuery() (+9 more) +Nodes (14): ok(), ChartBiValidator, ChatController, DatasourceController, EvalController, GlossaryController, MemoryController, SettingsController (+6 more) ### Community 3 - "Community 3" -Cohesion: 0.03 -Nodes (12): ok(), ChatController, DatasourceController, EvalController, GlossaryController, MemoryController, SettingsController, UserController (+4 more) +Cohesion: 0.02 +Nodes (32): AppConfigService, DatasourceRegistryService, countTracedRuns(), main(), parseArgs(), computeLangsmithCoverage(), bootstrap(), buildGraph() (+24 more) ### Community 4 - "Community 4" Cohesion: 0.05 -Nodes (104): addWorkspaceDatasourceBindings(), addWorkspaceMembers(), AdminApiError, commitModelingSetup(), composeApiUrl(), createGlossaryAnchor(), createGlossaryTerm(), createPromptTemplate() (+96 more) +Nodes (105): addWorkspaceDatasourceBindings(), addWorkspaceMembers(), AdminApiError, commitModelingSetup(), composeApiUrl(), createGlossaryAnchor(), createGlossaryTerm(), createPromptTemplate() (+97 more) ### Community 5 - "Community 5" Cohesion: 0.04 @@ -556,68 +558,68 @@ Cohesion: 0.04 Nodes (9): LlmConfigRepository, toModelHealthStatus(), toProviderCode(), toProviderSyncStatus(), ModelRerankerAdapter, PromptTemplateService, ProviderCatalogService, ProviderRouterService (+1 more) ### Community 8 - "Community 8" -Cohesion: 0.04 -Nodes (65): handleKeyDown(), isRecord(), nextFieldId(), normalizeFunctionGroupHints(), resolveCalculatedFieldSaveError(), save(), toForm(), upsertField() (+57 more) +Cohesion: 0.03 +Nodes (18): BuildPhysicalPlanNode, ChartBiIntentParser, parseSseEvents(), parseSseEvents(), GateMetricsService, parsePayload(), PlannerCacheService, RagAuditReplayService (+10 more) ### Community 9 - "Community 9" Cohesion: 0.04 -Nodes (57): BuildSemanticQueryNode, buildConversationKnowledgeAllowlistSet(), collectFiles(), compactLine(), createLineStarts(), extractImportReferences(), findRepoRoot(), indexToLineColumn() (+49 more) +Nodes (58): BuildSemanticQueryNode, ChartBiSpecCompiler, buildConversationKnowledgeAllowlistSet(), collectFiles(), compactLine(), createLineStarts(), extractImportReferences(), findRepoRoot() (+50 more) ### Community 10 - "Community 10" -Cohesion: 0.05 -Nodes (7): DataBootstrapService, PlatformDataBootstrapModule, FakePgClient, DatasourceRepository, DatasourceService, PostgresExecutorService, RelationshipDryRunService +Cohesion: 0.04 +Nodes (8): DataBootstrapService, PlatformDataBootstrapModule, FakePgClient, DatasourceRepository, DatasourceService, DatasourceWorkflowService, PostgresExecutorService, RelationshipDryRunService ### Community 11 - "Community 11" -Cohesion: 0.06 -Nodes (7): LaneTimeoutError, RagRetrievalService, appendUnique(), fuseWithRrf(), laneOrder(), normalizeRankConstant(), stableSortLaneHits() +Cohesion: 0.04 +Nodes (64): handleKeyDown(), isRecord(), nextFieldId(), normalizeFunctionGroupHints(), resolveCalculatedFieldSaveError(), save(), toForm(), upsertField() (+56 more) ### Community 12 - "Community 12" Cohesion: 0.04 -Nodes (8): HealthController, MysqlExecutorService, RagIngestionMetricsService, RagQualityController, RagQualityService, SemanticSpineShadowService, SqliteExecutorService, SqliteQueryService +Nodes (9): ChatPostRunHooksService, HealthController, MysqlExecutorService, RagIngestionMetricsService, RagQualityController, RagQualityService, SemanticSpineShadowService, SqliteExecutorService (+1 more) ### Community 13 - "Community 13" Cohesion: 0.06 -Nodes (58): createIdempotencyKey(), resolveDatasourceWorkflowApiError(), applySessionView(), dedupeSessions(), extractErrorCode(), init(), loadMessages(), mergeSessionMessages() (+50 more) +Nodes (7): LaneTimeoutError, RagRetrievalService, appendUnique(), fuseWithRrf(), laneOrder(), normalizeRankConstant(), stableSortLaneHits() ### Community 14 - "Community 14" -Cohesion: 0.08 -Nodes (7): asNonEmptyString(), toBindingKey(), toRuleKey(), toTablePermissionKey(), toTablePermissionSetKey(), WorkspaceDatasourcePolicyRepository, WorkspaceDatasourceService +Cohesion: 0.05 +Nodes (59): createIdempotencyKey(), resolveDatasourceWorkflowApiError(), setSessionModel(), applySessionView(), dedupeSessions(), extractErrorCode(), init(), loadMessages() (+51 more) ### Community 15 - "Community 15" Cohesion: 0.08 -Nodes (7): normalizeValue(), WorkspaceAdminGuard, toMembershipKey(), toWorkspaceMemberRole(), toWorkspaceStatus(), WorkspaceRepository, WorkspaceService +Nodes (7): asNonEmptyString(), toBindingKey(), toRuleKey(), toTablePermissionKey(), toTablePermissionSetKey(), WorkspaceDatasourcePolicyRepository, WorkspaceDatasourceService ### Community 16 - "Community 16" -Cohesion: 0.05 -Nodes (5): AuditLogRepository, ChatPostRunHooksService, DatasourceWorkflowService, MemoryPromotionPolicy, MemoryPromotionService +Cohesion: 0.07 +Nodes (7): AuditLogRepository, ModelingGraphRepository, toScopeKey(), ModelingGraphValidator, nonEmpty(), normalizeId(), WorkspaceRelationshipService ### Community 17 - "Community 17" Cohesion: 0.05 -Nodes (18): GraphBuilderService, appendPlanningWarnings(), buildExplicitPinningEvidence(), createLangGraphNodeHandlers(), createNodeRuntime(), extractQualifiedColumns(), getCallbacks(), normalizeIdentifier() (+10 more) +Nodes (8): GraphAccelerationCircuitBreaker, GraphService, QueryExecutorRouterService, RelationshipPublishGateFacade, RowFilterRewriteService, SafetyCheckNode, SqlSafetyGuard, SqlTableAccessGuardService ### Community 18 - "Community 18" -Cohesion: 0.05 -Nodes (7): GraphAccelerationCircuitBreaker, GraphService, QueryExecutorRouterService, RowFilterRewriteService, SafetyCheckNode, SqlSafetyGuard, SqlTableAccessGuardService +Cohesion: 0.08 +Nodes (7): normalizeValue(), WorkspaceAdminGuard, toMembershipKey(), toWorkspaceMemberRole(), toWorkspaceStatus(), WorkspaceRepository, WorkspaceService ### Community 19 - "Community 19" -Cohesion: 0.06 -Nodes (5): GenerateSqlNode, RagCacheKeyFactory, SqlGenerationService, SqlOutputExtractor, SqlPromptBuilder +Cohesion: 0.05 +Nodes (4): MemoryPromotionPolicy, MemoryPromotionService, RagReplayRepository, RagRerankService ### Community 20 - "Community 20" -Cohesion: 0.08 -Nodes (10): toPlatformUserStatus(), UserRepository, UserService, onConfirmBatchDelete(), onConfirmDeleteOne(), onConfirmResetPassword(), onSubmitEditor(), onToggleStatus() (+2 more) +Cohesion: 0.05 +Nodes (5): RagDocumentRepository, SaveViewFromRunUsecase, SavedPriorSqlService, ExpressionValidationError, WorkspaceCalculatedFieldExpressionValidatorService ### Community 21 - "Community 21" -Cohesion: 0.07 -Nodes (3): ChatDeliveryEnrichmentService, RagReplayRepository, RagRerankService +Cohesion: 0.05 +Nodes (19): GraphBuilderService, appendPlanningWarnings(), buildExplicitPinningEvidence(), createLangGraphNodeHandlers(), createNodeRuntime(), extractQualifiedColumns(), getCallbacks(), normalizeIdentifier() (+11 more) ### Community 22 - "Community 22" -Cohesion: 0.07 -Nodes (4): RagDocumentRepository, RelationshipPublishGateFacade, SaveViewFromRunUsecase, SavedPriorSqlService +Cohesion: 0.06 +Nodes (5): GenerateSqlNode, RagCacheKeyFactory, SqlGenerationService, SqlOutputExtractor, SqlPromptBuilder ### Community 23 - "Community 23" -Cohesion: 0.1 -Nodes (6): ModelingGraphRepository, toScopeKey(), ModelingGraphValidator, nonEmpty(), normalizeId(), WorkspaceRelationshipService +Cohesion: 0.08 +Nodes (10): toPlatformUserStatus(), UserRepository, UserService, onConfirmBatchDelete(), onConfirmDeleteOne(), onConfirmResetPassword(), onSubmitEditor(), onToggleStatus() (+2 more) ### Community 24 - "Community 24" Cohesion: 0.08 @@ -629,44 +631,44 @@ Nodes (19): BuildIntentPlanNode, buildRuleReasonCodes(), ClarificationFusionPoli ### Community 26 - "Community 26" Cohesion: 0.11 -Nodes (36): ApiClientRequestError, composeApiUrl(), createDatasource(), createSession(), DatasourceApiError, deleteSession(), getMessages(), getRun() (+28 more) +Nodes (39): ApiClientRequestError, composeApiUrl(), createDatasource(), createSession(), DatasourceApiError, deleteSession(), getMessages(), getRun() (+31 more) ### Community 27 - "Community 27" +Cohesion: 0.11 +Nodes (2): ChartBiArtifactService, ChatDeliveryEnrichmentService + +### Community 28 - "Community 28" Cohesion: 0.09 Nodes (35): createRunId(), withActor(), formatGovernanceError(), patchModel(), readRunIdFromQuery(), readWorkspaceIdFromQuery(), refreshModels(), resolveWorkspaceId() (+27 more) -### Community 28 - "Community 28" +### Community 29 - "Community 29" Cohesion: 0.13 Nodes (1): SemanticSpineRepository -### Community 29 - "Community 29" -Cohesion: 0.12 -Nodes (21): buildOperationNotice(), closeDialog(), closeDrawer(), createIdempotencyKey(), handleDelete(), handleToggleTerm(), load(), loadTerms() (+13 more) - ### Community 30 - "Community 30" Cohesion: 0.12 -Nodes (11): GeminiAdapter, resolveGeminiUrl(), isTimeoutAbortError(), LlmGatewayService, toErrorMessage(), LlmModelFactory, resolveProviderBaseUrl(), checkHealth() (+3 more) +Nodes (22): buildOperationNotice(), closeDialog(), closeDrawer(), createIdempotencyKey(), formatDate(), handleDelete(), handleToggleTerm(), load() (+14 more) ### Community 31 - "Community 31" -Cohesion: 0.12 -Nodes (17): buildGraph(), collectPersistedPositionByFlowNodeId(), collectPositionPatchFromFlowNodes(), fromFlowNodeId(), isFinitePosition(), normalizeColumnKey(), normalizeNodeSectionEntries(), normalizeTableKey() (+9 more) +Cohesion: 0.2 +Nodes (30): ensureRunLoaded(), isRecord(), mergeRunThinkingSteps(), normalizeArtifact(), normalizeChartArtifact(), normalizeChartMappings(), normalizeDeliveryContract(), normalizeDisplayArtifact() (+22 more) ### Community 32 - "Community 32" -Cohesion: 0.25 -Nodes (19): ensureRunLoaded(), isRecord(), mergeRunThinkingSteps(), normalizeArtifact(), normalizeDeliveryContract(), normalizeEvidence(), normalizeRunForVisibility(), normalizeSelectedContext() (+11 more) +Cohesion: 0.12 +Nodes (11): GeminiAdapter, resolveGeminiUrl(), isTimeoutAbortError(), LlmGatewayService, toErrorMessage(), LlmModelFactory, resolveProviderBaseUrl(), checkHealth() (+3 more) ### Community 33 - "Community 33" Cohesion: 0.13 Nodes (7): formatCell(), normalizeNullableText(), normalizeTableKey(), openEditDialog(), resolveModelRelationships(), resolveRelationshipCounterpartTable(), submitMetadataUpdate() ### Community 34 - "Community 34" -Cohesion: 0.24 -Nodes (1): RagAuditReplayService - -### Community 35 - "Community 35" Cohesion: 0.22 Nodes (2): ClarificationSemanticEvaluatorService, SemanticEvaluatorTimeoutError +### Community 35 - "Community 35" +Cohesion: 0.39 +Nodes (14): isRecord(), normalizeArtifact(), normalizeChart(), normalizeChartMappings(), normalizeDisplayType(), normalizeFallback(), normalizeSummary(), normalizeTable() (+6 more) + ### Community 36 - "Community 36" Cohesion: 0.26 Nodes (10): createIdempotencyKey(), isConflictError(), isRecord(), normalizeTableNames(), readNumberCandidate(), resolveConflictPolicyVersion(), resolveConflictSummary(), setsEqual() (+2 more) @@ -676,201 +678,201 @@ Cohesion: 0.29 Nodes (1): FileDatasourceExecutorService ### Community 38 - "Community 38" -Cohesion: 0.28 -Nodes (4): GraphAccelerationAdapter, GraphAccelerationError, GraphAccelerationTimeoutError, GraphAccelerationUnsupportedOperatorError +Cohesion: 0.25 +Nodes (6): createDeliverySandboxPolicy(), isSandboxFilesystemWriteAllowed(), isSandboxNetworkAllowed(), isSandboxProcessSpawnAllowed(), normalizeString(), SandboxRuntimeService ### Community 39 - "Community 39" -Cohesion: 0.26 -Nodes (1): SemanticSpineShadowGateService +Cohesion: 0.28 +Nodes (4): GraphAccelerationAdapter, GraphAccelerationError, GraphAccelerationTimeoutError, GraphAccelerationUnsupportedOperatorError ### Community 40 - "Community 40" -Cohesion: 0.19 -Nodes (2): GateMetricsService, TraceService - -### Community 41 - "Community 41" -Cohesion: 0.27 -Nodes (6): createDeliverySandboxPolicy(), isSandboxFilesystemWriteAllowed(), isSandboxNetworkAllowed(), isSandboxProcessSpawnAllowed(), normalizeString(), SandboxRuntimeService - -### Community 42 - "Community 42" Cohesion: 0.36 Nodes (1): SkillRegistryService -### Community 43 - "Community 43" -Cohesion: 0.25 -Nodes (3): JsonFileSemanticSpineRepositoryDouble, SemanticSpineContractError, validateSemanticObject() - -### Community 44 - "Community 44" -Cohesion: 0.29 -Nodes (2): BuildPhysicalPlanNode, PlannerCacheService - -### Community 45 - "Community 45" +### Community 41 - "Community 41" Cohesion: 0.27 Nodes (6): handleOpenChange(), includesFailClosedHint(), normalizeAlerts(), resolveAutoExpandedSection(), resolveFailClosedAlerts(), sectionSeverityScore() -### Community 46 - "Community 46" +### Community 42 - "Community 42" Cohesion: 0.42 Nodes (1): ResolveSavedPriorSqlNode -### Community 47 - "Community 47" +### Community 43 - "Community 43" Cohesion: 0.25 Nodes (0): -### Community 48 - "Community 48" +### Community 44 - "Community 44" Cohesion: 0.25 Nodes (0): -### Community 49 - "Community 49" +### Community 45 - "Community 45" Cohesion: 0.29 Nodes (3): InMemorySemanticSpineRepositoryDouble, SemanticSpineContractError, validateSemanticObject() -### Community 50 - "Community 50" +### Community 46 - "Community 46" Cohesion: 0.29 Nodes (0): -### Community 51 - "Community 51" +### Community 47 - "Community 47" Cohesion: 0.29 Nodes (0): -### Community 52 - "Community 52" +### Community 48 - "Community 48" Cohesion: 0.33 Nodes (2): providerStatusLabel(), providerStatusVariant() -### Community 53 - "Community 53" +### Community 49 - "Community 49" Cohesion: 0.57 Nodes (2): IngestionSourceAdapter, uniqueNonEmpty() -### Community 54 - "Community 54" +### Community 50 - "Community 50" Cohesion: 0.29 Nodes (1): KnowledgeChatSupportFacade -### Community 55 - "Community 55" -Cohesion: 0.33 -Nodes (0): - -### Community 56 - "Community 56" +### Community 51 - "Community 51" Cohesion: 0.33 Nodes (0): -### Community 57 - "Community 57" +### Community 52 - "Community 52" Cohesion: 0.33 Nodes (0): -### Community 58 - "Community 58" +### Community 53 - "Community 53" Cohesion: 0.33 Nodes (0): -### Community 59 - "Community 59" +### Community 54 - "Community 54" Cohesion: 0.4 Nodes (3): buildReadySnapshot(), buildSnakeCaseSnapshot(), SemanticSpineRepositoryStub -### Community 60 - "Community 60" +### Community 55 - "Community 55" Cohesion: 0.33 Nodes (1): PolicyEvaluatorService -### Community 61 - "Community 61" +### Community 56 - "Community 56" Cohesion: 0.33 Nodes (4): RelationshipBridgeDto, RelationshipBridgeEndpointDto, RelationshipGraphEdgeDto, ReplaceWorkspaceRelationshipGraphDto -### Community 62 - "Community 62" +### Community 57 - "Community 57" Cohesion: 0.4 Nodes (1): ResizeObserver -### Community 63 - "Community 63" +### Community 58 - "Community 58" Cohesion: 0.4 Nodes (0): -### Community 64 - "Community 64" +### Community 59 - "Community 59" Cohesion: 0.4 Nodes (0): -### Community 65 - "Community 65" +### Community 60 - "Community 60" Cohesion: 0.4 Nodes (0): -### Community 66 - "Community 66" +### Community 61 - "Community 61" Cohesion: 0.5 Nodes (2): CarouselNext(), useCarousel() -### Community 67 - "Community 67" +### Community 62 - "Community 62" Cohesion: 0.6 Nodes (4): nextEdgeId(), RelationshipEdgeEditorDialog(), toEdge(), toForm() -### Community 68 - "Community 68" +### Community 63 - "Community 63" Cohesion: 0.4 Nodes (0): -### Community 69 - "Community 69" +### Community 64 - "Community 64" Cohesion: 0.4 Nodes (0): -### Community 70 - "Community 70" +### Community 65 - "Community 65" Cohesion: 0.5 Nodes (2): statusLabel(), statusVariant() -### Community 71 - "Community 71" +### Community 66 - "Community 66" +Cohesion: 0.4 +Nodes (0): + +### Community 67 - "Community 67" Cohesion: 0.8 Nodes (4): normalizeConfidence(), normalizeEndpoint(), normalizeRelationshipGraphEdge(), normalizeRequired() -### Community 72 - "Community 72" +### Community 68 - "Community 68" Cohesion: 0.4 Nodes (4): UpsertDatasourceWorkflowDto, WorkflowDatasourcePayloadDto, WorkflowWorkspaceCreateDto, WorkflowWorkspacePayloadDto -### Community 73 - "Community 73" +### Community 69 - "Community 69" Cohesion: 0.5 Nodes (0): -### Community 74 - "Community 74" +### Community 70 - "Community 70" Cohesion: 0.5 Nodes (0): -### Community 75 - "Community 75" +### Community 71 - "Community 71" Cohesion: 0.5 Nodes (0): -### Community 76 - "Community 76" +### Community 72 - "Community 72" Cohesion: 0.5 Nodes (1): TimeoutEvaluator -### Community 77 - "Community 77" +### Community 73 - "Community 73" Cohesion: 0.5 Nodes (1): FakePgClient -### Community 78 - "Community 78" +### Community 74 - "Community 74" Cohesion: 0.83 Nodes (3): buildModelingRepository(), buildModelingService(), buildRelationshipService() -### Community 79 - "Community 79" +### Community 75 - "Community 75" Cohesion: 0.5 Nodes (1): RagModule -### Community 80 - "Community 80" +### Community 76 - "Community 76" Cohesion: 0.5 Nodes (1): ListPromptTemplatesQueryDto -### Community 81 - "Community 81" +### Community 77 - "Community 77" Cohesion: 0.5 Nodes (1): UpsertModelingSetupDto -### Community 82 - "Community 82" +### Community 78 - "Community 78" Cohesion: 0.5 Nodes (1): CreateGlossaryTermDto -### Community 83 - "Community 83" +### Community 79 - "Community 79" Cohesion: 0.5 Nodes (1): UpdateGlossaryTermDto -### Community 84 - "Community 84" +### Community 80 - "Community 80" Cohesion: 0.5 Nodes (3): ContextEnvelopeDto, ContextEnvelopeEntityMappingDto, ContextEnvelopeTimeRangeDto -### Community 85 - "Community 85" +### Community 81 - "Community 81" Cohesion: 0.5 Nodes (0): -### Community 86 - "Community 86" +### Community 82 - "Community 82" Cohesion: 1.0 Nodes (2): createDatasourceAndOpenSetupWizard(), openCreateToStep2() +### Community 83 - "Community 83" +Cohesion: 0.67 +Nodes (0): + +### Community 84 - "Community 84" +Cohesion: 0.67 +Nodes (0): + +### Community 85 - "Community 85" +Cohesion: 0.67 +Nodes (0): + +### Community 86 - "Community 86" +Cohesion: 0.67 +Nodes (0): + ### Community 87 - "Community 87" Cohesion: 0.67 Nodes (0): @@ -916,16 +918,16 @@ Cohesion: 0.67 Nodes (0): ### Community 98 - "Community 98" -Cohesion: 0.67 -Nodes (0): +Cohesion: 1.0 +Nodes (2): resolveBadgeLabel(), resolveBadgeVariant() ### Community 99 - "Community 99" Cohesion: 0.67 Nodes (0): ### Community 100 - "Community 100" -Cohesion: 1.0 -Nodes (2): resolveBadgeLabel(), resolveBadgeVariant() +Cohesion: 0.67 +Nodes (0): ### Community 101 - "Community 101" Cohesion: 0.67 @@ -937,143 +939,143 @@ Nodes (0): ### Community 103 - "Community 103" Cohesion: 0.67 -Nodes (0): +Nodes (1): DomainError ### Community 104 - "Community 104" Cohesion: 0.67 -Nodes (0): +Nodes (1): OpenRouterAdapter ### Community 105 - "Community 105" Cohesion: 0.67 -Nodes (0): +Nodes (1): KimiAdapter ### Community 106 - "Community 106" Cohesion: 0.67 -Nodes (1): DomainError +Nodes (1): VolcengineAdapter ### Community 107 - "Community 107" Cohesion: 0.67 -Nodes (1): OpenRouterAdapter +Nodes (1): MinimaxAdapter ### Community 108 - "Community 108" Cohesion: 0.67 -Nodes (1): KimiAdapter +Nodes (1): TencentHunyuanAdapter ### Community 109 - "Community 109" Cohesion: 0.67 -Nodes (1): VolcengineAdapter +Nodes (1): SiliconflowAdapter ### Community 110 - "Community 110" Cohesion: 0.67 -Nodes (1): MinimaxAdapter +Nodes (1): DeepSeekAdapter ### Community 111 - "Community 111" Cohesion: 0.67 -Nodes (1): TencentHunyuanAdapter +Nodes (1): OpenAiAdapter ### Community 112 - "Community 112" Cohesion: 0.67 -Nodes (1): SiliconflowAdapter +Nodes (1): TongyiAdapter ### Community 113 - "Community 113" Cohesion: 0.67 -Nodes (1): DeepSeekAdapter +Nodes (1): MemoryModule ### Community 114 - "Community 114" Cohesion: 0.67 -Nodes (1): OpenAiAdapter +Nodes (1): ChatModule ### Community 115 - "Community 115" Cohesion: 0.67 -Nodes (1): TongyiAdapter +Nodes (1): SendMessageDto ### Community 116 - "Community 116" Cohesion: 0.67 -Nodes (1): MemoryModule +Nodes (1): ListSessionsDto ### Community 117 - "Community 117" Cohesion: 0.67 -Nodes (1): ChatModule +Nodes (1): CreateSessionDto ### Community 118 - "Community 118" Cohesion: 0.67 -Nodes (1): SendMessageDto +Nodes (1): RenameSessionDto ### Community 119 - "Community 119" Cohesion: 0.67 -Nodes (1): ListSessionsDto +Nodes (1): AdminOnlyGuard ### Community 120 - "Community 120" Cohesion: 0.67 -Nodes (1): CreateSessionDto +Nodes (1): PlatformChatRuntimeFacade ### Community 121 - "Community 121" Cohesion: 0.67 -Nodes (1): RenameSessionDto +Nodes (1): GlossaryModule ### Community 122 - "Community 122" Cohesion: 0.67 -Nodes (1): AdminOnlyGuard +Nodes (1): RelationshipImpactSimulatorService ### Community 123 - "Community 123" Cohesion: 0.67 -Nodes (1): PlatformChatRuntimeFacade +Nodes (1): SemanticRegistryModule ### Community 124 - "Community 124" Cohesion: 0.67 -Nodes (1): GlossaryModule +Nodes (1): GovernanceChatAccessFacade ### Community 125 - "Community 125" Cohesion: 0.67 -Nodes (1): RelationshipImpactSimulatorService +Nodes (2): PreviewDatasourcePayloadDto, PreviewDatasourceTablesDto ### Community 126 - "Community 126" Cohesion: 0.67 -Nodes (1): SemanticRegistryModule +Nodes (1): UpdatePromptTemplateDto ### Community 127 - "Community 127" Cohesion: 0.67 -Nodes (1): GovernanceChatAccessFacade +Nodes (1): CreatePromptTemplateDto ### Community 128 - "Community 128" Cohesion: 0.67 -Nodes (2): PreviewDatasourcePayloadDto, PreviewDatasourceTablesDto +Nodes (1): UpsertModelingGraphDto ### Community 129 - "Community 129" Cohesion: 0.67 -Nodes (1): UpdatePromptTemplateDto +Nodes (1): ListWorkspaceDatasourceTablePermissionsDto ### Community 130 - "Community 130" Cohesion: 0.67 -Nodes (1): CreatePromptTemplateDto +Nodes (1): ResolveModelingSchemaChangeDto ### Community 131 - "Community 131" Cohesion: 0.67 -Nodes (1): UpsertModelingGraphDto +Nodes (1): ReplaceWorkspaceDatasourceTablePermissionsDto ### Community 132 - "Community 132" Cohesion: 0.67 -Nodes (1): ListWorkspaceDatasourceTablePermissionsDto +Nodes (1): ListUsersDto ### Community 133 - "Community 133" Cohesion: 0.67 -Nodes (1): ResolveModelingSchemaChangeDto +Nodes (1): ListGlossaryTermsQueryDto ### Community 134 - "Community 134" Cohesion: 0.67 -Nodes (1): ReplaceWorkspaceDatasourceTablePermissionsDto +Nodes (1): RollbackGlossaryAnchorDto ### Community 135 - "Community 135" -Cohesion: 0.67 -Nodes (1): ListUsersDto +Cohesion: 1.0 +Nodes (0): ### Community 136 - "Community 136" -Cohesion: 0.67 -Nodes (1): ListGlossaryTermsQueryDto +Cohesion: 1.0 +Nodes (0): ### Community 137 - "Community 137" -Cohesion: 0.67 -Nodes (1): RollbackGlossaryAnchorDto +Cohesion: 1.0 +Nodes (0): ### Community 138 - "Community 138" Cohesion: 1.0 @@ -1101,7 +1103,7 @@ Nodes (0): ### Community 144 - "Community 144" Cohesion: 1.0 -Nodes (0): +Nodes (1): ELK ### Community 145 - "Community 145" Cohesion: 1.0 @@ -1109,7 +1111,7 @@ Nodes (0): ### Community 146 - "Community 146" Cohesion: 1.0 -Nodes (1): ELK +Nodes (0): ### Community 147 - "Community 147" Cohesion: 1.0 @@ -1401,207 +1403,207 @@ Nodes (0): ### Community 219 - "Community 219" Cohesion: 1.0 -Nodes (0): +Nodes (1): AppModule ### Community 220 - "Community 220" Cohesion: 1.0 -Nodes (1): AppModule +Nodes (0): ### Community 221 - "Community 221" Cohesion: 1.0 -Nodes (0): +Nodes (1): LlmModule ### Community 222 - "Community 222" Cohesion: 1.0 -Nodes (1): LlmModule +Nodes (1): ApplyMemoryFeedbackDto ### Community 223 - "Community 223" Cohesion: 1.0 -Nodes (1): ApplyMemoryFeedbackDto +Nodes (1): AppConfigModule ### Community 224 - "Community 224" Cohesion: 1.0 -Nodes (1): AppConfigModule +Nodes (1): PlatformModule ### Community 225 - "Community 225" Cohesion: 1.0 -Nodes (1): PlatformModule +Nodes (1): PlatformLlmModule ### Community 226 - "Community 226" Cohesion: 1.0 -Nodes (1): PlatformLlmModule +Nodes (1): PlatformConfigModule ### Community 227 - "Community 227" Cohesion: 1.0 -Nodes (1): PlatformConfigModule +Nodes (1): PlatformObservabilityModule ### Community 228 - "Community 228" Cohesion: 1.0 -Nodes (1): PlatformObservabilityModule +Nodes (1): PlatformDataAccessModule ### Community 229 - "Community 229" Cohesion: 1.0 -Nodes (1): PlatformDataAccessModule +Nodes (1): PlatformDataQueryModule ### Community 230 - "Community 230" Cohesion: 1.0 -Nodes (1): PlatformDataQueryModule +Nodes (1): PlatformDataModule ### Community 231 - "Community 231" Cohesion: 1.0 -Nodes (1): PlatformDataModule +Nodes (1): PlatformDataPersistenceModule ### Community 232 - "Community 232" Cohesion: 1.0 -Nodes (1): PlatformDataPersistenceModule +Nodes (1): ObservabilityModule ### Community 233 - "Community 233" Cohesion: 1.0 -Nodes (1): ObservabilityModule +Nodes (1): SystemModule ### Community 234 - "Community 234" Cohesion: 1.0 -Nodes (1): SystemModule +Nodes (1): KnowledgeModule ### Community 235 - "Community 235" Cohesion: 1.0 -Nodes (1): KnowledgeModule +Nodes (1): SemanticSpineModule ### Community 236 - "Community 236" Cohesion: 1.0 -Nodes (1): SemanticSpineModule +Nodes (1): SkillRegistryModule ### Community 237 - "Community 237" Cohesion: 1.0 -Nodes (1): SkillRegistryModule +Nodes (1): GovernanceModule ### Community 238 - "Community 238" Cohesion: 1.0 -Nodes (1): GovernanceModule +Nodes (1): DatasourceModule ### Community 239 - "Community 239" Cohesion: 1.0 -Nodes (1): DatasourceModule +Nodes (1): CreateDatasourceDto ### Community 240 - "Community 240" Cohesion: 1.0 -Nodes (1): CreateDatasourceDto +Nodes (1): UploadFileDatasourceDto ### Community 241 - "Community 241" Cohesion: 1.0 -Nodes (1): UploadFileDatasourceDto +Nodes (1): UpdateDatasourceDto ### Community 242 - "Community 242" Cohesion: 1.0 -Nodes (1): UpdateDatasourceDto +Nodes (1): SettingsModule ### Community 243 - "Community 243" Cohesion: 1.0 -Nodes (1): SettingsModule +Nodes (1): BatchUpdateModelStatusDto ### Community 244 - "Community 244" Cohesion: 1.0 -Nodes (1): BatchUpdateModelStatusDto +Nodes (1): UpdateModelStatusDto ### Community 245 - "Community 245" Cohesion: 1.0 -Nodes (1): UpdateModelStatusDto +Nodes (1): RefreshProviderModelsDto ### Community 246 - "Community 246" Cohesion: 1.0 -Nodes (1): RefreshProviderModelsDto +Nodes (1): CreateProviderDto ### Community 247 - "Community 247" Cohesion: 1.0 -Nodes (1): CreateProviderDto +Nodes (1): GovernanceAccessModule ### Community 248 - "Community 248" Cohesion: 1.0 -Nodes (1): GovernanceAccessModule +Nodes (1): WorkspaceModule ### Community 249 - "Community 249" Cohesion: 1.0 -Nodes (1): WorkspaceModule +Nodes (1): RemoveWorkspaceMembersDto ### Community 250 - "Community 250" Cohesion: 1.0 -Nodes (1): RemoveWorkspaceMembersDto +Nodes (1): RenameWorkspaceDto ### Community 251 - "Community 251" Cohesion: 1.0 -Nodes (1): RenameWorkspaceDto +Nodes (1): GetModelingPreviewDto ### Community 252 - "Community 252" Cohesion: 1.0 -Nodes (1): GetModelingPreviewDto +Nodes (1): UpdateWorkspaceMemberRoleDto ### Community 253 - "Community 253" Cohesion: 1.0 -Nodes (1): UpdateWorkspaceMemberRoleDto +Nodes (1): DetectModelingSchemaChangeDto ### Community 254 - "Community 254" Cohesion: 1.0 -Nodes (1): DetectModelingSchemaChangeDto +Nodes (1): ListWorkspaceMembersDto ### Community 255 - "Community 255" Cohesion: 1.0 -Nodes (1): ListWorkspaceMembersDto +Nodes (1): DeployModelingGraphDto ### Community 256 - "Community 256" Cohesion: 1.0 -Nodes (1): DeployModelingGraphDto +Nodes (1): AddWorkspaceMemberDto ### Community 257 - "Community 257" Cohesion: 1.0 -Nodes (1): AddWorkspaceMemberDto +Nodes (1): CreateWorkspaceDto ### Community 258 - "Community 258" Cohesion: 1.0 -Nodes (1): CreateWorkspaceDto +Nodes (1): UserModule ### Community 259 - "Community 259" Cohesion: 1.0 -Nodes (1): UserModule +Nodes (1): BatchDeleteUsersDto ### Community 260 - "Community 260" Cohesion: 1.0 -Nodes (1): BatchDeleteUsersDto +Nodes (1): UpdateUserStatusDto ### Community 261 - "Community 261" Cohesion: 1.0 -Nodes (1): UpdateUserStatusDto +Nodes (1): UpdateUserDto ### Community 262 - "Community 262" Cohesion: 1.0 -Nodes (1): UpdateUserDto +Nodes (1): UserVariableDto ### Community 263 - "Community 263" Cohesion: 1.0 -Nodes (1): UserVariableDto +Nodes (1): CreateUserDto ### Community 264 - "Community 264" Cohesion: 1.0 -Nodes (1): CreateUserDto +Nodes (1): EvalModule ### Community 265 - "Community 265" Cohesion: 1.0 -Nodes (1): EvalModule +Nodes (1): RunEvaluationDto ### Community 266 - "Community 266" Cohesion: 1.0 -Nodes (1): RunEvaluationDto +Nodes (1): ConversationModule ### Community 267 - "Community 267" Cohesion: 1.0 -Nodes (1): ConversationModule +Nodes (1): SaveViewFromRunDto ### Community 268 - "Community 268" Cohesion: 1.0 -Nodes (1): SaveViewFromRunDto +Nodes (1): AgentModule ### Community 269 - "Community 269" Cohesion: 1.0 -Nodes (1): AgentModule +Nodes (0): ### Community 270 - "Community 270" Cohesion: 1.0 @@ -2467,20 +2469,30 @@ Nodes (0): Cohesion: 1.0 Nodes (0): +### Community 486 - "Community 486" +Cohesion: 1.0 +Nodes (0): + +### Community 487 - "Community 487" +Cohesion: 1.0 +Nodes (0): + ## Knowledge Gaps - **80 isolated node(s):** `ELK`, `ElkLayoutTimeoutError`, `AppModule`, `LlmModule`, `ApplyMemoryFeedbackDto` (+75 more) These have ≤1 connection - possible missing edges or undocumented components. -- **Thin community `Community 138`** (2 nodes): `pickSelectOption()`, `settings-modeling-relationship-editor.spec.tsx` +- **Thin community `Community 135`** (2 nodes): `pickSelectOption()`, `settings-modeling-relationship-editor.spec.tsx` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 136`** (2 nodes): `openCreateToStep2()`, `data-sources-page.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 139`** (2 nodes): `openCreateToStep2()`, `data-sources-page.spec.tsx` +- **Thin community `Community 137`** (2 nodes): `SectionHarness()`, `rag-delivery-section.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 140`** (2 nodes): `SectionHarness()`, `rag-delivery-section.spec.tsx` +- **Thin community `Community 138`** (2 nodes): `waitForWorkspaceDatasourceReady()`, `settings-modeling-workspace.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 141`** (2 nodes): `waitForWorkspaceDatasourceReady()`, `settings-modeling-workspace.spec.tsx` +- **Thin community `Community 139`** (2 nodes): `ChatBIResultTabsHarness()`, `chatbi-result-tabs.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 142`** (2 nodes): `createDelivery()`, `rag-delivery-panel.spec.tsx` +- **Thin community `Community 140`** (2 nodes): `createDelivery()`, `rag-delivery-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 143`** (2 nodes): `[ +- **Thin community `Community 141`** (2 nodes): `[ { id: "model.orders", tableName: "orders", @@ -2491,327 +2503,331 @@ Nodes (0): } ]()`, `settings-modeling-details-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 144`** (2 nodes): `onUnhandledRejection()`, `chat-panel.spec.tsx` +- **Thin community `Community 142`** (2 nodes): `onUnhandledRejection()`, `chat-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 145`** (2 nodes): `createSettingsView()`, `settings-mobile-smoke.spec.tsx` +- **Thin community `Community 143`** (2 nodes): `createSettingsView()`, `settings-mobile-smoke.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 146`** (2 nodes): `ELK`, `elkjs.d.ts` +- **Thin community `Community 144`** (2 nodes): `ELK`, `elkjs.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 147`** (2 nodes): `RootLayout()`, `layout.tsx` +- **Thin community `Community 145`** (2 nodes): `RootLayout()`, `layout.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 148`** (2 nodes): `ChatPage()`, `page.tsx` +- **Thin community `Community 146`** (2 nodes): `ChatPage()`, `page.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 149`** (2 nodes): `AppShell()`, `app-shell.tsx` +- **Thin community `Community 147`** (2 nodes): `AppShell()`, `app-shell.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 150`** (2 nodes): `AspectRatio()`, `aspect-ratio.tsx` +- **Thin community `Community 148`** (2 nodes): `AspectRatio()`, `aspect-ratio.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 151`** (2 nodes): `StateBlock()`, `state-block.tsx` +- **Thin community `Community 149`** (2 nodes): `StateBlock()`, `state-block.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 152`** (2 nodes): `DirectionProvider()`, `direction.tsx` +- **Thin community `Community 150`** (2 nodes): `DirectionProvider()`, `direction.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 153`** (2 nodes): `cn()`, `tabs.tsx` +- **Thin community `Community 151`** (2 nodes): `cn()`, `tabs.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 154`** (2 nodes): `ButtonGroup()`, `button-group.tsx` +- **Thin community `Community 152`** (2 nodes): `ButtonGroup()`, `button-group.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 155`** (2 nodes): `cn()`, `card.tsx` +- **Thin community `Community 153`** (2 nodes): `cn()`, `card.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 156`** (2 nodes): `InputGroup()`, `input-group.tsx` +- **Thin community `Community 154`** (2 nodes): `InputGroup()`, `input-group.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 157`** (2 nodes): `cn()`, `input-otp.tsx` +- **Thin community `Community 155`** (2 nodes): `cn()`, `input-otp.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 158`** (2 nodes): `HoverCard()`, `hover-card.tsx` +- **Thin community `Community 156`** (2 nodes): `HoverCard()`, `hover-card.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 159`** (2 nodes): `cn()`, `field.tsx` +- **Thin community `Community 157`** (2 nodes): `cn()`, `field.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 160`** (2 nodes): `Label()`, `label.tsx` +- **Thin community `Community 158`** (2 nodes): `Label()`, `label.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 161`** (2 nodes): `cn()`, `empty.tsx` +- **Thin community `Community 159`** (2 nodes): `cn()`, `empty.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 162`** (2 nodes): `TooltipContent()`, `tooltip.tsx` +- **Thin community `Community 160`** (2 nodes): `TooltipContent()`, `tooltip.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 163`** (2 nodes): `cn()`, `alert.tsx` +- **Thin community `Community 161`** (2 nodes): `cn()`, `alert.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 164`** (2 nodes): `Switch()`, `switch.tsx` +- **Thin community `Community 162`** (2 nodes): `Switch()`, `switch.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 165`** (2 nodes): `cn()`, `command.tsx` +- **Thin community `Community 163`** (2 nodes): `cn()`, `command.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 166`** (2 nodes): `ToggleGroup()`, `toggle-group.tsx` +- **Thin community `Community 164`** (2 nodes): `ToggleGroup()`, `toggle-group.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 167`** (2 nodes): `Avatar()`, `avatar.tsx` +- **Thin community `Community 165`** (2 nodes): `Avatar()`, `avatar.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 168`** (2 nodes): `cn()`, `kbd.tsx` +- **Thin community `Community 166`** (2 nodes): `cn()`, `kbd.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 169`** (2 nodes): `SectionHeader()`, `section-header.tsx` +- **Thin community `Community 167`** (2 nodes): `SectionHeader()`, `section-header.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 170`** (2 nodes): `Badge()`, `badge.tsx` +- **Thin community `Community 168`** (2 nodes): `Badge()`, `badge.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 171`** (2 nodes): `cn()`, `table.tsx` +- **Thin community `Community 169`** (2 nodes): `cn()`, `table.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 172`** (2 nodes): `Separator()`, `separator.tsx` +- **Thin community `Community 170`** (2 nodes): `Separator()`, `separator.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 173`** (2 nodes): `Checkbox()`, `checkbox.tsx` +- **Thin community `Community 171`** (2 nodes): `Checkbox()`, `checkbox.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 174`** (2 nodes): `Spinner()`, `spinner.tsx` +- **Thin community `Community 172`** (2 nodes): `Spinner()`, `spinner.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 175`** (2 nodes): `Collapsible()`, `collapsible.tsx` +- **Thin community `Community 173`** (2 nodes): `Collapsible()`, `collapsible.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 176`** (2 nodes): `cn()`, `textarea.tsx` +- **Thin community `Community 174`** (2 nodes): `cn()`, `textarea.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 177`** (2 nodes): `Input()`, `input.tsx` +- **Thin community `Community 175`** (2 nodes): `Input()`, `input.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 178`** (2 nodes): `Skeleton()`, `skeleton.tsx` +- **Thin community `Community 176`** (2 nodes): `Skeleton()`, `skeleton.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 179`** (2 nodes): `workspace-editor-dialog.tsx`, `WorkspaceEditorDialog()` +- **Thin community `Community 177`** (2 nodes): `workspace-editor-dialog.tsx`, `WorkspaceEditorDialog()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 180`** (2 nodes): `handleKeyDown()`, `modeling-context-drawer.tsx` +- **Thin community `Community 178`** (2 nodes): `handleKeyDown()`, `modeling-context-drawer.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 181`** (2 nodes): `resolveBlockingReasonGuidance()`, `modeling-deploy-panel.tsx` +- **Thin community `Community 179`** (2 nodes): `resolveBlockingReasonGuidance()`, `modeling-deploy-panel.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 182`** (2 nodes): `TextPart()`, `assistant-message.tsx` +- **Thin community `Community 180`** (2 nodes): `TextPart()`, `assistant-message.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 183`** (2 nodes): `resolveMessageRunId()`, `assistant-thread.tsx` +- **Thin community `Community 181`** (2 nodes): `isRecord()`, `sql-inline-panel.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 184`** (2 nodes): `ModelSelector()`, `model-selector.tsx` +- **Thin community `Community 182`** (2 nodes): `resolveMessageRunId()`, `assistant-thread.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 185`** (2 nodes): `SessionActions()`, `session-actions.tsx` +- **Thin community `Community 183`** (2 nodes): `ModelSelector()`, `model-selector.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 186`** (2 nodes): `SqlDebugDetails()`, `sql-debug-details.tsx` +- **Thin community `Community 184`** (2 nodes): `SessionActions()`, `session-actions.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 187`** (2 nodes): `SqlRunSummary()`, `sql-run-summary.tsx` +- **Thin community `Community 185`** (2 nodes): `SqlDebugDetails()`, `sql-debug-details.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 188`** (2 nodes): `isNumericValue()`, `sql-result-table.tsx` +- **Thin community `Community 186`** (2 nodes): `SqlRunSummary()`, `sql-run-summary.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 189`** (2 nodes): `statusVariant()`, `sql-execution-timeline.tsx` +- **Thin community `Community 187`** (2 nodes): `isNumericValue()`, `sql-result-table.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 190`** (2 nodes): `useIsMobile()`, `use-mobile.ts` +- **Thin community `Community 188`** (2 nodes): `statusVariant()`, `sql-execution-timeline.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 191`** (2 nodes): `utils.ts`, `cn()` +- **Thin community `Community 189`** (2 nodes): `useIsMobile()`, `use-mobile.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 192`** (2 nodes): `createService()`, `sql-generation.service.spec.ts` +- **Thin community `Community 190`** (2 nodes): `utils.ts`, `cn()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 193`** (2 nodes): `createService()`, `semantic-registry.service.spec.ts` +- **Thin community `Community 191`** (2 nodes): `createService()`, `sql-generation.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 194`** (2 nodes): `createBaseRun()`, `delivery-contract.mapper.spec.ts` +- **Thin community `Community 192`** (2 nodes): `createService()`, `semantic-registry.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 195`** (2 nodes): `createRequest()`, `request-actor.middleware.spec.ts` +- **Thin community `Community 193`** (2 nodes): `createBaseRun()`, `delivery-contract.mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 196`** (2 nodes): `workspace-admin.guard.spec.ts`, `createContext()` +- **Thin community `Community 194`** (2 nodes): `createRequest()`, `request-actor.middleware.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 197`** (2 nodes): `reset()`, `langsmith-config.spec.ts` +- **Thin community `Community 195`** (2 nodes): `workspace-admin.guard.spec.ts`, `createContext()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 198`** (2 nodes): `createNode()`, `build-semantic-query.node.spec.ts` +- **Thin community `Community 196`** (2 nodes): `reset()`, `langsmith-config.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 199`** (2 nodes): `createContext()`, `admin-only.guard.spec.ts` +- **Thin community `Community 197`** (2 nodes): `createNode()`, `build-semantic-query.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 200`** (2 nodes): `resetAppEnv()`, `config.module.spec.ts` +- **Thin community `Community 198`** (2 nodes): `createContext()`, `admin-only.guard.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 201`** (2 nodes): `writeRepoFile()`, `capability-boundary-check.spec.ts` +- **Thin community `Community 199`** (2 nodes): `resetAppEnv()`, `config.module.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 202`** (2 nodes): `createPolicy()`, `clarification-fusion.policy.spec.ts` +- **Thin community `Community 200`** (2 nodes): `writeRepoFile()`, `capability-boundary-check.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 203`** (2 nodes): `buildLongSchemaText()`, `rag-chunking.service.spec.ts` +- **Thin community `Community 201`** (2 nodes): `createPolicy()`, `clarification-fusion.policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 204`** (2 nodes): `datasource()`, `query-executor-router.spec.ts` +- **Thin community `Community 202`** (2 nodes): `buildLongSchemaText()`, `rag-chunking.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 205`** (2 nodes): `createNode()`, `build-intent-plan.node.spec.ts` +- **Thin community `Community 203`** (2 nodes): `datasource()`, `query-executor-router.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 206`** (2 nodes): `createBaseRun()`, `sandbox-failover.spec.ts` +- **Thin community `Community 204`** (2 nodes): `createNode()`, `build-intent-plan.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 207`** (2 nodes): `buildUsecase()`, `save-view-from-run.spec.ts` +- **Thin community `Community 205`** (2 nodes): `createBaseRun()`, `sandbox-failover.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 208`** (2 nodes): `workspace-relationship-api.spec.ts`, `buildService()` +- **Thin community `Community 206`** (2 nodes): `buildUsecase()`, `save-view-from-run.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 209`** (2 nodes): `buildArtifact()`, `sandbox-isolation.spec.ts` +- **Thin community `Community 207`** (2 nodes): `workspace-relationship-api.spec.ts`, `buildService()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 210`** (2 nodes): `createRun()`, `rag-audit-replay.spec.ts` +- **Thin community `Community 208`** (2 nodes): `buildArtifact()`, `sandbox-isolation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 211`** (2 nodes): `datasource()`, `query-executor-router-table-permissions.spec.ts` +- **Thin community `Community 209`** (2 nodes): `createRun()`, `rag-audit-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 212`** (2 nodes): `sleep()`, `rag-datasource-gate-metrics.spec.ts` +- **Thin community `Community 210`** (2 nodes): `datasource()`, `query-executor-router-table-permissions.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 213`** (2 nodes): `buildSnapshot()`, `semantic-spine-compiler.spec.ts` +- **Thin community `Community 211`** (2 nodes): `sleep()`, `rag-datasource-gate-metrics.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 214`** (2 nodes): `workspace-modeling-setup.spec.ts`, `buildService()` +- **Thin community `Community 212`** (2 nodes): `buildSnapshot()`, `semantic-spine-compiler.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 215`** (2 nodes): `createRun()`, `memory-promotion.spec.ts` +- **Thin community `Community 213`** (2 nodes): `workspace-modeling-setup.spec.ts`, `buildService()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 216`** (2 nodes): `buildSamples()`, `glossary-selected-context-gate.spec.ts` +- **Thin community `Community 214`** (2 nodes): `createRun()`, `memory-promotion.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 217`** (2 nodes): `readErrorMessage()`, `user-workspace-authz.spec.ts` +- **Thin community `Community 215`** (2 nodes): `buildSamples()`, `glossary-selected-context-gate.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 218`** (2 nodes): `readErrorMessage()`, `rule-group-authz.spec.ts` +- **Thin community `Community 216`** (2 nodes): `readErrorMessage()`, `user-workspace-authz.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 219`** (2 nodes): `workspace-datasource-authz.spec.ts`, `readErrorMessage()` +- **Thin community `Community 217`** (2 nodes): `readErrorMessage()`, `rule-group-authz.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 220`** (2 nodes): `AppModule`, `app.module.ts` +- **Thin community `Community 218`** (2 nodes): `workspace-datasource-authz.spec.ts`, `readErrorMessage()` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 221`** (2 nodes): `requestIdMiddleware()`, `request-id.middleware.ts` +- **Thin community `Community 219`** (2 nodes): `AppModule`, `app.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 222`** (2 nodes): `LlmModule`, `llm.module.ts` +- **Thin community `Community 220`** (2 nodes): `requestIdMiddleware()`, `request-id.middleware.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 223`** (2 nodes): `ApplyMemoryFeedbackDto`, `apply-memory-feedback.dto.ts` +- **Thin community `Community 221`** (2 nodes): `LlmModule`, `llm.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 224`** (2 nodes): `AppConfigModule`, `config.module.ts` +- **Thin community `Community 222`** (2 nodes): `ApplyMemoryFeedbackDto`, `apply-memory-feedback.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 225`** (2 nodes): `PlatformModule`, `platform.module.ts` +- **Thin community `Community 223`** (2 nodes): `AppConfigModule`, `config.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 226`** (2 nodes): `PlatformLlmModule`, `llm.module.ts` +- **Thin community `Community 224`** (2 nodes): `PlatformModule`, `platform.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 227`** (2 nodes): `PlatformConfigModule`, `config.module.ts` +- **Thin community `Community 225`** (2 nodes): `PlatformLlmModule`, `llm.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 228`** (2 nodes): `PlatformObservabilityModule`, `observability.module.ts` +- **Thin community `Community 226`** (2 nodes): `PlatformConfigModule`, `config.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 229`** (2 nodes): `PlatformDataAccessModule`, `access.module.ts` +- **Thin community `Community 227`** (2 nodes): `PlatformObservabilityModule`, `observability.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 230`** (2 nodes): `PlatformDataQueryModule`, `query.module.ts` +- **Thin community `Community 228`** (2 nodes): `PlatformDataAccessModule`, `access.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 231`** (2 nodes): `PlatformDataModule`, `data.module.ts` +- **Thin community `Community 229`** (2 nodes): `PlatformDataQueryModule`, `query.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 232`** (2 nodes): `PlatformDataPersistenceModule`, `persistence.module.ts` +- **Thin community `Community 230`** (2 nodes): `PlatformDataModule`, `data.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 233`** (2 nodes): `ObservabilityModule`, `observability.module.ts` +- **Thin community `Community 231`** (2 nodes): `PlatformDataPersistenceModule`, `persistence.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 234`** (2 nodes): `SystemModule`, `system.module.ts` +- **Thin community `Community 232`** (2 nodes): `ObservabilityModule`, `observability.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 235`** (2 nodes): `KnowledgeModule`, `knowledge.module.ts` +- **Thin community `Community 233`** (2 nodes): `SystemModule`, `system.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 236`** (2 nodes): `SemanticSpineModule`, `semantic-spine.module.ts` +- **Thin community `Community 234`** (2 nodes): `KnowledgeModule`, `knowledge.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 237`** (2 nodes): `SkillRegistryModule`, `skill-registry.module.ts` +- **Thin community `Community 235`** (2 nodes): `SemanticSpineModule`, `semantic-spine.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 238`** (2 nodes): `GovernanceModule`, `governance.module.ts` +- **Thin community `Community 236`** (2 nodes): `SkillRegistryModule`, `skill-registry.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 239`** (2 nodes): `DatasourceModule`, `datasource.module.ts` +- **Thin community `Community 237`** (2 nodes): `GovernanceModule`, `governance.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 240`** (2 nodes): `CreateDatasourceDto`, `create-datasource.dto.ts` +- **Thin community `Community 238`** (2 nodes): `DatasourceModule`, `datasource.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 241`** (2 nodes): `UploadFileDatasourceDto`, `upload-file-datasource.dto.ts` +- **Thin community `Community 239`** (2 nodes): `CreateDatasourceDto`, `create-datasource.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 242`** (2 nodes): `UpdateDatasourceDto`, `update-datasource.dto.ts` +- **Thin community `Community 240`** (2 nodes): `UploadFileDatasourceDto`, `upload-file-datasource.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 243`** (2 nodes): `SettingsModule`, `settings.module.ts` +- **Thin community `Community 241`** (2 nodes): `UpdateDatasourceDto`, `update-datasource.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 244`** (2 nodes): `BatchUpdateModelStatusDto`, `batch-update-model-status.dto.ts` +- **Thin community `Community 242`** (2 nodes): `SettingsModule`, `settings.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 245`** (2 nodes): `UpdateModelStatusDto`, `update-model-status.dto.ts` +- **Thin community `Community 243`** (2 nodes): `BatchUpdateModelStatusDto`, `batch-update-model-status.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 246`** (2 nodes): `RefreshProviderModelsDto`, `refresh-provider-models.dto.ts` +- **Thin community `Community 244`** (2 nodes): `UpdateModelStatusDto`, `update-model-status.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 247`** (2 nodes): `CreateProviderDto`, `create-provider.dto.ts` +- **Thin community `Community 245`** (2 nodes): `RefreshProviderModelsDto`, `refresh-provider-models.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 248`** (2 nodes): `GovernanceAccessModule`, `access.module.ts` +- **Thin community `Community 246`** (2 nodes): `CreateProviderDto`, `create-provider.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 249`** (2 nodes): `workspace.module.ts`, `WorkspaceModule` +- **Thin community `Community 247`** (2 nodes): `GovernanceAccessModule`, `access.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 250`** (2 nodes): `RemoveWorkspaceMembersDto`, `remove-workspace-members.dto.ts` +- **Thin community `Community 248`** (2 nodes): `workspace.module.ts`, `WorkspaceModule` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 251`** (2 nodes): `RenameWorkspaceDto`, `rename-workspace.dto.ts` +- **Thin community `Community 249`** (2 nodes): `RemoveWorkspaceMembersDto`, `remove-workspace-members.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 252`** (2 nodes): `GetModelingPreviewDto`, `get-modeling-preview.dto.ts` +- **Thin community `Community 250`** (2 nodes): `RenameWorkspaceDto`, `rename-workspace.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 253`** (2 nodes): `UpdateWorkspaceMemberRoleDto`, `update-workspace-member-role.dto.ts` +- **Thin community `Community 251`** (2 nodes): `GetModelingPreviewDto`, `get-modeling-preview.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 254`** (2 nodes): `DetectModelingSchemaChangeDto`, `detect-modeling-schema-change.dto.ts` +- **Thin community `Community 252`** (2 nodes): `UpdateWorkspaceMemberRoleDto`, `update-workspace-member-role.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 255`** (2 nodes): `ListWorkspaceMembersDto`, `list-workspace-members.dto.ts` +- **Thin community `Community 253`** (2 nodes): `DetectModelingSchemaChangeDto`, `detect-modeling-schema-change.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 256`** (2 nodes): `DeployModelingGraphDto`, `deploy-modeling-graph.dto.ts` +- **Thin community `Community 254`** (2 nodes): `ListWorkspaceMembersDto`, `list-workspace-members.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 257`** (2 nodes): `AddWorkspaceMemberDto`, `add-workspace-member.dto.ts` +- **Thin community `Community 255`** (2 nodes): `DeployModelingGraphDto`, `deploy-modeling-graph.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 258`** (2 nodes): `CreateWorkspaceDto`, `create-workspace.dto.ts` +- **Thin community `Community 256`** (2 nodes): `AddWorkspaceMemberDto`, `add-workspace-member.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 259`** (2 nodes): `UserModule`, `user.module.ts` +- **Thin community `Community 257`** (2 nodes): `CreateWorkspaceDto`, `create-workspace.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 260`** (2 nodes): `BatchDeleteUsersDto`, `batch-delete-users.dto.ts` +- **Thin community `Community 258`** (2 nodes): `UserModule`, `user.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 261`** (2 nodes): `UpdateUserStatusDto`, `update-user-status.dto.ts` +- **Thin community `Community 259`** (2 nodes): `BatchDeleteUsersDto`, `batch-delete-users.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 262`** (2 nodes): `UpdateUserDto`, `update-user.dto.ts` +- **Thin community `Community 260`** (2 nodes): `UpdateUserStatusDto`, `update-user-status.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 263`** (2 nodes): `UserVariableDto`, `user-variable.dto.ts` +- **Thin community `Community 261`** (2 nodes): `UpdateUserDto`, `update-user.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 264`** (2 nodes): `CreateUserDto`, `create-user.dto.ts` +- **Thin community `Community 262`** (2 nodes): `UserVariableDto`, `user-variable.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 265`** (2 nodes): `EvalModule`, `eval.module.ts` +- **Thin community `Community 263`** (2 nodes): `CreateUserDto`, `create-user.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 266`** (2 nodes): `RunEvaluationDto`, `run-evaluation.dto.ts` +- **Thin community `Community 264`** (2 nodes): `EvalModule`, `eval.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 267`** (2 nodes): `ConversationModule`, `conversation.module.ts` +- **Thin community `Community 265`** (2 nodes): `RunEvaluationDto`, `run-evaluation.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 268`** (2 nodes): `SaveViewFromRunDto`, `save-view-from-run.dto.ts` +- **Thin community `Community 266`** (2 nodes): `ConversationModule`, `conversation.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 269`** (2 nodes): `AgentModule`, `agent.module.ts` +- **Thin community `Community 267`** (2 nodes): `SaveViewFromRunDto`, `save-view-from-run.dto.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 270`** (1 nodes): `semantic-spine.ts` +- **Thin community `Community 268`** (2 nodes): `AgentModule`, `agent.module.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 271`** (1 nodes): `modeling.ts` +- **Thin community `Community 269`** (1 nodes): `semantic-spine.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 272`** (1 nodes): `api.d.ts` +- **Thin community `Community 270`** (1 nodes): `modeling.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 273`** (1 nodes): `api.ts` +- **Thin community `Community 271`** (1 nodes): `api.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 274`** (1 nodes): `index.ts` +- **Thin community `Community 272`** (1 nodes): `api.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 275`** (1 nodes): `index.d.ts` +- **Thin community `Community 273`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 276`** (1 nodes): `api.contract.spec.ts` +- **Thin community `Community 274`** (1 nodes): `index.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 277`** (1 nodes): `next-env.d.ts` +- **Thin community `Community 275`** (1 nodes): `api.contract.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 278`** (1 nodes): `vitest.config.ts` +- **Thin community `Community 276`** (1 nodes): `next-env.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 279`** (1 nodes): `assistant-thinking-panel.spec.tsx` +- **Thin community `Community 277`** (1 nodes): `vitest.config.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 280`** (1 nodes): `session-sidebar.spec.tsx` +- **Thin community `Community 278`** (1 nodes): `assistant-thinking-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 281`** (1 nodes): `run-visibility-mapper.spec.ts` +- **Thin community `Community 279`** (1 nodes): `session-sidebar.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 282`** (1 nodes): `chat-rag-sync-stream-consistency.spec.tsx` +- **Thin community `Community 280`** (1 nodes): `run-visibility-mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 283`** (1 nodes): `settings-modeling-page-context.spec.tsx` +- **Thin community `Community 281`** (1 nodes): `chat-rag-sync-stream-consistency.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 284`** (1 nodes): `settings-relationship-publish-gate.spec.tsx` +- **Thin community `Community 282`** (1 nodes): `settings-modeling-page-context.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 285`** (1 nodes): `settings-users-management.spec.tsx` +- **Thin community `Community 283`** (1 nodes): `settings-relationship-publish-gate.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 286`** (1 nodes): `settings-modeling-model-drawer.spec.tsx` +- **Thin community `Community 284`** (1 nodes): `settings-users-management.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 287`** (1 nodes): `admin-api-client-modeling-contract.spec.ts` +- **Thin community `Community 285`** (1 nodes): `settings-modeling-model-drawer.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 288`** (1 nodes): `settings-modeling-deploy-panel.spec.tsx` +- **Thin community `Community 286`** (1 nodes): `admin-api-client-modeling-contract.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 289`** (1 nodes): `settings-modeling-schema-change-panel.spec.tsx` +- **Thin community `Community 287`** (1 nodes): `settings-modeling-deploy-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 290`** (1 nodes): `settings-modeling-sidebar-tree.spec.tsx` +- **Thin community `Community 288`** (1 nodes): `settings-modeling-schema-change-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 291`** (1 nodes): `settings-modeling-deploy.spec.tsx` +- **Thin community `Community 289`** (1 nodes): `settings-modeling-sidebar-tree.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 292`** (1 nodes): `settings-workspace-management.spec.tsx` +- **Thin community `Community 290`** (1 nodes): `settings-modeling-deploy.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 293`** (1 nodes): `chat-context-envelope-input.spec.tsx` +- **Thin community `Community 291`** (1 nodes): `settings-workspace-management.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 294`** (1 nodes): `chat-sql-preview.integration.spec.tsx` +- **Thin community `Community 292`** (1 nodes): `chat-context-envelope-input.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 295`** (1 nodes): `settings-workspace-datasource-table-permissions.spec.tsx` +- **Thin community `Community 293`** (1 nodes): `chat-sql-preview.integration.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 296`** (1 nodes): `settings-modeling-view-sync.spec.tsx` +- **Thin community `Community 294`** (1 nodes): `settings-workspace-datasource-table-permissions.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 297`** (1 nodes): `settings-retired-governance-management.spec.tsx` +- **Thin community `Community 295`** (1 nodes): `settings-modeling-view-sync.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 298`** (1 nodes): `sql-preview.spec.tsx` +- **Thin community `Community 296`** (1 nodes): `settings-retired-governance-management.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 299`** (1 nodes): `settings-relationship-modeling-canvas.spec.tsx` +- **Thin community `Community 297`** (1 nodes): `sql-preview.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 300`** (1 nodes): `settings-modeling-schema-change.spec.tsx` +- **Thin community `Community 298`** (1 nodes): `settings-relationship-modeling-canvas.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 301`** (1 nodes): `app-shell.spec.tsx` +- **Thin community `Community 299`** (1 nodes): `settings-modeling-schema-change.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 302`** (1 nodes): `settings-workspace-table-permission-entry.spec.tsx` +- **Thin community `Community 300`** (1 nodes): `app-shell.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 303`** (1 nodes): `settings-modeling-calculated-field-editor.spec.tsx` +- **Thin community `Community 301`** (1 nodes): `settings-workspace-table-permission-entry.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 304`** (1 nodes): `settings-modeling-complete-parity.spec.tsx` +- **Thin community `Community 302`** (1 nodes): `settings-modeling-calculated-field-editor.spec.tsx` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 303`** (1 nodes): `settings-modeling-complete-parity.spec.tsx` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 304`** (1 nodes): `chatbi-result-panel.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. - **Thin community `Community 305`** (1 nodes): `chat-save-as-view.spec.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. @@ -2853,17 +2869,17 @@ Nodes (0): Too small to be a meaningful cluster - may be noise or needs more connections extracted. - **Thin community `Community 324`** (1 nodes): `elk-layout.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 325`** (1 nodes): `sql-inline-panel.tsx` +- **Thin community `Community 325`** (1 nodes): `message-list.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 326`** (1 nodes): `message-list.tsx` +- **Thin community `Community 326`** (1 nodes): `assistant-composer.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 327`** (1 nodes): `assistant-composer.tsx` +- **Thin community `Community 327`** (1 nodes): `workspace-selector-inline.tsx` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 328`** (1 nodes): `workspace-selector-inline.tsx` +- **Thin community `Community 328`** (1 nodes): `jest.config.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 329`** (1 nodes): `jest.config.ts` +- **Thin community `Community 329`** (1 nodes): `jest.setup.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 330`** (1 nodes): `jest.setup.ts` +- **Thin community `Community 330`** (1 nodes): `chartbi-artifact.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. - **Thin community `Community 331`** (1 nodes): `sandbox-policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. @@ -2877,312 +2893,316 @@ Nodes (0): Too small to be a meaningful cluster - may be noise or needs more connections extracted. - **Thin community `Community 336`** (1 nodes): `openai-compatible.client.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 337`** (1 nodes): `slot-filling-context.spec.ts` +- **Thin community `Community 337`** (1 nodes): `chartbi-intent-parser.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 338`** (1 nodes): `sql-prompt.builder.spec.ts` +- **Thin community `Community 338`** (1 nodes): `slot-filling-context.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 339`** (1 nodes): `resolve-saved-prior-sql.node.spec.ts` +- **Thin community `Community 339`** (1 nodes): `sql-prompt.builder.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 340`** (1 nodes): `tool-execution-guard.spec.ts` +- **Thin community `Community 340`** (1 nodes): `resolve-saved-prior-sql.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 341`** (1 nodes): `clarify.node.spec.ts` +- **Thin community `Community 341`** (1 nodes): `tool-execution-guard.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 342`** (1 nodes): `planner-version-lock.service.spec.ts` +- **Thin community `Community 342`** (1 nodes): `clarify.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 343`** (1 nodes): `planner-cache.service.spec.ts` +- **Thin community `Community 343`** (1 nodes): `planner-version-lock.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 344`** (1 nodes): `tool-events.mapper.spec.ts` +- **Thin community `Community 344`** (1 nodes): `planner-cache.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 345`** (1 nodes): `tool-registry.service.spec.ts` +- **Thin community `Community 345`** (1 nodes): `tool-events.mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 346`** (1 nodes): `semantic-spine-context-pack.mapper.spec.ts` +- **Thin community `Community 346`** (1 nodes): `tool-registry.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 347`** (1 nodes): `llm-model-factory.spec.ts` +- **Thin community `Community 347`** (1 nodes): `chartbi-validator.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 348`** (1 nodes): `rag-event-consumer.spec.ts` +- **Thin community `Community 348`** (1 nodes): `semantic-spine-context-pack.mapper.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 349`** (1 nodes): `bootstrap.spec.ts` +- **Thin community `Community 349`** (1 nodes): `llm-model-factory.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 350`** (1 nodes): `skill-registry.service.spec.ts` +- **Thin community `Community 350`** (1 nodes): `rag-event-consumer.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 351`** (1 nodes): `rrf-fusion.spec.ts` +- **Thin community `Community 351`** (1 nodes): `bootstrap.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 352`** (1 nodes): `sql-output-extractor.spec.ts` +- **Thin community `Community 352`** (1 nodes): `skill-registry.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 353`** (1 nodes): `safety-check.node.spec.ts` +- **Thin community `Community 353`** (1 nodes): `rrf-fusion.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 354`** (1 nodes): `knowledge-facade-contract.spec.ts` +- **Thin community `Community 354`** (1 nodes): `sql-output-extractor.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 355`** (1 nodes): `chat-langsmith-trace.spec.ts` +- **Thin community `Community 355`** (1 nodes): `safety-check.node.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 356`** (1 nodes): `query-executors.spec.ts` +- **Thin community `Community 356`** (1 nodes): `knowledge-facade-contract.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 357`** (1 nodes): `audit-log.repository.spec.ts` +- **Thin community `Community 357`** (1 nodes): `chat-langsmith-trace.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 358`** (1 nodes): `rag-foundation-replay.spec.ts` +- **Thin community `Community 358`** (1 nodes): `query-executors.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 359`** (1 nodes): `rag-retrieval.service.spec.ts` +- **Thin community `Community 359`** (1 nodes): `audit-log.repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 360`** (1 nodes): `user-workspace-repository.spec.ts` +- **Thin community `Community 360`** (1 nodes): `rag-foundation-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 361`** (1 nodes): `rag-foundation-observability.spec.ts` +- **Thin community `Community 361`** (1 nodes): `rag-retrieval.service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 362`** (1 nodes): `rag-incremental-refresh.spec.ts` +- **Thin community `Community 362`** (1 nodes): `user-workspace-repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 363`** (1 nodes): `graph-acceleration-fallback.spec.ts` +- **Thin community `Community 363`** (1 nodes): `rag-foundation-observability.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 364`** (1 nodes): `agent-rag-main-flow.spec.ts` +- **Thin community `Community 364`** (1 nodes): `rag-incremental-refresh.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 365`** (1 nodes): `data-flow.spec.ts` +- **Thin community `Community 365`** (1 nodes): `graph-acceleration-fallback.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 366`** (1 nodes): `rule-group-schema.spec.ts` +- **Thin community `Community 366`** (1 nodes): `agent-rag-main-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 367`** (1 nodes): `workspace-modeling-deploy.spec.ts` +- **Thin community `Community 367`** (1 nodes): `data-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 368`** (1 nodes): `session-repository.spec.ts` +- **Thin community `Community 368`** (1 nodes): `rule-group-schema.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 369`** (1 nodes): `trace-lineage.spec.ts` +- **Thin community `Community 369`** (1 nodes): `workspace-modeling-deploy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 370`** (1 nodes): `rag-rerank.integration.spec.ts` +- **Thin community `Community 370`** (1 nodes): `session-repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 371`** (1 nodes): `relationship-impact-simulation.spec.ts` +- **Thin community `Community 371`** (1 nodes): `trace-lineage.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 372`** (1 nodes): `rag-multi-datasource-orchestrator.spec.ts` +- **Thin community `Community 372`** (1 nodes): `rag-rerank.integration.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 373`** (1 nodes): `rag-run-replay.spec.ts` +- **Thin community `Community 373`** (1 nodes): `relationship-impact-simulation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 374`** (1 nodes): `workspace-modeling-schema-change.spec.ts` +- **Thin community `Community 374`** (1 nodes): `rag-multi-datasource-orchestrator.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 375`** (1 nodes): `skill-registry-rag.spec.ts` +- **Thin community `Community 375`** (1 nodes): `rag-run-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 376`** (1 nodes): `semantic-registry.spec.ts` +- **Thin community `Community 376`** (1 nodes): `workspace-modeling-schema-change.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 377`** (1 nodes): `chat-run-template-trace.spec.ts` +- **Thin community `Community 377`** (1 nodes): `skill-registry-rag.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 378`** (1 nodes): `workspace-datasource-table-permissions-schema.spec.ts` +- **Thin community `Community 378`** (1 nodes): `semantic-registry.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 379`** (1 nodes): `policy-evaluator.spec.ts` +- **Thin community `Community 379`** (1 nodes): `chat-run-template-trace.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 380`** (1 nodes): `rag-cache-version-invalidation.spec.ts` +- **Thin community `Community 380`** (1 nodes): `workspace-datasource-table-permissions-schema.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 381`** (1 nodes): `rag-index-builder.spec.ts` +- **Thin community `Community 381`** (1 nodes): `policy-evaluator.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 382`** (1 nodes): `prisma-graph-baseline.spec.ts` +- **Thin community `Community 382`** (1 nodes): `rag-cache-version-invalidation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 383`** (1 nodes): `agent-rag-degrade-flow.spec.ts` +- **Thin community `Community 383`** (1 nodes): `rag-index-builder.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 384`** (1 nodes): `langsmith-coverage.spec.ts` +- **Thin community `Community 384`** (1 nodes): `prisma-graph-baseline.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 385`** (1 nodes): `session-management.spec.ts` +- **Thin community `Community 385`** (1 nodes): `agent-rag-degrade-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 386`** (1 nodes): `planner-cache-replay.spec.ts` +- **Thin community `Community 386`** (1 nodes): `langsmith-coverage.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 387`** (1 nodes): `planner-version-lock.spec.ts` +- **Thin community `Community 387`** (1 nodes): `session-management.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 388`** (1 nodes): `agent-context-pack.spec.ts` +- **Thin community `Community 388`** (1 nodes): `planner-cache-replay.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 389`** (1 nodes): `graph-acceleration-circuit-breaker.spec.ts` +- **Thin community `Community 389`** (1 nodes): `planner-version-lock.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 390`** (1 nodes): `agent-sql-prompt-template-runtime.spec.ts` +- **Thin community `Community 390`** (1 nodes): `agent-context-pack.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 391`** (1 nodes): `agent-main-flow.spec.ts` +- **Thin community `Community 391`** (1 nodes): `graph-acceleration-circuit-breaker.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 392`** (1 nodes): `datasource-repository.spec.ts` +- **Thin community `Community 392`** (1 nodes): `agent-sql-prompt-template-runtime.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 393`** (1 nodes): `rag-performance-budget.spec.ts` +- **Thin community `Community 393`** (1 nodes): `agent-main-flow.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 394`** (1 nodes): `agent-relationship-correction-loop.spec.ts` +- **Thin community `Community 394`** (1 nodes): `datasource-repository.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 395`** (1 nodes): `chat-context-envelope-accuracy.spec.ts` +- **Thin community `Community 395`** (1 nodes): `rag-performance-budget.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 396`** (1 nodes): `rag-index-activation.spec.ts` +- **Thin community `Community 396`** (1 nodes): `agent-relationship-correction-loop.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 397`** (1 nodes): `datasource-access-policy.spec.ts` +- **Thin community `Community 397`** (1 nodes): `chat-context-envelope-accuracy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 398`** (1 nodes): `eval-langsmith-trace.spec.ts` +- **Thin community `Community 398`** (1 nodes): `rag-index-activation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 399`** (1 nodes): `rag-quality.spec.ts` +- **Thin community `Community 399`** (1 nodes): `datasource-access-policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 400`** (1 nodes): `eval-report.spec.ts` +- **Thin community `Community 400`** (1 nodes): `eval-langsmith-trace.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 401`** (1 nodes): `semantic-spine-shadow-gate.spec.ts` +- **Thin community `Community 401`** (1 nodes): `rag-quality.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 402`** (1 nodes): `agent-saved-prior-sql-shortcut.spec.ts` +- **Thin community `Community 402`** (1 nodes): `eval-report.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 403`** (1 nodes): `chat-persistence-retry.spec.ts` +- **Thin community `Community 403`** (1 nodes): `semantic-spine-shadow-gate.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 404`** (1 nodes): `saved-prior-sql-ingestion.spec.ts` +- **Thin community `Community 404`** (1 nodes): `agent-saved-prior-sql-shortcut.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 405`** (1 nodes): `r3-gate-rehearsal.spec.ts` +- **Thin community `Community 405`** (1 nodes): `chat-persistence-retry.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 406`** (1 nodes): `r3-semantic-registry-migration.spec.ts` +- **Thin community `Community 406`** (1 nodes): `saved-prior-sql-ingestion.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 407`** (1 nodes): `workspace-service.spec.ts` +- **Thin community `Community 407`** (1 nodes): `r3-gate-rehearsal.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 408`** (1 nodes): `rag-document-factory.spec.ts` +- **Thin community `Community 408`** (1 nodes): `r3-semantic-registry-migration.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 409`** (1 nodes): `r6-gate-readiness.spec.ts` +- **Thin community `Community 409`** (1 nodes): `workspace-service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 410`** (1 nodes): `user-service.spec.ts` +- **Thin community `Community 410`** (1 nodes): `rag-document-factory.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 411`** (1 nodes): `provider-fallback.spec.ts` +- **Thin community `Community 411`** (1 nodes): `r6-gate-readiness.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 412`** (1 nodes): `saved-prior-sql-quality.spec.ts` +- **Thin community `Community 412`** (1 nodes): `user-service.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 413`** (1 nodes): `workspace-api.spec.ts` +- **Thin community `Community 413`** (1 nodes): `provider-fallback.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 414`** (1 nodes): `r6-release-rollback-rehearsal.e2e-spec.ts` +- **Thin community `Community 414`** (1 nodes): `saved-prior-sql-quality.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 415`** (1 nodes): `datasource-visibility-api.spec.ts` +- **Thin community `Community 415`** (1 nodes): `workspace-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 416`** (1 nodes): `workspace-datasource-api.spec.ts` +- **Thin community `Community 416`** (1 nodes): `r6-release-rollback-rehearsal.e2e-spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 417`** (1 nodes): `graph-acceleration-chaos.e2e-spec.ts` +- **Thin community `Community 417`** (1 nodes): `datasource-visibility-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 418`** (1 nodes): `clarification-hybrid-balance.acceptance.spec.ts` +- **Thin community `Community 418`** (1 nodes): `workspace-datasource-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 419`** (1 nodes): `datasource-workflow-api.spec.ts` +- **Thin community `Community 419`** (1 nodes): `graph-acceleration-chaos.e2e-spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 420`** (1 nodes): `rag-multi-datasource-isolation.spec.ts` +- **Thin community `Community 420`** (1 nodes): `clarification-hybrid-balance.acceptance.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 421`** (1 nodes): `settings-api.spec.ts` +- **Thin community `Community 421`** (1 nodes): `datasource-workflow-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 422`** (1 nodes): `workspace-datasource-audit.spec.ts` +- **Thin community `Community 422`** (1 nodes): `rag-multi-datasource-isolation.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 423`** (1 nodes): `chat-api.spec.ts` +- **Thin community `Community 423`** (1 nodes): `settings-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 424`** (1 nodes): `rule-group-api.spec.ts` +- **Thin community `Community 424`** (1 nodes): `workspace-datasource-audit.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 425`** (1 nodes): `chat-table-permissions-policy.spec.ts` +- **Thin community `Community 425`** (1 nodes): `chat-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 426`** (1 nodes): `rag-r6-mixed-load.spec.ts` +- **Thin community `Community 426`** (1 nodes): `rule-group-api.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 427`** (1 nodes): `rag-cache-budget-load.spec.ts` +- **Thin community `Community 427`** (1 nodes): `chat-table-permissions-policy.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 428`** (1 nodes): `browser.ts` +- **Thin community `Community 428`** (1 nodes): `rag-r6-mixed-load.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 429`** (1 nodes): `client.ts` +- **Thin community `Community 429`** (1 nodes): `rag-cache-budget-load.spec.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 430`** (1 nodes): `models.ts` +- **Thin community `Community 430`** (1 nodes): `browser.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 431`** (1 nodes): `commonInputTypes.ts` +- **Thin community `Community 431`** (1 nodes): `client.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 432`** (1 nodes): `enums.ts` +- **Thin community `Community 432`** (1 nodes): `models.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 433`** (1 nodes): `prismaNamespace.ts` +- **Thin community `Community 433`** (1 nodes): `commonInputTypes.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 434`** (1 nodes): `prismaNamespaceBrowser.ts` +- **Thin community `Community 434`** (1 nodes): `enums.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 435`** (1 nodes): `WorkspaceDatasourceTablePermission.ts` +- **Thin community `Community 435`** (1 nodes): `prismaNamespace.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 436`** (1 nodes): `WorkspaceDatasourceTablePermissionSet.ts` +- **Thin community `Community 436`** (1 nodes): `prismaNamespaceBrowser.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 437`** (1 nodes): `ModelCatalog.ts` +- **Thin community `Community 437`** (1 nodes): `WorkspaceDatasourceTablePermission.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 438`** (1 nodes): `RagDocument.ts` +- **Thin community `Community 438`** (1 nodes): `WorkspaceDatasourceTablePermissionSet.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 439`** (1 nodes): `WorkspaceDatasourceBinding.ts` +- **Thin community `Community 439`** (1 nodes): `ModelCatalog.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 440`** (1 nodes): `AgentAuditLog.ts` +- **Thin community `Community 440`** (1 nodes): `RagDocument.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 441`** (1 nodes): `SqlRun.ts` +- **Thin community `Community 441`** (1 nodes): `WorkspaceDatasourceBinding.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 442`** (1 nodes): `Workspace.ts` +- **Thin community `Community 442`** (1 nodes): `AgentAuditLog.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 443`** (1 nodes): `WorkspaceMember.ts` +- **Thin community `Community 443`** (1 nodes): `SqlRun.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 444`** (1 nodes): `RagChunkIndexEntry.ts` +- **Thin community `Community 444`** (1 nodes): `Workspace.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 445`** (1 nodes): `SemanticEdge.ts` +- **Thin community `Community 445`** (1 nodes): `WorkspaceMember.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 446`** (1 nodes): `GraphSnapshot.ts` +- **Thin community `Community 446`** (1 nodes): `RagChunkIndexEntry.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 447`** (1 nodes): `RagIndexVersion.ts` +- **Thin community `Community 447`** (1 nodes): `SemanticEdge.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 448`** (1 nodes): `GlossaryTerm.ts` +- **Thin community `Community 448`** (1 nodes): `GraphSnapshot.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 449`** (1 nodes): `SemanticMemory.ts` +- **Thin community `Community 449`** (1 nodes): `RagIndexVersion.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 450`** (1 nodes): `Datasource.ts` +- **Thin community `Community 450`** (1 nodes): `GlossaryTerm.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 451`** (1 nodes): `ProviderConfig.ts` +- **Thin community `Community 451`** (1 nodes): `SemanticMemory.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 452`** (1 nodes): `WorkspaceModelingGraphRevision.ts` +- **Thin community `Community 452`** (1 nodes): `Datasource.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 453`** (1 nodes): `SemanticSpineSnapshot.ts` +- **Thin community `Community 453`** (1 nodes): `ProviderConfig.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 454`** (1 nodes): `Message.ts` +- **Thin community `Community 454`** (1 nodes): `WorkspaceModelingGraphRevision.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 455`** (1 nodes): `Session.ts` +- **Thin community `Community 455`** (1 nodes): `SemanticSpineSnapshot.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 456`** (1 nodes): `RagChunk.ts` +- **Thin community `Community 456`** (1 nodes): `Message.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 457`** (1 nodes): `SemanticRegistryTerm.ts` +- **Thin community `Community 457`** (1 nodes): `Session.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 458`** (1 nodes): `GlossaryAnchor.ts` +- **Thin community `Community 458`** (1 nodes): `RagChunk.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 459`** (1 nodes): `PlatformUser.ts` +- **Thin community `Community 459`** (1 nodes): `SemanticRegistryTerm.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 460`** (1 nodes): `SemanticRegistryVersion.ts` +- **Thin community `Community 460`** (1 nodes): `GlossaryAnchor.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 461`** (1 nodes): `RagRunReplay.ts` +- **Thin community `Community 461`** (1 nodes): `PlatformUser.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 462`** (1 nodes): `EvaluationReport.ts` +- **Thin community `Community 462`** (1 nodes): `SemanticRegistryVersion.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 463`** (1 nodes): `PromptTemplate.ts` +- **Thin community `Community 463`** (1 nodes): `RagRunReplay.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 464`** (1 nodes): `express.d.ts` +- **Thin community `Community 464`** (1 nodes): `EvaluationReport.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 465`** (1 nodes): `llm-gateway.interface.ts` +- **Thin community `Community 465`** (1 nodes): `PromptTemplate.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 466`** (1 nodes): `provider-capabilities.ts` +- **Thin community `Community 466`** (1 nodes): `express.d.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 467`** (1 nodes): `provider-adapter.interface.ts` +- **Thin community `Community 467`** (1 nodes): `llm-gateway.interface.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 468`** (1 nodes): `index.ts` +- **Thin community `Community 468`** (1 nodes): `provider-capabilities.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 469`** (1 nodes): `index.ts` +- **Thin community `Community 469`** (1 nodes): `provider-adapter.interface.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 470`** (1 nodes): `modeling-graph.types.ts` +- **Thin community `Community 470`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. - **Thin community `Community 471`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 472`** (1 nodes): `langsmith.types.ts` +- **Thin community `Community 472`** (1 nodes): `modeling-graph.types.ts` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 473`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 473`** (1 nodes): `rag-retrieval.types.ts` +- **Thin community `Community 474`** (1 nodes): `langsmith.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 474`** (1 nodes): `knowledge-rag.contract.ts` +- **Thin community `Community 475`** (1 nodes): `rag-retrieval.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 475`** (1 nodes): `knowledge-memory.contract.ts` +- **Thin community `Community 476`** (1 nodes): `knowledge-rag.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 476`** (1 nodes): `knowledge-facade.contract.ts` +- **Thin community `Community 477`** (1 nodes): `knowledge-memory.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 477`** (1 nodes): `knowledge-semantic-registry.contract.ts` +- **Thin community `Community 478`** (1 nodes): `knowledge-facade.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 478`** (1 nodes): `knowledge-glossary.contract.ts` +- **Thin community `Community 479`** (1 nodes): `knowledge-semantic-registry.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 479`** (1 nodes): `rag-retrieval.types.ts` +- **Thin community `Community 480`** (1 nodes): `knowledge-glossary.contract.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 480`** (1 nodes): `modeling-graph.validator.ts` +- **Thin community `Community 481`** (1 nodes): `rag-retrieval.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 481`** (1 nodes): `modeling-graph.types.ts` +- **Thin community `Community 482`** (1 nodes): `modeling-graph.validator.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 482`** (1 nodes): `semantic-spine.types.ts` +- **Thin community `Community 483`** (1 nodes): `modeling-graph.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 483`** (1 nodes): `index.ts` +- **Thin community `Community 484`** (1 nodes): `semantic-spine.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 484`** (1 nodes): `query-executor.interface.ts` +- **Thin community `Community 485`** (1 nodes): `index.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. -- **Thin community `Community 485`** (1 nodes): `agent.types.ts` +- **Thin community `Community 486`** (1 nodes): `query-executor.interface.ts` + Too small to be a meaningful cluster - may be noise or needs more connections extracted. +- **Thin community `Community 487`** (1 nodes): `agent.types.ts` Too small to be a meaningful cluster - may be noise or needs more connections extracted. ## Suggested Questions _Questions this graph is uniquely positioned to answer:_ -- **Why does `ok()` connect `Community 3` to `Community 0`, `Community 12`, `Community 5`?** - _High betweenness centrality (0.044) - this node is a cross-community bridge._ -- **Why does `DatasourceService` connect `Community 10` to `Community 0`?** - _High betweenness centrality (0.022) - this node is a cross-community bridge._ +- **Why does `ok()` connect `Community 2` to `Community 12`, `Community 5`, `Community 14`?** + _High betweenness centrality (0.021) - this node is a cross-community bridge._ +- **Why does `RagRetrievalService` connect `Community 13` to `Community 32`, `Community 17`?** + _High betweenness centrality (0.021) - this node is a cross-community bridge._ - **Are the 85 inferred relationships involving `ok()` (e.g. with `.applyFeedback()` and `.createSession()`) actually correct?** _`ok()` has 85 INFERRED edges - model-reasoned connections that need verification._ - **What connects `ELK`, `ElkLayoutTimeoutError`, `AppModule` to the rest of the system?** @@ -3190,6 +3210,6 @@ _Questions this graph is uniquely positioned to answer:_ - **Should `Community 0` be split into smaller, more focused modules?** _Cohesion score 0.03 - nodes in this community are weakly interconnected._ - **Should `Community 1` be split into smaller, more focused modules?** - _Cohesion score 0.03 - nodes in this community are weakly interconnected._ + _Cohesion score 0.02 - nodes in this community are weakly interconnected._ - **Should `Community 2` be split into smaller, more focused modules?** _Cohesion score 0.03 - nodes in this community are weakly interconnected._ \ No newline at end of file diff --git a/graphify-out/graph.html b/graphify-out/graph.html index 0624df1..b02d54f 100644 --- a/graphify-out/graph.html +++ b/graphify-out/graph.html @@ -50,12 +50,12 @@

Node Info

Communities

-
3481 nodes · 6638 edges · 486 communities
+
3616 nodes · 7005 edges · 488 communities