import type { KdHarnessMode, KdPhase } from "./types.ts"; export interface ContractField { id: string; label: string; aliases: string[]; question: string; } export const IMPLEMENTATION_CONTRACT_FIELDS: ContractField[] = [ { id: "trigger", label: "触发入口或执行时机", aliases: ["触发入口", "执行时机", "触发时机", "插件类型和事件", "入口事件"], question: "该实现由哪个入口触发,执行时机是什么?", }, { id: "source", label: "源对象/输入数据", aliases: ["源对象", "源单", "输入数据", "数据来源", "取数来源"], question: "实现读取的源对象或输入数据是什么?", }, { id: "target", label: "目标对象/输出结果", aliases: ["目标对象", "目标单据", "目标表单", "输出结果", "生成结果"], question: "实现写入、生成或影响的目标对象是什么?", }, { id: "data-change", label: "数据变化或字段映射", aliases: ["数据变化", "字段映射", "字段对应", "字段改写", "赋值规则"], question: "列出新增、修改、映射或保持不变的字段。", }, { id: "rules", label: "业务规则和适用条件", aliases: ["业务规则", "适用条件", "执行条件", "过滤条件", "前置条件"], question: "该实现在哪些条件下执行,业务规则是什么?", }, { id: "failure", label: "失败处理、回滚或人工处理方式", aliases: ["失败处理", "异常处理", "错误处理", "回滚", "补偿", "人工处理"], question: "失败、异常或重复执行时如何处理?", }, { id: "acceptance", label: "验收样例或测试数据", aliases: ["验收样例", "测试数据", "请求样例", "响应样例", "示例数据"], question: "用于验收的样例数据和预期结果是什么?", }, ]; export const DATA_SOURCE_CONTEXT_FIELDS: ContractField[] = [ { id: "target-form", label: "目标 FormId/单据或表单标识", aliases: ["目标 FormId", "FormId", "Form ID", "form id", "表单标识", "单据标识", "目标单据", "目标表单"], question: "目标 FormId、单据或表单标识是什么?", }, { id: "plugin-hook", label: "插件类型和触发事件", aliases: ["插件类型", "触发事件", "生命周期", "挂载点", "插件类型和事件"], question: "插件类型和触发事件是什么?", }, { id: "field-entity", label: "字段/实体/分录标识", aliases: ["字段标识", "实体标识", "分录标识", "单据体标识", "字段/实体标识"], question: "涉及哪些字段、实体或分录标识?", }, { id: "data-access", label: "数据读取写入方式", aliases: ["数据读取写入方式", "读取方式", "写入方式", "数据访问", "取数方式"], question: "数据通过表单模型、服务、SQL/KSQL 还是接口读取和写入?", }, { id: "sql-identifiers", label: "SQL/KSQL 表名和数据库字段名", aliases: ["SQL 表名", "KSQL 表名", "表名", "数据库字段名", "字段名"], question: "SQL/KSQL 场景的表名和数据库字段名是什么?", }, ]; export const INTEGRATION_CONTEXT_FIELDS: ContractField[] = [ { id: "interface-doc", label: "接口文档来源/版本", aliases: ["接口文档", "API 文档", "文档来源", "协议文档", "OpenAPI", "Swagger"], question: "第三方接口文档来源和版本是什么?", }, { id: "direction", label: "对接方向和触发时机", aliases: ["对接方向", "触发时机", "同步方向", "数据流向"], question: "对接方向、调用方和触发时机是什么?", }, { id: "endpoint-auth", label: "接口地址、认证和密钥配置方式", aliases: ["接口地址", "认证", "鉴权", "密钥", "Token", "OAuth"], question: "接口地址、认证方式和密钥配置方式是什么?", }, { id: "field-mapping", label: "第三方字段到金蝶字段的映射", aliases: ["字段映射", "映射关系", "字段对应", "字段对照"], question: "第三方字段与金蝶字段如何对应?", }, { id: "concurrency", label: "并发/幂等策略", aliases: ["并发", "幂等", "去重", "唯一键", "重复提交"], question: "并发、重复提交和幂等如何处理?", }, { id: "retry", label: "超时、重试和限流策略", aliases: ["超时", "重试", "限流", "频率", "熔断"], question: "超时、重试和限流策略是什么?", }, { id: "error", label: "错误处理和失败补偿", aliases: ["错误处理", "异常处理", "失败处理", "失败补偿", "告警"], question: "接口失败、异常响应和补偿流程如何处理?", }, { id: "logging", label: "日志、审计和敏感信息脱敏策略", aliases: ["日志", "审计", "留痕", "脱敏", "敏感信息"], question: "列出日志字段、审计留痕和敏感信息脱敏规则。", }, { id: "samples", label: "请求/响应样例和验收数据", aliases: ["请求样例", "响应样例", "报文样例", "验收数据", "payload"], question: "请求样例、响应样例和验收数据是什么?", }, ]; export const PROJECT_PERSISTENT_RULES = [ "计划或编辑代码前必须读取本文件;本文件过期时先运行 `kcode ctx --refresh`。", "根据任务复杂度选择 Harness 模式:quick 用于简单插件、文档、只读分析和低风险小改;normal 用于更全面的工程化流程,包含完整阶段、事实和证据硬门禁。", "quick 走快速实现 + UAT 闭环:保留工作区安全、PLAN 批准文件、编码规范检查和验证记录;FormId、字段、SDK 签名和元数据证据能自动查就先查,查不到不阻塞首版代码,但必须在 UAT/验证中暴露并修复。", "信息不足时禁止开始编码。必须先使用 `kd_ask_user` 登记当前事实组问题,获得可核验答案后再继续;禁止输出 demo/sample/scaffold、模板代码或占位实现。", "已有未回答阻断问题时,必须先记录用户答案,禁止继续提出、登记或展示新问题。", "登记 open blocking question 前必须固化当前工作上下文:`contextSummary` 记录已读取文件逻辑、当前结论和恢复继续点,`sourceRefs` 记录来源文件或证据。", "每轮必须读取 Context Pack 中的 Working Set、Tool Result Contracts、Action Commit Log 和 Write Transactions;它们用于保持当前工作线程、工具结果、唯一下一动作和写入边界。", "Working Set 和 Tool Result Contracts 只能作为操作上下文和来源线索,不能替代 `run.facts`、metadata evidence 或验证证据。", "写入或编辑 source-like 生产源码前必须使用 `kd_anchor_read` 获取最新 line#hash 锚点;后续 write/edit 事务必须通过 Source Anchor 校验,缺失或陈旧锚点必须重新读取。", "需要深度项目上下文时执行 `kcode init-deep` 或 `kcode ctx --deep` 生成 `.pi/kd/DEEP_CONTEXT.md`;Context Pack 会按 Working Set 自动摘取相关目录约定。", "API 文档、SDK 文档和知识库只能证明技术用法,不能替代业务事实。FormId、单据/表单标识、字段/实体/分录标识、插件类型与事件、SQL/KSQL 表名和数据库字段名必须来自用户确认、项目元数据或 evidence。", `产品代码实现前必须具备通用实现契约:${IMPLEMENTATION_CONTRACT_FIELDS.map((field) => field.label).join("、")}。`, `涉及业务数据源时必须具备数据源上下文:${DATA_SOURCE_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`, `涉及第三方对接时必须具备接口与运行上下文:${INTEGRATION_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`, "内部插件、自动下推、字段改写、数据同步等需求不得按场景写死提示词;统一通过实现契约、数据源上下文、第三方对接上下文补齐事实。", "PLAN 自由文本不能单独证明 FormId、字段、插件事件或读写方式;门禁只信任结构化 facts 和 evidence。", "`run.facts` 是唯一结构化事实源;已回答 questions 仅为审计记录,禁止在读取状态时从历史 question 反推事实。", "`factLabel` 必须使用集中定义的事实标签或别名;未知标签、占位答案、口头确认语、待确认/TODO/TBD/按实际环境等不能解除门禁。", "用户回答 open question 后,先用 `kd_answer_user action=answer` 写入答案;用户更正事实时用 `kd_answer_user action=revise`,禁止重复询问已确认事实。", "门禁提示可暴露当前同一事实组缺失项;LLM 必须用 `kd_ask_user questions` 形成结构化问题,禁止在自由文本中散列问题清单。", "企业版 Python 插件通常没有本地构建可替代验证;BOS 注册、外部系统操作、人工功能测试和生产环境验证必须由用户提供可核验证据并记录来源。", "提示语集中管理为正式工程指令;禁止口语化、闲聊式、鼓励式提示词进入运行时规则。", "工具使用必须匹配当前会话实际可用工具和操作系统;Windows 默认使用 PowerShell、`rg`、`Get-ChildItem`、`Get-Content`,禁止假设 bash 可用。", "Windows 查找文件和目录时优先使用 `kd_find_file`、`kd_list_dir` 或 PowerShell;禁止用 Linux find/ls 语法反复调用 Pi 内置 bash。", "PowerShell 仅用于只读探索和验证命令;禁止使用 Set-Content、Out-File、重定向、Remove/Move/Copy 等文件变更命令绕过 write/edit 事务。", "写入或编辑文件时禁止使用当前工作区外的绝对路径或 home 路径;生产源码必须使用当前业务项目相对路径,并受 PLAN.md 批准文件约束。", "文件定位必须使用真实搜索或目录读取结果;工具不可用时明确说明阻塞原因和需要用户执行的命令,禁止猜测路径、反复自述工具失败或用 kd_subagent 代替基础文件搜索。", "数据库访问优先通过 `kcode db` 配置的成熟 MCP server 和 Pi MCP adapter;禁止在 KCode 主包内临时自写半成品数据库连接工具。", "数据库 MCP 连接必须绑定具体业务库、账套库或服务名;PostgreSQL 禁止连接默认 postgres/template 库做元数据判断,SQL Server 必须指定具体 AIS 库,Oracle serviceName 必须指向具体业务服务。", "禁止给 LLM 使用能枚举所有库、跨库查询或超级用户权限的数据库账号;数据库账号只授予目标业务库只读权限。", "数据库 MCP 查询到企业版 FKERNELXML 或苍穹/星瀚/旗舰版 t_meta_entitydesign.fdata 后,必须先用 `kd_metadata_parse` 或 `kcode metadata` 解析并写入数据源 evidence;禁止直接凭原始 XML 编码。", "标识使用规则:苍穹/星瀚/旗舰版/企业版的表单插件、单据模型、DynamicObject、操作插件和字段赋值默认使用 FormId、实体/分录标识和字段标识;SQL、KSQL、报表、存储过程、直接查库和数据修复脚本使用数据库表名和数据库字段名;禁止在未说明访问方式时把字段标识当数据库列名或把数据库列名当字段标识。", "数据库 MCP 只允许元数据读取和只读查询;INSERT/UPDATE/DELETE/DDL、数据修复和存储过程执行只能生成 SQL 文本交给用户执行,除非可证明为纯查询存储过程且用户明确授权。", "数据库连接凭证只能来自环境变量或用户本地私有配置;禁止写入仓库文件、阶段文档、更新日志、evidence、prompt 或工具输出。", "不做旧状态迁移兼容或旧问题答案推断;坏状态只过滤,缺失事实必须重新提问确认。", ]; export const HARNESS_MODE_INSTRUCTIONS: Record = { quick: "当前模式 quick:阶段为 execute。无工作流门禁,直接编码;获取目标对象、触发点、关键字段和业务规则后即可实现。", normal: "当前模式 normal:阶段为 discuss -> plan -> execute -> verify。适用于更全面的工程化流程,包含完整阶段、事实、数据源、SDK 和 evidence 硬门禁。", }; export function harnessModeInstruction(mode: KdHarnessMode): string { return HARNESS_MODE_INSTRUCTIONS[mode]; } export const PROMPT_STYLE_RULES = [ "使用正式、可执行的工程指令;禁止口语化、闲聊式、鼓励式表达。", "事实不足时生成阻断问题;禁止输出模板代码、占位实现或基于猜测的业务标识。", "提问必须使用 `kd_ask_user`;同一事实组可一次提出多个独立问题,每个问题必须指向可验证事实、数据标识或验收证据。", "引用顺序:当前项目文件、PLAN/SPEC、元数据 evidence、SDK 签名、验证输出。", "响应必须说明当前阶段、门禁结论、采用的事实来源和唯一下一动作;禁止同时给出多个并行动作让用户选择。", ]; export const PROMPT_DECISION_PROTOCOL = [ "先读取 Harness 状态、当前阶段资料、已问已答事实和项目上下文;禁止只根据用户最后一句话行动。", "先读取 Working Set、Tool Result Contracts、Action Commit Log 和 Write Transactions;禁止在已有工具结果或动作承诺存在时重新猜路径、重问事实或回到模板化实现。", "先读取 Source Anchors 和 Deep Context;写入 source-like 文件时必须复用最新锚点,锚点缺失或陈旧时重新读取目标文件。", "先识别当前 Harness 模式和阶段序列;只能推进到该模式包含的下一阶段,禁止要求生成当前模式已跳过的阶段产物。", "先处理恢复断点:如果存在 open resume snapshot 或 open blocking question,最新用户输入只能按回答、事实修订或明确停止/改需求解释;禁止被最新工具结果或新话题吸引而丢弃断点。", "先判断用户输入是否是在回答 open blocking question;如是,唯一动作是调用 `kd_answer_user action=answer` 记录答案。", "再判断当前阶段门禁是否阻塞;阻塞时只执行门禁要求的唯一下一动作,禁止绕过阶段推进。", "再判断是否缺少可核验事实;缺失时使用 `kd_ask_user` 登记当前事实组问题,禁止用自由文本代替工具提问。", "quick 模式中,阶段、open question、PLAN 批准文件、工作区安全和编码规范是硬门禁;元数据/SDK 证据优先自动采集,缺失时作为风险记录进入 UAT/验证闭环,不阻塞首版代码。normal 中事实和证据仍是硬门禁。", ]; export const PROMPT_FACT_PROTOCOL = [ "`run.facts` 是结构化事实唯一来源;`questions` 仅为问答审计记录。", "业务事实必须有来源分类:用户明确回答、项目元数据/evidence、当前项目文件、验证输出。", "API/SDK 文档只作为技术用法线索;不得补全 FormId、字段标识、实体标识、表名、接口字段映射、并发策略或验收数据。", "事实值包含待确认、未知、按实际环境、TODO/TBD、可以、好的、是/否等内容时,视为无效事实。", "同一事实存在冲突时,停止推进并使用 `kd_answer_user action=revise` 或通过 `kd_ask_user` 登记更正问题。", ]; export const PROMPT_QUESTION_PROTOCOL = [ "发起用户澄清只能使用 `kd_ask_user`,禁止使用其他工具或自由文本问题清单。", "`kd_ask_user.questions` 可包含同一事实组内多个独立问题;每个问题必须有 header、question、contextSummary,并在适用时提供 factLabel、sourceRefs、options。", "已有 open blocking question 时,禁止继续提问或登记新问题;唯一动作是获取并记录当前问题答案。", "带 `factLabel` 的问题必须使用集中定义标签或别名;产品类型不是 factLabel。", "`options` 使用 `{label, description}`;custom 默认启用,禁止手工添加“其他/自行输入”选项。", "问题登记后停止推进,等待用户回答;不得在同一轮继续生成方案、计划或代码。", ]; export const PROMPT_OUTPUT_CONTRACT = [ "阻塞时输出:阻塞原因、唯一下一动作、需要使用的工具或命令;不得输出实现方案或代码片段。", "恢复断点存在时输出:先复述断点中的缺口、来源和下一动作,再处理最新用户输入;禁止重新开始分析。", "提问时输出:使用 `kd_ask_user` 登记结构化问题;不得在自由文本中附带额外问题。", "提问时必须保留恢复上下文:contextSummary 包含已读取文件逻辑、当前判断、等待用户回答后继续执行的位置。", "计划时输出:目标路径、允许修改文件、查证项、验证命令、回滚方式和 evidence 要求。", "执行时输出:仅说明已修改文件、证据文件和验证结果;不得把未验证内容写成已完成。", "验证失败时输出:失败证据、定位结论、下一轮修复边界;不得直接跳到 ship。", ]; export const PROMPT_PROHIBITED_BEHAVIORS = [ "禁止根据 API 文档、SDK 记忆或知识库推断业务数据源。", "禁止生成 demo/sample/scaffold、占位实现、伪代码或仅可手工替换的模板。", "禁止用自由文本一次性向用户索要多项业务信息;同一事实组问题必须通过 `kd_ask_user.questions` 结构化提交。", "禁止重复询问已确认事实或已存在 open blocking question 的问题。", "禁止猜测路径、包名、类名、FormId、字段、实体、SQL 表名、数据库字段名或接口字段映射。", "禁止写入或编辑当前工作区外的绝对路径或 home 路径。", "禁止用子 agent、shell 失败自述或自由文本计划绕过门禁。", "禁止将外部系统操作、BOS 注册、人工功能测试或生产验证描述为 LLM 已完成。", "禁止执行会修改数据库状态的 SQL、DDL 或存储过程;只能生成脚本并要求用户自行执行。", "禁止在任何仓库文件、文档、日志或提交说明中泄露数据库密码、连接串或密钥。", "禁止因最新用户消息、最新工具输出或子 agent 结果而覆盖当前恢复断点;除非用户明确要求废弃当前任务或修订事实。", ]; export const CORE_WORKFLOW_CONSTRAINTS = [ "Harness 模式:quick=execute;normal=discuss -> plan -> execute -> verify。", "模式控制约束强度:quick 保留少数硬护栏并快速进入 UAT;normal 启用完整阶段、业务事实、数据源、SDK/TDD 和 evidence 硬门禁。", "第三方对接、SQL/KSQL、数据修复、外部系统验证、生产发布或高风险改动应使用 normal;简单表单/操作/列表插件默认 quick,拿到足够信息后直接实现并交给 UAT。", "产品代码只在 execute 阶段写入,并限于 PLAN.md 批准的文件。", "每轮执行必须以当前 run 的恢复断点、open question 和结构化 facts 为最高优先级上下文;最新消息只作为输入事件,不能替代任务状态。", "quick 写代码前必须至少有目标文件或可定位源码、触发入口、关键字段/对象和主要业务规则;其余实现契约作为质量提示和 UAT 检查项。normal 写代码前必须具备完整通用实现契约。", "业务数据源优先自动查证;quick 不因 FormId 或字段证据暂缺阻塞首版编码,但必须记录风险并通过 UAT 修复。SQL/KSQL 场景仍必须在 normal 中确认表名和数据库字段名后再交付。", "第三方对接确认接口文档、对接方向、触发时机、认证配置、字段映射、并发/幂等、重试超时限流、错误补偿、日志脱敏和验收样例后再编码。", "事实缺失时使用 kd_ask_user 登记当前事实组问题;禁止用 API 文档、SDK 知识库或推测替代业务事实。", "已有 open blocking question 时禁止继续提问;先记录该问题答案,再根据刷新后的门禁决定下一问。", "用户输入是在回答 open question 时,必须先调用 kd_answer_user action=answer 记录答案,再继续推进或登记下一个问题。", "同一 factLabel 已有当前事实时禁止重复提问;用户明确更正时使用 kd_answer_user action=revise 记录新事实和更正原因。", "run.facts 是唯一结构化事实源;questions 仅作为问答审计记录,禁止从历史 question 反推门禁事实。", "待确认、未知、按实际环境、TODO/TBD 等占位答案不能解除门禁。", "Java/C# SDK 签名以当前项目 jar/dll、构建输出或官方元数据为准。", "Java/Cosmic 使用当前项目 Gradle;C#/企业版使用 dotnet build。", "evidence 必须记录命令、Exit 和关键输出;命令无法运行时记录阻塞原因。", "外部系统操作、BOS 注册、人工功能测试和生产环境验证不能由 LLM 代办;必须要求用户提供验证结果或可核验证据,并记录证据来源。", "Windows 路径规则:读取可使用工作区内绝对路径;写入和编辑必须使用当前工作区内路径,默认使用项目相对路径。禁止写入 C:\\Users、~\\Desktop、Desktop 或其他非当前工作区路径。", "工具规则:按当前会话实际 shell 和工具执行;Windows 不假设 bash,优先用 kd_find_file/kd_list_dir/PowerShell;PowerShell 禁止执行文件变更;禁止猜路径或用 kd_subagent 代替基础搜索失败。", "工具结果、动作承诺和写入事务必须进入 run state 并随 Context Pack 注入 prompt;缺失或不一致时先修复上下文包,禁止继续业务编码。", "精确编辑规则:写入 source-like 文件前使用 kd_anchor_read 获取 line#hash 锚点;Source Anchor 缺失或校验失败时重新读取,不得凭旧上下文继续改同一文件。", "数据库能力规则:优先使用 `kcode db` 生成的 Pi MCP adapter 配置接入 PostgreSQL、SQL Server 和 Oracle;执行只读查询和元数据读取,修改类 SQL 或存储过程只生成文本并等待用户执行。", "数据库作用域规则:MCP 连接必须限定到具体业务库/账套库/serviceName;禁止使用默认维护库、跨库权限或全库枚举权限。", "元数据解析规则:企业版 FKERNELXML 和苍穹/星瀚/旗舰版 t_meta_entitydesign.fdata 必须解析为 `evidence/data-source.md` 后才能作为 FormId、实体、字段、表名或数据库字段事实使用。", "标识选择规则:模型层/插件层使用字段标识和实体标识,SQL 层使用表名和数据库字段名;当需求未明确读写方式时先澄清数据访问方式,禁止自动选择 SQL 或模型 API。", ]; export const PHASE_GUIDANCE: Record = { discuss: "梳理需求来源、范围、已知事实;如缺通用实现契约、数据源或第三方接口关键事实,使用 kd_ask_user 登记当前事实组问题。", spec: "将需求转成验收标准、实现契约、数据对象、接口契约、异常行为、依赖和风险;数据对象和字段映射必须落到可核验标识。", plan: "检查项目结构,写明目标路径、允许修改文件、通用实现契约、数据源/元数据查证项、第三方接口契约、插件挂载点、验证命令和回滚说明。", execute: "按 PLAN.md 实现,记录步骤结果、变更文件和 evidence。", verify: "运行计划中的验证命令,并用 kd_verify_result 记录结果;失败会回到 execute 修复,成功后更新 VERIFY.md、证据和残余风险。", ship: "整理 SHIP.md,包括摘要、验证证据、风险和后续事项。", }; export const PLAN_REQUIRED_CHECK_LINES = [ "涉及表单、单据、字段、实体、SQL/KSQL、数据读取写入或插件事件时,确认真实数据源/元数据后再编码;禁止只根据 API 文档编码。", "Cosmic 家族官方数据源证据:evidence/cosmic-metadata.json;企业版/BOS/本地 FKERNELXML 或 fdata 解析证据:evidence/data-source.md。", `所有产品代码实现必须写明通用实现契约后再编码:${IMPLEMENTATION_CONTRACT_FIELDS.map((field) => field.label).join("、")}。`, `进入 execute 前必须写明:${DATA_SOURCE_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`, `第三方对接必须写明:${INTEGRATION_CONTEXT_FIELDS.map((field) => field.label).join("、")}。`, "Java/C# 代码涉及 SDK 类、方法、构造器、枚举、属性时,使用 kd_sdk_signature 或项目构建输出确认真实签名后再编码。", "知识库搜索、随包 Cosmic API 查询只能作为线索,不能作为最终方法签名事实。", "SDK 签名证据:evidence/sdk-signature.md", ]; export function formatPromptLines(lines: string[]): string[] { return lines.map((line) => `- ${line}`); } export function fieldLabels(fields: ContractField[]): string[] { return fields.map((field) => field.label); } export function allFactLabels(): string[] { return fieldLabels([...IMPLEMENTATION_CONTRACT_FIELDS, ...DATA_SOURCE_CONTEXT_FIELDS, ...INTEGRATION_CONTEXT_FIELDS]); } export function formatFactLabelCatalog(): string { return [ "## 实现契约事实标签", ...formatPromptLines(fieldLabels(IMPLEMENTATION_CONTRACT_FIELDS)), "", "## 数据源上下文事实标签", ...formatPromptLines(fieldLabels(DATA_SOURCE_CONTEXT_FIELDS)), "", "## 第三方对接事实标签", ...formatPromptLines(fieldLabels(INTEGRATION_CONTEXT_FIELDS)), "", "产品类型不是 factLabel;使用 /kd product <产品> 或先登记无 factLabel 的产品确认问题。", ].join("\n"); } export function questionForMissingLabel(label: string): string | undefined { const fields = [...IMPLEMENTATION_CONTRACT_FIELDS, ...DATA_SOURCE_CONTEXT_FIELDS, ...INTEGRATION_CONTEXT_FIELDS]; return fields.find((field) => field.label === label)?.question; } export function canonicalFactLabel(label: string): string | undefined { const normalized = normalizeLabel(label); const fields = [...IMPLEMENTATION_CONTRACT_FIELDS, ...DATA_SOURCE_CONTEXT_FIELDS, ...INTEGRATION_CONTEXT_FIELDS]; return fields.find((field) => [field.label, ...field.aliases].some((item) => normalizeLabel(item) === normalized))?.label; } function normalizeLabel(label: string): string { return label.trim().toLowerCase().replace(/\s+/g, ""); }