Feature: 30 天变更索引 # 按窗口聚合发布与行为变化计数,按功能聚合能力增量并以新增场景开头,输出 Markdown 变更索引。 @edge @EC-1 Scenario: EC-1 Given 统计窗口内没有发布 When 渲染变更索引 Then 输出「统计窗口内没有发布」 @edge @EC-2 Scenario: EC-2 Given 窗口内功能增量超过 20 个 When 渲染变更索引 Then 只列前 20 个功能,其余以「其余 N 个功能有零星变化」一行收尾 @edge @EC-3 Scenario: EC-3 Given 功能改名后历史变化仍存旧 feature_key When loadWindowFeatureDeltas 执行 Then 按 feature_aliases 归并到当前功能,显示名取当前规格功能名 @edge @EC-4 Scenario: EC-4 Given 窗口内发布超过 100 次 When loadRecentReleasesWithChanges 执行 Then 按提交时间倒序最多返回 100 条发布 @happy @HP-1 Scenario: HP-1 Given 需要 30 天变更索引 When renderProgressSummary 执行 Then 输出标题为「30 天变更索引 · 产品名」的 Markdown,含窗口合计、能力进展与发布明细 @happy @HP-2 Scenario: HP-2 Given 窗口内存在锚定上线前的全量重建口径发布 When 渲染变更索引 Then 附口径注释说明重建口径含同义改写噪音 Feature: AI 规格分析与发布变化 # 连接仓库后由 AI 提取功能与场景,push 或手动触发分析,生成带源码证据的规格与发布行为变化;上一版规格措辞锚定与措辞漂移折叠,回归清单附带场景行为文本;PR 预演在 PR 中评论受影响规格并挂完成检查;额度闸门拒绝时返回可用额度与升级指引,并支持显式补分析最新 SHA。 @edge @EC-1 Scenario: EC-1 Given 全量重建模式分析(无 affectedKeys) When 写入规格版本并计算 diff Then diffSpec 第三参数为 undefined,不应用行为窗口,新功能正常记为 added @edge @EC-10 Scenario: EC-10 Given 增量模式下存在未命中规格索引的行为变更文件 When analyzeWithAI 组装增量 prompt Then 未归属行为变更文件清单显式写入 prompt,请模型特别核对 @edge @EC-11 Scenario: EC-11 Given 增量窗口中发现不属于清单内既有功能的新入口或功能拆分 When analyzeWithAI 组装增量 prompt Then prompt 要求必须作为独立功能输出,不得因不在清单里而省略 @edge @EC-19 Scenario: EC-19 Given 模型输出的单个功能场景数超过 schema 上限 40(增量锚定回显叠加变体累积) When parseAnalysisContent 在 schema 校验前执行 capScenariosPerFeature Then 该功能场景保序截断为前 40 条并打 warn 日志记录丢弃,分析继续解析成功而非整次 run 失败 @edge @EC-2 Scenario: EC-2 Given AI 输出中同一行为从不同角色/视角/措辞重复建条 When 按锚定规则处理输出 Then 与锚中既有场景语义相同的输出按沿用处理,不新增重复场景 @edge @EC-32 Scenario: EC-32 Given 模型把 features 数组漂移到未知顶层键 When normalizeTopLevelCollections 执行 Then 按 {name,scenarios} 形状识别并提升为 features,打 warn 日志 @edge @EC-33 Scenario: EC-33 Given 增量模式存在受影响功能且上一版规格锚超过 24k 字符预算 When loadPreviousSpecText 以 affectedFeatureNames 作为 prioritize 装载锚 Then 受影响功能稳定排到最前,其余功能保持 position 序,截断时先保住受影响功能的旧场景 @edge @EC-34 Scenario: EC-34 Given 增量模式组装 prompt 且锚段存在 When specAnchoringSection 生成锚段文本 Then 锚段包含「同一行为只允许一条场景」约束,禁止同一行为从不同角色或措辞重复建条 @edge @EC-35 Scenario: EC-35 Given 增量分析中受影响功能在上一版规格锚的 24k 预算内 When loadPreviousSpecText 按受影响功能名定向装入锚 Then 受影响功能稳定排到锚最前,其余功能保持 position 序,截断时先保受影响功能的旧场景 @edge @EC-36 Scenario: EC-36 Given 增量分析中模型为同一行为从不同角色或措辞重复建条 When analyzeWithAI 组装增量 prompt Then 锚段明确「同一行为只允许一条场景」,与锚中既有场景语义相同的输出按沿用处理 @edge @EC-37 Scenario: EC-37 Given 增量分析中受影响功能在上一版规格锚中不存在 When AI 按增量契约处理该功能 Then 允许重新枚举,通常每功能 3-8 个场景,happy/edge/error 全部类别逐一检查,边界与错误路径不得省略 @edge @EC-38 Scenario: EC-38 Given removed 与 added 场景 category 不同(如 happy 与 error)。 When 运行规格差异分析。 Then 不判定为变体,不折叠,仍作为独立的移除与新增变化行输出。 @edge @EC-39 Scenario: EC-39 Given removed 与 added 场景操作或结果中的数字 token 不一致(如 404 与 400)。 When 运行规格差异分析。 Then variantFieldGate 拒绝,不折叠为变体,变化行保留。 @edge @EC-4 Scenario: EC-4 Given 场景证据为空数组 When 调用 isTestOnlyEvidence 判定 Then 返回 false,不显示「测试佐证」徽标 @edge @EC-40 Scenario: EC-40 Given removed 与 added 场景否定词计数不同。 When 运行规格差异分析。 Then 不判定为变体,变化行保留。 @edge @EC-41 Scenario: EC-41 Given 场景 source 为 manual 或存在于 spec_verifications(entity_type=scenario)。 When 运行合并提案脚本。 Then 该场景被跳过并在报告中标注「人工/已核实」,不进入删除提案。 @edge @EC-42 Scenario: EC-42 Given 分析调用方未传 outputLanguage When 调用 analyzeWithAI Then 字段可选,调用不受影响 @edge @EC-43 Scenario: EC-43 Given 产品未设置 output_language When 执行分析运行 Then outputLanguage 为 undefined,提取按缺省 zh-CN 处理 @edge @EC-44 Scenario: EC-44 Given 存量 spec_versions 无语言记录。 When 迁移执行。 Then 存量版本 output_language 置为 zh-CN。 @error @ER-1 Scenario: ER-1 Given 加载上一版场景全文时数据库调用失败 When runAnalysisBody 执行 loadPreviousSpecText Then 错误被捕获并记录日志,previousSpecText 置为空数组,分析继续执行(锚定降级为仅命名约束,不阻塞分析) @error @ER-6 Scenario: ER-6 Given 场景既无 outcome 也无 result 字符串 When parseAnalysisContent 解析模型输出 Then schema 校验失败并抛错,不静默放行 @error @ER-7 Scenario: ER-7 Given 模型输出缺 product 或 release When 解析分析结果 Then 不回填 product/release,解析失败交由重试与 run 失败如实暴露 @error @ER-8 Scenario: ER-8 Given removed 与 added 场景操作/结果不等价且合并文本 contentJaccard 低于 0.5。 When 运行规格差异分析。 Then 不折叠,作为独立变化行输出,不产生 variant 审计条目。 @error @ER-9 Scenario: ER-9 Given 未提供 --repository 参数,或仓库不存在/无规格版本。 When 运行合并提案脚本。 Then 打印用法或抛出错误,进程以退出码 1 结束。 @happy @HP-1 Scenario: HP-1 Given 产品已注册且仓库可读 When 在 push 后由 webhook 触发分析 Then 生成新的规格快照与发布行为变化,每条结论带证据路径 @happy @HP-10 Scenario: HP-10 Given 指定仓库最新规格版本内存在措辞变体场景,且非 manual、未被人工核实。 When 运行 merge-variant-scenarios.ts --repository (不带 --apply)。 Then 输出按功能分组的删除/并入提案与跳过项,提示 dry-run 未改动数据。 @happy @HP-11 Scenario: HP-11 Given 已人工审阅 dry-run 提案。 When 运行 merge-variant-scenarios.ts --repository --apply。 Then 按提案删除被合并的变体场景行并打印删除行数,历史 changes 的 before/after JSON 不动。 @happy @HP-12 Scenario: HP-12 Given 产品已设置输出语言为 en 且分析 run 已加载该产品 When runAnalysisBody 组装 analyzeWithAI 上下文 Then 上下文携带 outputLanguage=en,主提取与分区提取均按该语言生成规格与发布稿 @happy @HP-13 Scenario: HP-13 Given 分析调用方传入 outputLanguage When 调用 analyzeWithAI Then context 接受该可选字段,调用正常完成 @happy @HP-14 Scenario: HP-14 Given 产品 output_language 为 en When 执行分析运行 Then loadRun 读取 outputLanguage=en,单分区与多分区提取 context 均携带 en @happy @HP-15 Scenario: HP-15 Given 产品输出语言发生切换。 When 触发首个分析 run。 Then 新 spec_version 记录新 output_language 并强制全量重建。 @happy @HP-2 Scenario: HP-2 Given 上一版规格全文超过 24k 字符且包含受影响功能 When runAnalysisBody 在受影响集计算后加载上一版场景全文并传入受影响功能名 Then 受影响功能优先装入锚,截断时先保留受影响功能的旧场景 @happy @HP-3 Scenario: HP-3 Given 多分区提取结果中同一功能出现同一行为的措辞变体 When mergeChunkResults 对场景做去重合并 Then 行为等价或近似变体的场景被折叠为一条,scenariosDeduped 计数增加 @happy @HP-4 Scenario: HP-4 Given 规格工作区展示一个证据全部来自测试/文档的场景 When 渲染 ScenarioCard Then reference 之后显示「测试佐证」徽标,悬停提示证据全部来自测试/文档、行为以人工核实为准 @happy @HP-5 Scenario: HP-5 Given 仓库已有上一版规格快照 When 再次执行分析 Then 上一版场景全文作为锚点注入 prompt,未变行为要求逐字沿用,功能简介同样逐字沿用 @happy @HP-6 Scenario: HP-6 Given 增量/carry 模式分析(存在 affectedKeys) When 写入规格版本并计算 diff Then diffSpec 收到 behavioralChangePaths,窗口外证据的新增功能可被证伪 @happy @HP-7 Scenario: HP-7 Given 增量分析中上一版规格锚包含受影响功能的旧场景 When AI 按增量契约对受影响功能做修订 Then 未变场景逐字沿用原编号与文字,变化行为改写对应条目,新增行为才新增条目,消失行为不再输出,每功能输出条数不超过锚中该功能条数 +3 @happy @HP-8 Scenario: HP-8 Given 同一功能内一个场景被移除、另一个措辞近似(同义替换)的场景被新增,category 一致且操作/结果通过严守护。 When 运行规格差异分析。 Then 该 removed↔added 对被折叠,不产生 added/removed 变化行,折叠明细中出现 reason=variant 的审计条目,原始 before/after 保留。 @happy @HP-9 Scenario: HP-9 Given 分析前读取上一版规格全文作为锚,且已知受影响功能名列表。 When 调用 loadPreviousSpecText 并传入 prioritize 受影响功能名。 Then 受影响功能按名称匹配稳定排到返回列表最前,其余功能保持 position 序,24k 锚预算优先覆盖受影响功能。 Feature: Archify 运行时准备与命令调用 # 按 release.json 校验 MIT 许可与归档 SHA-256,解压到固定运行时目录并以标记文件复用;通过子进程调用 Archify 的 deliver 与 visual-check 命令,解析 JSON 回执,失败时抛出携带回执的命令错误。 @edge @EC-1 Scenario: EC-1 Given 运行时目录缺失或标记文件与发布 SHA-256 不一致 When prepareArchify 执行 Then 清空运行时目录、重新解压归档并写入标记文件 @error @ER-1 Scenario: ER-1 Given 归档文件 SHA-256 与 release.json 不符 When verifyArchifyArchive 执行 Then 抛出 Archify archive SHA-256 mismatch @error @ER-2 Scenario: ER-2 Given Archify 命令返回 ok=false 或子进程失败但 stdout 含回执 When runArchify 执行 Then 抛出携带回执的 ArchifyCommandError,消息取首条诊断或 error @happy @HP-1 Scenario: HP-1 Given 运行时目录已存在且标记文件与发布 SHA-256 一致 When prepareArchify 执行 Then 直接返回 bin/archify.mjs 路径,不重复解压 @happy @HP-2 Scenario: HP-2 Given 需要生成架构产物 When deliverArchify 执行 Then 以 deliver architecture 参数调用 Archify,默认 quality=showcase 并解析 JSON 回执 @happy @HP-3 Scenario: HP-3 Given 需要验收已生成的架构 HTML When visualCheckArchify 执行 Then 调用 visual-check 并返回解析后的回执 Feature: Chandler OAuth 登录 # 通过 Chandler OAuth 授权码加 PKCE 换取令牌并拉取用户信息,按邮箱白名单校验登录资格。 @edge @EC-1 Scenario: EC-1 Given 配置了邮箱白名单且用户邮箱不在名单内 When assertChandlerAllowed 执行 Then 抛出 forbidden @error @ER-1 Scenario: ER-1 Given 令牌交换或用户信息请求失败 When completeChandlerOAuth 执行 Then 抛出 token_exchange_failed 或 userinfo_failed @happy @HP-1 Scenario: HP-1 Given 用户完成 Chandler 授权回调 When completeChandlerOAuth 执行 Then 返回用户资料与访问令牌 Feature: Chandler webhook 处理 # 验签后事件先落库(唯一键防重),重复事件直接 ACK;订单定位后按平台状态推进 paid 履约或 refunded 退款确认,未知单返回 404 让平台重试。 @edge @EC-1 Scenario: EC-1 Given 同一平台单号与状态的事件重复投递 When processChandlerWebhook 执行 Then 撞唯一键直接 ACK,返回 duplicate @edge @EC-2 Scenario: EC-2 Given 平台状态为 closed 等其他态 When processChandlerWebhook 执行 Then 仅记录 raw_notify,不推进状态机,返回 200 @error @ER-1 Scenario: ER-1 Given 平台单号在本地不存在 When processChandlerWebhook 执行 Then 返回 404 unknown_order,让平台重试 @error @ER-2 Scenario: ER-2 Given 事件落库或订单推进抛错 When processChandlerWebhook 执行 Then 返回 500,平台重试 @error @ER-3 Scenario: ER-3 Given 签名缺失或不匹配 When processChandlerWebhook 执行 Then 返回 401 invalid_signature @happy @HP-1 Scenario: HP-1 Given 签名正确且事件首次到达 When processChandlerWebhook 执行 Then 事件落库并推进订单状态,返回 200 ok Feature: Chandler 支付客户端与回调验签 # 以服务端 Apikey 建单与退款、用户 token 预支付与查单,幂等键分别为本地商户单号与 refund-平台单号;webhook 以 HMAC-SHA256 恒时验签并解析平台单号与状态。 @edge @EC-1 Scenario: EC-1 Given 建单响应缺少平台单号 When createChandlerOrder 执行 Then 抛出 chandler create_order returned no platform order number @edge @EC-2 Scenario: EC-2 Given 未配置 CHANDLER_API_KEY When createChandlerOrder 执行 Then 回退使用用户 token 的 Bearer 鉴权建单 @error @ER-1 Scenario: ER-1 Given webhook 签名缺失或与 HMAC 计算结果不符 When verifyChandlerWebhookSignature 执行 Then 返回 false,拒绝该回调 @error @ER-2 Scenario: ER-2 Given webhook body 不是合法 JSON 或缺平台单号与状态 When parseChandlerWebhookPayload 执行 Then 返回 null,不推进订单 @error @ER-3 Scenario: ER-3 Given Chandler 接口返回非 2xx When chandlerRequest 执行 Then 抛出携带 chandlerStatus 与解析后 chandlerBody 的错误 @happy @HP-1 Scenario: HP-1 Given 需要创建支付订单 When createChandlerOrder 执行 Then 以本地商户单号为幂等键建单并返回平台单号 @happy @HP-2 Scenario: HP-2 Given 订单已建且用户持有 token When prepayChandlerOrder 执行 Then 返回微信扫码、H5 与支付宝跳转参数 @happy @HP-3 Scenario: HP-3 Given 需要核对远端订单状态 When queryChandlerOrder 执行 Then 返回平台订单状态字符串,缺失时返回 unknown @happy @HP-4 Scenario: HP-4 Given 需要按平台单号退款 When refundChandlerOrder 执行 Then 以 refund-平台单号 为幂等键、携带必填正数金额与原因调用退款接口 Feature: Chandler 用户令牌刷新 # 按用户持久化 Chandler access/refresh token 并加密落库,临期 60 秒内用用户级 GET_LOCK 串行刷新;远端明确拒绝清行引导重登录,超时或 5xx 保留行可重试。 @edge @EC-1 Scenario: EC-1 Given 同一用户并发刷新 When getValidUserToken 执行 Then GET_LOCK 串行化,拿不到锁抛 refresh_failed @error @ER-1 Scenario: ER-1 Given 刷新被远端 4xx 明确拒绝 When getValidUserToken 执行 Then 清除该用户令牌行并抛 refresh_rejected,引导重新登录 @error @ER-2 Scenario: ER-2 Given 刷新请求超时或远端 5xx When getValidUserToken 执行 Then 保留令牌行并抛 refresh_failed,可重试 @happy @HP-1 Scenario: HP-1 Given 用户已绑定 Chandler 且 access token 未临期 When getValidUserToken 执行 Then 解密返回现有 access token,不发起刷新 @happy @HP-2 Scenario: HP-2 Given access token 已临期且存在 refresh token When getValidUserToken 执行 Then 持用户级锁刷新并写回新令牌,返回新 access token Feature: CoDelphi dogfood 技能与 MCP 直连脚本 # 以项目级技能文档固化 CoDelphi 自身的 dogfood 循环(红线、日常对账与验收、生产只读核查、部署),并提供从本地配置读取凭据直连生产 MCP 的命令行脚本。 @edge @EC-1 Scenario: EC-1 Given 调用 MCP 工具返回 RPC error When 执行 mcp-call.py '' Then 错误 JSON 打印到 stderr 并以退出码 1 结束 @edge @EC-2 Scenario: EC-2 Given 未提供任何命令行参数 When 执行 mcp-call.py Then 打印用法文档并以退出码 2 结束 @edge @EC-3 Scenario: EC-3 Given search 或 lookup 返回空结果 When 按技能文档判断账本覆盖 Then 视为账本未覆盖该行为,不反推「没有这个行为」,也不当 bug 修 @happy @HP-1 Scenario: HP-1 Given 会话未连接 codelphi MCP,需要验收工具面 When 执行 mcp-call.py tools-list Then 从 ~/.zcode/cli/config.json 读取端点与 key,调用 tools/list 并逐行打印工具名 @happy @HP-2 Scenario: HP-2 Given 需要直连生产调用某个 MCP 工具 When 执行 mcp-call.py '' Then 以 Bearer 鉴权发起 tools/call,打印 structuredContent 的 JSON @happy @HP-3 Scenario: HP-3 Given push 或部署 CoDelphi 之后需要对账 When 按技能文档执行 list_releases 与 get_release Then 核对变化清单是否属于本次 push 意图、全量重建的 added 行数是否等于真实新功能数、summary 与 changes 是否脱节,异常走 send_feedback 上报 Feature: Digest 内容组装与受众分桶 # 按发布顺序组装本期变更清单,并按 Highlight 类别确定性分桶到 support/sales/marketing 三段;每条宣布项带 specChangeId 可回溯到 spec_changes。 @edge @EC-1 Scenario: EC-1 Given 变更无 Highlight 类别 When audienceBuckets 执行 Then 该变更只进变更清单与 support 段,不进 sales/marketing @edge @EC-2 Scenario: EC-2 Given 变更 Highlight 为 Security/Access/Limits When audienceBuckets 执行 Then 该变更置顶到 support 段 @happy @HP-1 Scenario: HP-1 Given 窗口内存在行为变化 When buildDigest 执行 Then 返回周期、带 specChangeId 的变更清单与三段受众分桶 Feature: Digest 周报编排 # 滚动 7 天窗口(UTC,止于昨日)按产品汇总行为变化,确定性组装 Digest 内容并落库投递;无变化的周不生成不发,已生成过的周期不重发。 @edge @EC-1 Scenario: EC-1 Given 窗口内产品没有任何行为变化 When runDigestSweepOnce 执行 Then buildDigest 返回 null,不生成不发,也不留空行 @edge @EC-2 Scenario: EC-2 Given 该产品本期已生成过 Digest When runDigestSweepOnce 执行 Then 按 product_id 与 period_start 命中已有记录,skipped 计数加一,不重发 @happy @HP-1 Scenario: HP-1 Given 产品在窗口内有行为变化且本期未生成过 Digest When runDigestSweepOnce 执行 Then buildDigest 落 digests 表并投递通知,generated 计数加一 @happy @HP-2 Scenario: HP-2 Given 需要把 Digest 内容转成渠道或邮件文本 When renderDigestText 执行 Then 输出含周期标题、变更清单与受众分节的确定性文本,每条附证据路径 Feature: Docker 生产部署与回滚 # 以 Docker 镜像部署生产:拉镜像、幂等迁移、切换应用、冒烟检查,失败自动回滚到上一好版本并记录版本状态。 @edge @EC-1 Scenario: EC-1 Given 目标镜像已是当前运行版本 When deploy.sh 执行 Then 报错退出,不重复部署 @edge @EC-2 Scenario: EC-2 Given 另一个部署或回滚正在运行 When deploy.sh 执行 Then 获取部署锁失败并退出 @error @ER-1 Scenario: ER-1 Given 冒烟检查失败 When deploy.sh 执行 Then 回滚到上一好版本并记录状态后以错误退出 @error @ER-2 Scenario: ER-2 Given 镜像不在本地 When require_image 执行 Then 报错提示先 docker pull @happy @HP-1 Scenario: HP-1 Given 新镜像已推送且本地可拉取 When deploy.sh 执行 Then 迁移、切换、冒烟通过后记录 last_good 并输出 DEPLOY OK Feature: Explainer 渲染缓存 # 按产品与当前规格版本缓存 Explainer HTML,命中零 Chromium 调用;读写均 best-effort,缓存故障不阻断导出。 @edge @EC-1 Scenario: EC-1 Given 产品尚无规格版本 When loadCachedExplainerHtml 执行 Then 返回 null,退化为直接渲染 @error @ER-1 Scenario: ER-1 Given 缓存读写数据库失败 When loadCachedExplainerHtml 或 saveExplainerCache 执行 Then 仅记录告警,不阻断导出 @happy @HP-1 Scenario: HP-1 Given 产品已有规格版本且缓存命中 When loadCachedExplainerHtml 执行 Then 返回缓存的 HTML,不重新渲染 Feature: GitHub App 凭据与安装令牌 # 生成 App Manifest 与 JWT,转换 Manifest 得到凭据,按安装 ID 换取安装访问令牌并读取安装信息(挂起时拒绝)。 @error @ER-1 Scenario: ER-1 Given 安装已被挂起 When getAppInstallation 执行 Then 抛出 GitHub installation is suspended @error @ER-2 Scenario: ER-2 Given GitHub App API 返回非 2xx When appApiFetch 执行 Then 抛出携带状态码与响应体的错误 @happy @HP-1 Scenario: HP-1 Given 需要创建 GitHub App When codelphiManifest 执行 Then 返回含回调、webhook 与最小权限的 Manifest @happy @HP-2 Scenario: HP-2 Given 已有 Manifest 转换码 When convertManifestCode 执行 Then 返回 appId、clientId、clientSecret、pem 与 webhookSecret @happy @HP-3 Scenario: HP-3 Given 需要以安装身份访问仓库 When getInstallationAccessToken 执行 Then 返回安装令牌与过期时间 Feature: GitHub App 安装与仓库列表 # 通过 App Manifest 创建并安装 GitHub App,校验安装回调 state,按安装令牌分页列出可接入仓库。 @edge @EC-1 Scenario: EC-1 Given 安装回调 state 不匹配或缺失 When 完成安装回调 Then 返回 installation_state 错误,不保存安装 @error @ER-1 Scenario: ER-1 Given GitHub App 无权读取仓库列表或安装已断开 When 列出仓库 Then 返回 400 权限或安装不存在错误 @happy @HP-1 Scenario: HP-1 Given 工作区 editor 在集成页 When 提交 App Manifest 并完成转换回调 Then 凭据加密保存,进入安装步骤 @happy @HP-2 Scenario: HP-2 Given 已安装 GitHub App When 分页浏览可接入仓库 Then 返回该安装下的仓库列表与 hasMore Feature: GitHub App 连接管理 # 集成页展示 GitHub App 与各安装连接,可重新检查、断开连接,未创建 App 时引导创建。 @edge @EC-1 Scenario: EC-1 Given App 已创建但尚未安装 When 渲染连接列表 Then 显示「尚未安装到任何 GitHub 账号或组织」 @edge @EC-2 Scenario: EC-2 Given 连接状态非 active When 渲染操作按钮 Then 不显示断开连接按钮 @error @ER-1 Scenario: ER-1 Given 连接操作接口返回错误 When 执行检查或断开 Then 显示接口错误消息 @happy @HP-1 Scenario: HP-1 Given 已创建 GitHub App 且存在安装 When 渲染集成卡片 Then 列出各安装账号、状态与操作按钮 @happy @HP-2 Scenario: HP-2 Given 编辑者点击重新检查 When handleConnectionAction 执行 Then 调用检查接口并刷新页面 Feature: GitHub PR 文件与评论检查操作 # 分页读取 PR 变更文件,列出与原地更新 Issue 评论,并创建带结论的 check run,供 PR 预演使用。 @happy @HP-1 Scenario: HP-1 Given PR 变更文件超过 100 个 When getPullRequestFiles 执行 Then 逐页拉取直到不足 100 条,返回全部文件 @happy @HP-2 Scenario: HP-2 Given 需要更新已有预演评论 When upsertIssueComment 执行 Then 携带 commentId 时 PATCH 原评论,否则新建评论并返回 id @happy @HP-3 Scenario: HP-3 Given 预演完成需要挂检查 When createCheckRun 执行 Then 以指定结论创建 completed 检查并返回 id Feature: GitHub webhook 验签与投递幂等 # 校验 GitHub webhook HMAC 签名,按 X-GitHub-Delivery 两阶段持久化接收事实并幂等推进状态机,重复投递返回原结果。 @edge @EC-1 Scenario: EC-1 Given push 为分支删除或非默认分支 When 处理 push webhook Then 返回 202 ignored,不入队 @edge @EC-2 Scenario: EC-2 Given 仓库触发模式为 daily 或 push_count 且未达阈值 When 处理 push webhook Then pending_push_count 加一,投递标记 skipped 并返回 202 deferred_trigger,不入队 @error @ER-1 Scenario: ER-1 Given 签名缺失或不匹配 When 处理 webhook Then 返回 401 invalid_signature @error @ER-2 Scenario: ER-2 Given 阶段二入队抛可重试异常 When 处理 push webhook Then 投递标记 failed_retryable 并返回 500 delivery_retryable @happy @HP-1 Scenario: HP-1 Given 签名正确且投递首次到达 When processGitHubWebhook 执行 Then 阶段一落库 received,阶段二推进为 queued 并返回 202 与 runId @happy @HP-2 Scenario: HP-2 Given 同一 delivery 已 queued 或 skipped When 再次处理该投递 Then 幂等返回原结果,不重复入队 Feature: GitHub 仓库选择器 # 添加产品时按连接分页读取可访问仓库,支持筛选、下拉选择与手动输入,最多加载 5 页。 @edge @EC-1 Scenario: EC-1 Given 仓库数量超过 10 个 When 渲染选择器 Then 显示筛选输入框 @edge @EC-2 Scenario: EC-2 Given 已加载 5 页仍有更多 When 渲染加载更多 Then 提示已显示前 500 个仓库,其余请手动输入 @error @ER-1 Scenario: ER-1 Given 仓库列表读取失败 When 加载仓库 Then 显示「仓库列表读取失败」错误 @happy @HP-1 Scenario: HP-1 Given 已选择 GitHub 连接 When 打开仓库选择器 Then 加载首屏仓库列表并展示下拉选项 @happy @HP-2 Scenario: HP-2 Given 仓库列表仍有下一页 When 点击加载更多仓库 Then 追加下一页仓库并更新 hasMore Feature: GitHub 安装回调 state 校验 # 签发带组织与 App 身份的短期 JWT 作为安装 state,回调时校验签名、签发方、受众与组织/App 匹配。 @edge @EC-1 Scenario: EC-1 Given state 缺失、与预期不一致或组织/App 不匹配 When verifyGitHubInstallationState 执行 Then 返回 false,不保存安装 @happy @HP-1 Scenario: HP-1 Given 发起 GitHub App 安装 When createGitHubInstallationState 执行 Then 返回 10 分钟有效的 HS256 state Feature: GitHub 源码快照与变更上下文 # 从 GitHub 拉取仓库树与源码快照,按入口点、文档与目录分组优先级选取候选文件并做预算截断;同时把 push 变更上下文归一化为提交信息与预算内的文件 patch。 @edge @EC-1 Scenario: EC-1 Given push 提交数超过 2000 字符预算 When normalizeChangeContext 执行 Then 保留最新提交主题并注明省略最早的若干条 @edge @EC-2 Scenario: EC-2 Given 变更文件数超过 120 或 patch 总量超过 56000 字符 When normalizeChangeContext 执行 Then 按顶层目录轮转选取并截断 patch,保持各目录代表性 @happy @HP-1 Scenario: HP-1 Given 仓库树可读 When getSourceSnapshot 执行 Then 按优先级选取源码文件并返回带行号的源码内容,总预算 160000 字符 @happy @HP-2 Scenario: HP-2 Given push 含多个提交与多个文件 When getChangeContext 执行 Then 返回提交信息、提交时间、URL 与预算内文件 patch 列表 Feature: GitHub 访问令牌解析 # 按显式 token、连接安装令牌或环境变量 GITHUB_TOKEN 的顺序解析 GitHub 访问令牌,安装令牌带缓存并在临期前刷新。 @edge @EC-1 Scenario: EC-1 Given 未提供连接与 token When resolveGitHubToken 执行 Then 回退读取环境变量 GITHUB_TOKEN @error @ER-1 Scenario: ER-1 Given 连接状态不是 active When resolveGitHubToken 执行 Then 抛出 GitHub installation is disconnected @happy @HP-1 Scenario: HP-1 Given 请求显式携带 token When resolveGitHubToken 执行 Then 直接返回该 token @happy @HP-2 Scenario: HP-2 Given 请求携带连接 ID 与组织 ID 且缓存剩余有效期超过 5 分钟 When resolveGitHubToken 执行 Then 直接复用缓存的安装令牌 Feature: GitHub 连接检查 # 按连接 ID 与工作区读取 GitHub App 安装信息并刷新安装记录,失败时把连接标记为 disconnected 并返回 503。 @error @ER-1 Scenario: ER-1 Given 连接不存在或不属于该工作区 When checkGitHubConnection 执行 Then 返回 404 connection_not_found @error @ER-2 Scenario: ER-2 Given 读取安装信息抛错 When checkGitHubConnection 执行 Then 连接标记 disconnected 并返回 503 与错误消息 @happy @HP-1 Scenario: HP-1 Given 连接存在且安装可读取 When checkGitHubConnection 执行 Then 保存安装信息并返回 active Feature: GitHub 集成凭据存储 # GitHub App 凭据(client secret、pem、webhook secret)加密落库,按 slug 或最新一条读取解密;安装记录按组织归属保存,跨工作区安装被拒绝。 @edge @EC-1 Scenario: EC-1 Given 安装已归属其他工作区 When saveInstallation 执行 Then 抛出 GitHub installation belongs to another workspace @happy @HP-1 Scenario: HP-1 Given 完成 App Manifest 转换 When saveGitHubApp 执行 Then 凭据加密后按 slug 幂等写入 github_apps @happy @HP-2 Scenario: HP-2 Given 需要读取已保存的 App 凭据 When getGitHubAppBySlug 或 getLatestGitHubApp 执行 Then 解密返回 clientSecret、pem 与可选 webhookSecret Feature: GitLab 接入层 # 以 PRIVATE-TOKEN 访问 GitLab API,解析多级 namespace 仓库路径,拉取分支头、变更上下文与成员仓库列表;webhook 以明文 secret token 头恒时比较验签。 @edge @EC-1 Scenario: EC-1 Given 首次接入且 before 为空 When getGitlabChangeContext 执行 Then 取最新一次提交及其 diff 作为变更上下文 @error @ER-1 Scenario: ER-1 Given 未提供 token 且未配置 GITLAB_TOKEN When gitlabToken 执行 Then 抛出 GITLAB_TOKEN is not configured @error @ER-2 Scenario: ER-2 Given webhook 缺少 secret token 头或长度不符 When verifyGitlabSignature 执行 Then 返回 false,拒绝该回调 @happy @HP-1 Scenario: HP-1 Given 已配置 GitLab token 且项目可读 When getGitlabChangeContext 执行 Then 返回最新提交信息、时间、URL 与 diff 文件列表 @happy @HP-2 Scenario: HP-2 Given 需要浏览可接入 GitLab 项目 When listGitlabRepositories 执行 Then 返回成员项目列表与 hasMore Feature: LLM 调用回执记账 # 所有结构化 LLM 调用统一经 requestStructuredCompletion 发起,超时、失败、结构不可解析与成功均写入调用回执,独立生成调用以 runId 为 null 加 callKind 标注。 @error @ER-1 Scenario: ER-1 Given 调用超时或网络失败 When requestStructuredCompletion 执行 Then 写入 outcome=timeout 或 failed 的 unknown-usage 回执后抛出错误 @error @ER-2 Scenario: ER-2 Given 网关返回非 2xx When requestStructuredCompletion 执行 Then 写入 outcome=failed 的 unknown-usage 回执后抛出网关错误 @happy @HP-1 Scenario: HP-1 Given LLM 调用成功返回 When requestStructuredCompletion 执行 Then 写入 outcome=success 的回执,含 usage 与 providerRequestId Feature: locale 感知日期格式化 # formatDateIn/formatDateTimeIn 按界面语言用 Intl 格式化日期,空值与非法日期返回「—」;仪表盘、设置页、成本观测页与产品总览的日期标签统一走该函数。 @edge @EC-1 Scenario: EC-1 Given 日期值为空或无法解析为合法日期 When formatDateIn 执行 Then 返回「—」,不抛错 @edge @EC-2 Scenario: EC-2 Given 传入空值或无法解析的日期字符串 When formatDateIn 执行 Then 返回「—」 @happy @HP-1 Scenario: HP-1 Given 界面语言为 en 且设置页存在计费周期与订单记录 When 渲染设置页 Then 计费周期、宽限期、额度钱包到期与订单时间以英文日期时间格式展示 @happy @HP-2 Scenario: HP-2 Given 界面语言为 en 且产品总览存在发布时间线 When buildProductOverview 传入 locale=en 执行 Then 时间线条目与最近一次有行为变化发布的日期标签按英文月份格式展示 @happy @HP-3 Scenario: HP-3 Given 传入合法日期与 locale When formatDateIn 执行 Then 返回该 locale 下的格式化日期字符串,可自定义 dateStyle 等选项 @happy @HP-4 Scenario: HP-4 Given 需要展示日期与时间 When formatDateTimeIn 执行 Then 返回 medium 日期加 short 时间的本地化字符串 Feature: MCP key 管理与只读工具面 # MCP 连接指南、OAuth 自动发现、动态客户端注册与授权/令牌流程。 @edge @EC-1 Scenario: EC-1 Given 尚无任何 key When 渲染 key 列表 Then 显示「尚无 key」 @edge @EC-10 Scenario: EC-10 Given 授权请求使用未注册 client_id 或回跳地址不匹配 When GET 或 POST /api/mcp/oauth/authorize Then 返回 400 client 校验失败(未注册或回跳地址不匹配)。 @edge @EC-11 Scenario: EC-11 Given 用户在授权页选择拒绝 When POST /api/mcp/oauth/authorize,decision=deny Then 带 error=access_denied 回跳 redirect_uri(含 state)。 @edge @EC-12 Scenario: EC-12 Given 令牌请求缺少 client_id When POST /api/mcp/oauth/token Then 返回 400 invalid_request。 @edge @EC-13 Scenario: EC-13 Given refresh_token 分支缺少 refresh_token When POST /api/mcp/oauth/token,grant_type=refresh_token Then 返回 400 invalid_request。 @edge @EC-14 Scenario: EC-14 Given 设置页 06 面板已签发 key 并展示明文 When 查看明文区与配置卡片 Then 明文 key 仍单独显示一次并可复制;配置卡片同时提供烤入该 key 的 JSON,两者互不影响 @edge @EC-15 Scenario: EC-15 Given 无管理权限的成员查看设置页 06 面板 When 查看连接向导 Then 仍可见端点、配置 JSON 与客户端说明,但不显示签发输入框与撤销按钮 @edge @EC-2 Scenario: EC-2 Given viewer 打开 MCP 面板 When 渲染面板 Then 不显示签发与撤销入口,仅可查看列表 @edge @EC-3 Scenario: EC-3 Given 调用工具时未提供 JSON 参数 When 运行 mcp-call.py Then 以空对象作为 arguments 发送 tools/call @edge @EC-4 Scenario: EC-4 Given 尚未签发 key 或刚签发前 When 查看连接配置 Then Authorization 头显示可见占位符 ,签发后替换为真实 key @edge @EC-5 Scenario: EC-5 Given 尚未签发 key 或 key 为空白 When 渲染连接配置 Then Authorization 头回退为可见占位符 ,不写入空值 @edge @EC-7 Scenario: EC-7 Given 尚未签发 key 或签发后未保留明文 When 查看连接配置 Then Authorization 使用可见占位符 ,提示签发 key 后自动写入真实值 @edge @EC-8 Scenario: EC-8 Given 注册请求体不是合法 JSON 或缺少 client_name When POST /api/mcp/oauth/register Then 返回 400 invalid_client_metadata。 @edge @EC-9 Scenario: EC-9 Given 注册请求的 redirect_uris 为空、超过 5 个、超长或非 http/https When POST /api/mcp/oauth/register Then 返回 400 invalid_redirect_uri。 @error @ER-1 Scenario: ER-1 Given 签发或撤销请求失败 When 提交操作 Then 显示接口错误消息或「签发失败」「撤销失败」 @error @ER-11 Scenario: ER-11 Given 调用 lookup_by_files 时 paths 为空或超过 20 个 When handleMcpRpc 处理 tools/call Then 返回 JSON-RPC 错误码 -32602(INVALID_PARAMS) @error @ER-12 Scenario: ER-12 Given 调用不存在的工具或方法 When handleMcpRpc 处理 tools/call 或未知 method Then 返回 JSON-RPC 错误码 -32601(METHOD_NOT_FOUND) @error @ER-8 Scenario: ER-8 Given 设置页 06 面板签发 key 失败 When 点击「签发 key」且接口返回错误 Then 面板显示错误信息,不产生明文与配置变更,pending 状态复位 @error @ER-9 Scenario: ER-9 Given 客户端以无效凭据访问 /api/mcp When 发送带无效 Bearer token 的请求 Then 返回 401 JSON-RPC 错误(code -32001),响应头携带指向资源元数据的 WWW-Authenticate @happy @HP-1 Scenario: HP-1 Given 工作区 editor/owner 在设置页 When 填写名称并签发 key Then 返回明文仅显示一次,列表展示 key 提示与创建者 @happy @HP-10 Scenario: HP-10 Given 连接器启动连接时自动注册客户端 When POST /api/mcp/oauth/register,提交 client_name 与 1-5 个 http/https redirect_uris Then 返回 201,含 client_id、client_id_issued_at、client_name、redirect_uris。 @happy @HP-11 Scenario: HP-11 Given 已登录用户批准已注册客户端的授权请求 When GET 授权页携带 client_id、redirect_uri、code_challenge、S256,随后 POST decision=approve Then 页面展示注册应用名与回跳地址;批准后带授权码回跳 redirect_uri(含 state)。 @happy @HP-12 Scenario: HP-12 Given 连接器用授权码换取令牌 When POST /api/mcp/oauth/token,grant_type=authorization_code 并携带 client_id、code、code_verifier、redirect_uri Then 返回 access_token、token_type Bearer、expires_in 与 refresh_token。 @happy @HP-13 Scenario: HP-13 Given 访问令牌过期后静默续期 When POST /api/mcp/oauth/token,grant_type=refresh_token 并携带 client_id 与 refresh_token Then 返回新的 access_token、token_type Bearer、expires_in 与旋转后的 refresh_token。 @happy @HP-14 Scenario: HP-14 Given 管理员在设置页 06 MCP 集成面板 When 输入 key 名称并点击「签发 key」 Then key 签发成功,明文只显示一次;连接配置卡片标题切换为「已写入刚签发的 key,复制即用」,配置 JSON 中已烤入真实 key @happy @HP-15 Scenario: HP-15 Given 设置页 06 MCP 集成面板已展示连接配置 When 点击「复制配置」 Then 通用 mcpServers JSON 被写入剪贴板,按钮文案变为「已复制」 @happy @HP-16 Scenario: HP-16 Given 未签发 key 时查看设置页 06 面板 When 查看连接配置卡片 Then 卡片标题显示「签发 key 后自动写入真实值」,配置 JSON 以占位符形式展示端点与 key 位置 @happy @HP-17 Scenario: HP-17 Given 访客未登录访问 /mcp When 打开 /mcp 页面 Then 以公开外壳渲染连接指南,含端点、通用 mcpServers JSON、各客户端文件位置、OAuth 连接器说明、13 个工具清单与工作流约定,并显示「返回产品总览」链接 @happy @HP-18 Scenario: HP-18 Given 已登录用户访问 /mcp When 打开 /mcp 页面 Then 以应用外壳渲染同一连接指南内容,侧栏 MCP 项高亮 @happy @HP-19 Scenario: HP-19 Given 支持 OAuth 自动发现的连接器(ChatGPT/Claude 网页版) When 以无凭据请求访问 /api/mcp Then 返回 401 JSON-RPC 错误,WWW-Authenticate 头为 Bearer resource_metadata="/.well-known/oauth-protected-resource/api/mcp",连接器据此发现授权服务器 @happy @HP-2 Scenario: HP-2 Given 已签发 key 需要失效 When 点击撤销 Then key 标记已撤销,使用该 key 的连接立即失效 @happy @HP-20 Scenario: HP-20 Given 用户在设置页 MCP 卡片查看连接向导,且刚签发了新 key。 When 渲染通用 mcpServers JSON 模板。 Then 模板中 Authorization 为 Bearer 加新签发 key(去空白),并展示各客户端文件位置提示与连接/验证说明。 @happy @HP-21 Scenario: HP-21 Given 用户尚未签发 key 时查看连接向导。 When 渲染 mcpServers JSON 模板。 Then Authorization 使用占位符 ,提示用户先签发 key。 @happy @HP-22 Scenario: HP-22 Given ChatGPT/Claude Web 连接器请求受保护资源元数据。 When GET 资源元数据端点。 Then 返回 resource=/api/mcp、authorization_servers=[base]、scopes_supported=[mcp:read]、resource_documentation=/mcp,并带 access-control-allow-origin:* 与 5 分钟缓存头。 @happy @HP-23 Scenario: HP-23 Given 连接器请求授权服务器元数据。 When GET 授权服务器元数据端点。 Then 返回 issuer、authorize/token/register 端点、response_types=[code]、grant_types=[authorization_code,refresh_token]、code_challenge_methods=[S256]、token_endpoint_auth_method=none、scopes_supported=[mcp:read]。 @happy @HP-24 Scenario: HP-24 Given 连接器进行 RFC 7591 动态客户端注册。 When 提交 client_name 与 redirect_uris。 Then 返回 mcp_ 前缀的 client_id,客户端信息落库(名称截断 191,回跳地址 JSON 存储)。 @happy @HP-25 Scenario: HP-25 Given 用户已批准授权,连接器持授权码、PKCE verifier、回跳地址与 client_id 换取 token。 When 调用授权码换 token。 Then 校验通过后返回 access token(expiresIn=3600)与 refresh token,授权码一次性作废。 @happy @HP-26 Scenario: HP-26 Given 客户端 access token 过期,持有效 refresh token 与相同 client_id 续期。 When 调用 refresh 换新 token。 Then 返回新的 access 与 refresh token,refresh 有效期顺延 30 天,旧 refresh 立即失效。 @happy @HP-27 Scenario: HP-27 Given 客户端发起 MCP initialize 握手 When handleMcpRpc 处理 initialize 请求 Then 响应含 protocolVersion、tools 能力、serverInfo 与 instructions 工作流约定(覆盖 lookup_by_files、get_release、send_feedback 等时机) @happy @HP-28 Scenario: HP-28 Given agent 读取工具列表 When handleMcpRpc 处理 tools/list 请求 Then 返回 13 个只读工具,描述中标注调用时机(list_projects 先匹配 slug、lookup_by_files 改前必查、list_releases/get_release push 后对账、send_feedback 带三要素) @happy @HP-29 Scenario: HP-29 Given 客户端发起 initialize 握手 When handleMcpRpc 处理 initialize 请求 Then 响应除 protocolVersion、capabilities.tools 与 serverInfo 外,还携带 instructions 字段,内容含 lookup_by_files、get_release、send_feedback 等工作流约定 @happy @HP-3 Scenario: HP-3 Given 需要接入 AI 客户端 When 查看 MCP 集成面板 Then 展示 Streamable HTTP 端点、通用 mcpServers JSON 连接配置与 Claude Code / ZCode / Cursor 文件位置提示 @happy @HP-30 Scenario: HP-30 Given 客户端请求 tools/list When handleMcpRpc 处理 tools/list 请求 Then 返回只读工具清单,其中 list_projects 描述提示先用 git remote 的 owner/name 匹配产品取 slug,lookup_by_files 描述标注做计划或改文件前必查,list_releases 与 get_release 描述标注 push 后对账,send_feedback 描述要求带场景、证据、代码实际行为三要素 @happy @HP-32 Scenario: HP-32 Given agent 查看工具清单以决定调用时机 When tools/list 返回工具定义 Then list_projects 描述提示先用 git remote 的 owner/name 匹配产品取 slug,lookup_by_files 标注做计划或改文件前必查,list_releases 标注 per-push 一条且 push 后用本次 sha 找发布,get_release 标注 push 后对账,send_feedback 标注带场景/证据/实际行为三要素 @happy @HP-33 Scenario: HP-33 Given coding agent 以 API key 或 OAuth 身份连接 MCP 端点并发送 initialize 请求 When 客户端调用 initialize Then 响应包含 protocolVersion、capabilities、serverInfo,并新增 instructions 字段,值为四条工作流约定文本(计划前 lookup_by_files 反查、search_scenarios/get_feature 理解行为、push 后 list_releases/get_release 对账、send_feedback 三要素上报) @happy @HP-4 Scenario: HP-4 Given 本地已配置 ~/.zcode/cli/config.json 中的 codelphi MCP 服务 When 运行 mcp-call.py tools-list Then 逐行打印 MCP 工具名列表 @happy @HP-5 Scenario: HP-5 Given 本地已配置 codelphi MCP 服务 When 运行 mcp-call.py '' 调用工具 Then 以缩进 JSON 打印 result.structuredContent @happy @HP-8 Scenario: HP-8 Given 连接器读取授权服务器元数据 When GET /.well-known/oauth-authorization-server Then 返回 RFC 8414 授权服务器元数据 JSON(issuer 为站点 origin),附带发现响应头。 @happy @HP-9 Scenario: HP-9 Given 连接器读取受保护资源元数据 When GET /.well-known/oauth-protected-resource 或 /.well-known/oauth-protected-resource/api/mcp Then 返回 RFC 9728 受保护资源元数据 JSON,附带发现响应头。 Feature: MCP OAuth 自动发现与动态客户端注册 # 为 ChatGPT/Claude 网页连接器提供 OAuth 自动发现:RFC 8414 授权服务器元数据与 RFC 9728 受保护资源元数据(根路径与路径插入式两条推导),RFC 7591 动态客户端注册;授权页要求已注册 client 且回跳地址精确匹配,token 端点支持授权码与 refresh_token 旋转(access 1 小时、refresh 30 天顺延),MCP 端点 401 指向资源元数据。 @edge @EC-1 Scenario: EC-1 Given 未携带凭据访问 MCP 端点 When POST /api/mcp Then 返回 401,WWW-Authenticate 为 Bearer 且 resource_metadata 指向 /.well-known/oauth-protected-resource/api/mcp @edge @EC-10 Scenario: EC-10 Given 授权码已交换过、verifier 错误、回跳地址或 client 不匹配 When POST /api/mcp/oauth/token 以 authorization_code 换取令牌 Then 返回 400 invalid_grant @edge @EC-11 Scenario: EC-11 Given 注册请求的 redirect_uris 缺失、非 http(s)、格式非法、为空或超过 5 条 When POST /api/mcp/oauth/register Then 返回 400 invalid_redirect_uri,不注册客户端 @edge @EC-12 Scenario: EC-12 Given 授权页缺少 client_id、redirect_uri、code_challenge 或 method 非 S256 When GET 授权页 Then 返回 400 缺少 PKCE 参数 @edge @EC-13 Scenario: EC-13 Given 授权请求缺少 client_id、redirect_uri 或 code_challenge,或 code_challenge_method 非 S256 When GET /api/mcp/oauth/authorize Then 返回 400「缺少 PKCE 参数」 @edge @EC-14 Scenario: EC-14 Given token 请求未携带 client_id When POST /api/mcp/oauth/token Then 返回 400 invalid_request @edge @EC-3 Scenario: EC-3 Given refresh token 已被旋转重放、过期、已撤销、30 天未使用或 client 不匹配 When POST /api/mcp/oauth/token 以 refresh_token 授权类型刷新 Then 返回 400 invalid_grant @edge @EC-4 Scenario: EC-4 Given 授权请求的 client_id 未注册或 redirect_uri 与注册值不完全一致 When GET 或 POST /api/mcp/oauth/authorize Then 返回 400 client 校验失败,不签发授权码 @edge @EC-6 Scenario: EC-6 Given 注册请求的 client_name 为空白 When POST /api/mcp/oauth/register Then 返回 400 invalid_client_metadata,不写入客户端 @edge @EC-9 Scenario: EC-9 Given client_id 未注册或回跳地址与注册值不精确匹配 When 访问授权页或提交批准 Then 返回 400 client 校验失败,不签发授权码 @error @ER-1 Scenario: ER-1 Given 注册请求 client_name 为空白或 JSON 体非法 When POST /api/mcp/oauth/register Then 返回 400 invalid_client_metadata,不写入客户端 @error @ER-10 Scenario: ER-10 Given client_id 未注册或回跳地址与注册值不精确匹配 When GET 或 POST /api/mcp/oauth/authorize Then 返回 400 client 校验失败(未注册或回跳地址不匹配) @error @ER-11 Scenario: ER-11 Given token 请求缺少 client_id 或授权码参数 When POST /api/mcp/oauth/token Then 返回 400 invalid_request @error @ER-12 Scenario: ER-12 Given 授权码无效、已使用、过期,或 verifier/回跳地址/client 不匹配;refresh_token 无效 When POST /api/mcp/oauth/token Then 返回 400 invalid_grant @error @ER-13 Scenario: ER-13 Given token 请求的 grant_type 不是 authorization_code 或 refresh_token When POST /api/mcp/oauth/token Then 返回 400 unsupported_grant_type @error @ER-14 Scenario: ER-14 Given 注册请求 redirect_uris 缺失、超过 5 个、非 http(s) 或格式非法 When POST /api/mcp/oauth/register Then 返回 400 invalid_redirect_uri,不写入客户端 @error @ER-17 Scenario: ER-17 Given client_id 未注册或回跳地址与注册值不精确匹配 When GET 或 POST 授权页 Then 返回 400 client 校验失败(未注册或回跳地址不匹配) @error @ER-18 Scenario: ER-18 Given token 请求缺少 client_id、缺少授权码参数或授权类型不受支持 When POST /api/mcp/oauth/token Then 返回 400 invalid_request 或 unsupported_grant_type @error @ER-20 Scenario: ER-20 Given token 端点收到 authorization_code 与 refresh_token 之外的 grant_type When POST /api/mcp/oauth/token Then 返回 400 unsupported_grant_type @error @ER-3 Scenario: ER-3 Given client_id 未注册或回跳地址与注册值不精确匹配 When GET 或 POST /api/mcp/oauth/authorize Then 返回 400「client 校验失败(未注册或回跳地址不匹配)」,不签发授权码 @error @ER-4 Scenario: ER-4 Given token 请求缺少 client_id,或授权码交换时 verifier/回跳地址/client 不匹配、码已用过或已过期 When POST /api/mcp/oauth/token Then 缺少 client_id 返回 400 invalid_request;交换失败返回 400 invalid_grant @error @ER-5 Scenario: ER-5 Given 注册请求 client_name 为空白或 body 不是合法 JSON When POST /api/mcp/oauth/register Then 返回 400 invalid_client_metadata @happy @HP-1 Scenario: HP-1 Given 连接器读取受保护资源元数据 When GET /.well-known/oauth-protected-resource 或 /.well-known/oauth-protected-resource/api/mcp Then 两条路径返回相同元数据(resource、authorization_servers、scopes_supported、resource_documentation),带 access-control-allow-origin: * 与 5 分钟缓存 @happy @HP-10 Scenario: HP-10 Given 用户已登录且 client 已注册、回跳地址匹配 When 在授权页批准 Then 签发绑定 client_id 的一次性授权码并回跳 @happy @HP-11 Scenario: HP-11 Given 连接器持有有效授权码与 code_verifier When POST /api/mcp/oauth/token(grant_type=authorization_code) Then 返回 access_token、token_type=Bearer、expires_in 与 refresh_token @happy @HP-12 Scenario: HP-12 Given access token 过期但 refresh token 有效 When POST /api/mcp/oauth/token(grant_type=refresh_token) Then 返回新的 access_token 与旋转后的 refresh_token @happy @HP-13 Scenario: HP-13 Given 连接器读取受保护资源元数据 When GET /.well-known/oauth-protected-resource 或 /.well-known/oauth-protected-resource/api/mcp Then 两条路径返回相同元数据:resource 为站点 /api/mcp、authorization_servers 为站点 origin、scopes_supported 为 mcp:read,带 CORS 与 5 分钟缓存 @happy @HP-14 Scenario: HP-14 Given 连接器读取授权服务器元数据 When GET /.well-known/oauth-authorization-server Then 返回 issuer、authorize/token/register 端点、code 响应类型、authorization_code 与 refresh_token 授权类型、S256 PKCE、token_endpoint_auth_method=none @happy @HP-15 Scenario: HP-15 Given 用户已登录且 client 已注册、回跳地址匹配 When 在授权页批准 Then 生成绑定该 client 的一次性授权码并回跳 @happy @HP-16 Scenario: HP-16 Given 已持有有效 refresh_token When POST /api/mcp/oauth/token 以 grant_type=refresh_token 提交 Then 返回新的 access_token(1 小时)与旋转后的 refresh_token(30 天顺延) @happy @HP-17 Scenario: HP-17 Given 未认证请求访问 MCP 端点 When POST /api/mcp Then 返回 401,WWW-Authenticate 指向 /.well-known/oauth-protected-resource/api/mcp @happy @HP-18 Scenario: HP-18 Given 连接器读取发现元数据 When 请求 /.well-known/oauth-authorization-server 与 /.well-known/oauth-protected-resource Then 返回 issuer 为站点 origin 的授权服务器元数据与资源元数据,端点指向既有 /api/mcp/oauth/*,带 CORS 与 5 分钟缓存 @happy @HP-19 Scenario: HP-19 Given 连接器启动连接需要注册客户端 When POST /api/mcp/oauth/register 提交 client_name 与 redirect_uris Then 返回 201 与一次性 client_id、client_name、redirect_uris @happy @HP-20 Scenario: HP-20 Given 已注册客户端发起授权且用户已登录 When GET 授权页并批准 Then 展示注册应用名与回跳地址,批准后带授权码回跳 @happy @HP-21 Scenario: HP-21 Given 已持有授权码与 PKCE verifier When POST /api/mcp/oauth/token 以 authorization_code 换取令牌 Then 返回 access_token、token_type、expires_in 与 refresh_token @happy @HP-22 Scenario: HP-22 Given access token 过期且持有 refresh token When POST /api/mcp/oauth/token 以 refresh_token 换取令牌 Then 返回新的 access_token 与旋转后的 refresh_token,refresh 有效期顺延 @happy @HP-25 Scenario: HP-25 Given 连接器启动连接需要注册客户端 When POST /api/mcp/oauth/register 携带 client_name 与 redirect_uris Then 返回 201 与 client_id、client_id_issued_at、client_name、redirect_uris @happy @HP-26 Scenario: HP-26 Given 已注册客户端发起授权且用户已登录 When GET 授权页后 POST 批准 Then 校验 client 与回跳地址后建授权记录并带一次性授权码回跳 @happy @HP-27 Scenario: HP-27 Given 已持有授权码与 refresh token When POST /api/mcp/oauth/token 分别以 authorization_code 或 refresh_token 授权类型换取 Then 返回 access_token、token_type、expires_in 与旋转后的 refresh_token @happy @HP-28 Scenario: HP-28 Given 连接器读取受保护资源元数据 When GET /.well-known/oauth-protected-resource 或 /.well-known/oauth-protected-resource/api/mcp Then 返回 resource、authorization_servers、scopes_supported 与 resource_documentation,两条路径载荷一致并带 CORS 头 @happy @HP-31 Scenario: HP-31 Given access token 过期但持有有效 refresh token When 以 grant_type=refresh_token 调用 token 端点 Then 返回新的 access_token 与旋转后的 refresh_token,refresh 有效期顺延 @happy @HP-5 Scenario: HP-5 Given 持有授权码与 code_verifier When POST /api/mcp/oauth/token 以 authorization_code 授权类型换取 token Then 返回 access_token、token_type、expires_in 与 refresh_token,授权码一次性失效 @happy @HP-6 Scenario: HP-6 Given access token 过期但 refresh token 有效 When POST /api/mcp/oauth/token 以 refresh_token 授权类型刷新 Then 返回新的 access_token 与旋转后的 refresh_token,refresh 有效期顺延 30 天 @happy @HP-7 Scenario: HP-7 Given 连接器读取受保护资源元数据 When GET /.well-known/oauth-protected-resource 或 /.well-known/oauth-protected-resource/api/mcp Then 两条路径返回相同元数据(resource、authorization_servers、scopes_supported、resource_documentation),带 CORS 与 300 秒缓存 Feature: MCP 连接指南公开页 # 以公开或应用外壳渲染 MCP 连接指南:端点、通用 mcpServers JSON、各客户端文件位置、OAuth 连接器说明、工具清单与工作流约定,配置与指令段可一键复制。 @edge @EC-1 Scenario: EC-1 Given 未传入 locale 的 CopyBlock 使用方 When 点击复制按钮 Then 按钮文案以默认 zh-CN 显示「复制」/「已复制」 @edge @EC-18 Scenario: EC-18 Given 团队希望把同一约定共享给所有成员 When 查看 /mcp 指南的指令文件说明 Then 指南说明可再把同段贴进仓库根目录 AGENTS.md 作为可选加强,并说明已连接 agent 会随连接收到相同约定(MCP instructions) @edge @EC-21 Scenario: EC-21 Given 用户尚未签发 key When 查看通用 mcpServers JSON 配置块 Then Authorization 使用占位符 ,提示签发后自动写入真实值 @edge @EC-26 Scenario: EC-26 Given 已签发 key 后查看连接配置 When 查看通用 mcpServers JSON 代码块 Then Authorization 为真实 key,可点击复制按钮一键复制 @error @ER-1 Scenario: ER-1 Given 浏览器不支持或拒绝剪贴板写入 When 点击 CopyBlock 的「复制」按钮 Then 复制失败,按钮文案不切换为「已复制」 @happy @HP-1 Scenario: HP-1 Given 访客未登录访问 /mcp When 打开 /mcp 页面 Then 以公开外壳渲染连接指南,含端点、通用 mcpServers JSON、各客户端文件位置、OAuth 连接器说明、13 个工具清单与工作流约定,并显示「返回产品总览」链接 @happy @HP-2 Scenario: HP-2 Given 已登录用户访问 /mcp When 打开 /mcp 页面 Then 以应用外壳渲染同一连接指南内容,侧栏 MCP 项高亮 @happy @HP-27 Scenario: HP-27 Given 用户需要让 agent 在所有仓库自觉执行工作流约定 When 在 /mcp 指南中点击用户级常驻指令段的「复制」 Then 门控片段(未连接 codelphi MCP 或 remote 匹配不到时自动跳过)写入剪贴板,按钮显示「已复制」,可贴进 ~/.zcode/AGENTS.md 或 ~/.claude/CLAUDE.md @happy @HP-28 Scenario: HP-28 Given 用户需要复制通用 mcpServers 连接配置 When 在 /mcp 指南的「通用 mcpServers JSON」块点击「复制」 Then 配置 JSON 写入剪贴板,按钮文案变为「已复制」 @happy @HP-29 Scenario: HP-29 Given 英文界面语言下查看 MCP 连接指南 When 点击复制按钮 Then 按钮文案由 Copy 变为 Copied,文本写入剪贴板 Feature: OAuth 回调校验 # 校验授权码、PKCE verifier 与 state 后放行回调,失败按错误码跳转登录页或 forbidden。 @edge @EC-1 Scenario: EC-1 Given next 参数为外站地址或含换行 When safeNextPath 执行 Then 回退为 / @edge @EC-2 Scenario: EC-2 Given 回调缺少 code 或 verifier When parseOAuthCallback 执行 Then 返回 null @error @ER-1 Scenario: ER-1 Given state 缺失或不匹配 When requireOAuthCallback 执行 Then 抛出 invalid_oauth_state @error @ER-2 Scenario: ER-2 Given OAuth 失败且错误为 forbidden When oauthFailurePath 执行 Then 返回 /forbidden @happy @HP-1 Scenario: HP-1 Given 回调携带 code、verifier 且 state 与预期一致 When parseOAuthCallback 执行 Then 返回 code、verifier 与安全化的 next 路径 Feature: patch 行为性判定 # 检测同一功能内同一错误/状态码 token 的否定陈述与肯定陈述并存冲突;否定判定要求「不再」与 token 紧邻、中间无句读。 @edge @EC-1 Scenario: EC-1 Given 变更文件无 patch、语言未知或 patch 超过 4000 字符疑似截断 When 分析期执行 behavioralChangePaths Then 保守视为行为变更,文件进入行为变更集 @edge @EC-2 Scenario: EC-2 Given 同一功能内一条场景为「创建不再因 MissingOAuth2TokenError 返回 422」,另一条场景肯定陈述同一 token。 When 调用 detectOutcomeConflicts 处理该功能场景。 Then 「不再」与 token 之间无句读,判为否定,与肯定陈述构成冲突对并输出。 @edge @EC-3 Scenario: EC-3 Given token 前 16 字符窗口内不存在「不再」。 When 调用 isNegated 判定该 token。 Then 返回 false,该 token 按肯定陈述处理。 @error @ER-1 Scenario: ER-1 Given 场景结果陈述中不包含待判定 token When 调用 isNegated 判定该 token Then indexOf 返回 -1,直接返回 false @happy @HP-1 Scenario: HP-1 Given 实现文件 patch 仅含注释或空白变动 When behavioralChangePaths 执行 Then 该文件不进入行为变更集 Feature: PR 预演 # PR 触碰文件与规格索引求交,在 PR 中原地更新预演评论并挂 codelphi/behavior-preview 检查;未命中规格也挂 success,预演自身失败挂 neutral 不阻塞合并。 @edge @EC-1 Scenario: EC-1 Given PR 无变更文件 When runPrPreview 执行 Then 直接返回,不生成评论与检查 @edge @EC-2 Scenario: EC-2 Given 预演开关关闭 When runPrPreview 执行 Then 静默返回 @error @ER-1 Scenario: ER-1 Given 预演自身失败(权限/网络等) When runPrPreview 执行 Then 挂 neutral 检查并附失败原因与重试提示,不阻塞合并 @happy @HP-1 Scenario: HP-1 Given PR 触碰文件命中规格索引 When runPrPreview 执行 Then 按功能分组渲染场景编号与证据路径评论,并挂 success 检查 @happy @HP-2 Scenario: HP-2 Given PR 触碰文件未命中已知规格 When runPrPreview 执行 Then 挂 success 检查并说明未命中已知功能或场景 Feature: QA 回归清单 # 按发布的行为变化生成回归任务清单,按功能分组展示场景行为文本与命中原因,支持标记通过/未通过/跳过/待验证并附备注,可导出 Markdown;无行为变化时提示瘦身回归。 @edge @EC-1 Scenario: EC-1 Given 回归任务只有仅措辞变化来源且无文件命中理由 When loadRegressionTasks 执行 Then 该任务被过滤,不进入回归清单 @error @ER-1 Scenario: ER-1 Given 更新回归任务状态但任务不属于该产品 When setRegressionTask 执行 Then 抛出 regression_task_invalid @happy @HP-1 Scenario: HP-1 Given 发布存在场景级行为变化与文件索引命中 When planRegressionTasks 执行 Then 同一场景的多个来源合并为一个任务,理由去重,输出顺序确定 @happy @HP-3 Scenario: HP-3 Given 需要导出发布回归清单 When renderRegressionTaskMarkdown 执行 Then 按功能分组输出 Markdown,任务附理由、状态与场景情境/操作/结果 Feature: spec 文件反向索引与影响面查询 # 用最新 spec 快照重建 path → 规格的反向索引,供 PR 预演与增量分析按 diff 文件反查受影响功能与场景。 @edge @EC-1 Scenario: EC-1 Given 传入空文件清单 When loadAffectedFeatureKeys 或 loadAffectedScenarios 执行 Then 直接返回空结果,不查询数据库 @error @ER-1 Scenario: ER-1 Given 影响面查询接口失败 When SpecImpactQuery 发起查询 Then 显示「查询暂时不可用,请稍后重试。」 @happy @HP-1 Scenario: HP-1 Given 输入若干文件路径查询行为 When SpecImpactQuery 发起查询 Then 按功能聚合展示受影响场景引用、行为文本与证据路径 @happy @HP-2 Scenario: HP-2 Given 生成新规格版本 When rebuildSpecFileIndex 在同一事务内执行 Then 按证据路径重建 repository 的 spec_file_index @happy @HP-3 Scenario: HP-3 Given 给定 diff 触碰的文件清单 When loadAffectedScenarios 执行 Then 按功能聚合返回受影响场景的引用、证据路径与情境/操作/结果 Feature: 中断与积压分析恢复 # 启动时把重启遗留的 running 孤儿 run 重新排队并派发,周期 sweep 对超阈值仍无人认领的 queued run 只补跑每仓库最新一条,更旧标记 superseded。 @edge @EC-1 Scenario: EC-1 Given 补跑闸门不允许派发(额度不足等) When dispatchCandidates 执行 Then 保持 queued 不标 failed,等条件满足后下一轮补跑 @edge @EC-2 Scenario: EC-2 Given 并发派发方已认领该 run When dispatchCandidates 原子认领 Then 认领失败静默跳过,不重复派发 @happy @HP-1 Scenario: HP-1 Given 服务重启后存在遗留 running run When recoverInterruptedRuns 执行 Then run 重置为 queued 并过闸门派发,用户无需手工处理 @happy @HP-2 Scenario: HP-2 Given 某仓库存在多条超阈值 queued run When dispatchStuckQueuedRuns 执行 Then 只派发最新一条,更旧标记 superseded 终态 Feature: 产品反馈提交与失败自动记录 # 成员在产品页提交反馈;分析失败时系统按可报告性自动记录失败反馈(去重键 product+revision+error_message),管理员可把反馈转为 GitHub Issue。 @edge @EC-1 Scenario: EC-1 Given 同一产品同一 revision 同一错误已记录过 When createFeedbackReport 执行 Then 返回已有反馈 id,不重复插入 @error @ER-1 Scenario: ER-1 Given 反馈接口返回错误 When submitProductFeedback 执行 Then 抛出接口错误或「反馈提交失败」 @error @ER-2 Scenario: ER-2 Given GitHub Issue 创建失败 When submitFeedbackToGitHub 执行 Then 反馈标记 failed 并记录 submission_error,错误上抛 @happy @HP-1 Scenario: HP-1 Given 成员在产品页提交反馈 When submitProductFeedback 执行 Then 返回反馈 id @happy @HP-2 Scenario: HP-2 Given 分析 run 失败且错误可报告 When recordRunFailureFeedback 执行 Then 按去重键落 feedback_reports,来源为系统自动记录 @happy @HP-3 Scenario: HP-3 Given 管理员把反馈转为 GitHub Issue When submitFeedbackToGitHub 执行 Then 创建 Issue 并回写 submitted 与 issue URL;已存在则直接返回原 URL Feature: 产品反馈转 GitHub Issue # 管理员在反馈管理页把已提交反馈转为 GitHub Issue,成功后展示 Issue 链接,失败可重试。 @error @ER-1 Scenario: ER-1 Given GitHub Issue 创建失败 When handleGithubIssue 执行 Then 显示错误消息并把状态置为 failed,按钮变为「重试提交」 @happy @HP-1 Scenario: HP-1 Given 管理员对一条反馈点击提交到 GitHub When handleGithubIssue 执行 Then 记录 Issue 链接并把状态置为 submitted,按钮变为「查看 Issue」 Feature: 产品发布入口 # 总览页「发布产品」按钮打开对话框,勾选本次要宣布的功能(默认全选)后生成发布稿草稿并跳转发布卡校对;只读成员看到禁用按钮与权限说明,匿名演示访客不渲染该入口。 @edge @EC-1 Scenario: EC-1 Given 本次发布没有规格行为变化 When 打开发布对话框 Then 提示无可宣布项,历史发布可在发布卡手动生成 @edge @EC-2 Scenario: EC-2 Given 匿名演示访客查看总览页 When 渲染发布入口 Then 不渲染任何发布按钮或登录链接 @edge @EC-3 Scenario: EC-3 Given 登录只读成员查看总览页 When 渲染发布入口 Then 显示禁用的「发布产品 · 需编辑权限」按钮 @edge @EC-5 Scenario: EC-5 Given 匿名 demo 访客查看总览页 When 渲染发布入口 Then 不渲染任何发布入口,登录引导集中在顶栏 @error @ER-1 Scenario: ER-1 Given 生成发布稿接口返回错误 When 点击「生成发布稿」 Then 对话框内显示错误消息,不跳转 @error @ER-2 Scenario: ER-2 Given 生成发布稿草稿的接口返回错误 When 提交生成请求 Then 面板显示接口错误消息,不跳转发布卡 @happy @HP-1 Scenario: HP-1 Given 编辑者打开产品总览页 When 点击「发布产品」 Then 打开发布对话框,列出本次发布的功能及新增/更新/移除计数,默认全选 @happy @HP-2 Scenario: HP-2 Given 发布对话框已勾选若干功能 When 点击「生成发布稿」 Then POST release-notes 生成草稿,成功后显示「去校对并发布 →」链接 @happy @HP-3 Scenario: HP-3 Given 编辑者打开产品总览页 When 点击「发布产品」并勾选功能后生成草稿 Then 生成发布稿草稿并跳转到发布卡校对发布 Feature: 产品变化列表与发布卡 # 产品变化视图按发布倒序展示发布卡与行为变化,支持加载更多发布并导出 30 天变更索引。 @edge @EC-1 Scenario: EC-1 Given 产品还没有发布记录 When ProductChanges 渲染 Then 显示「还没有发布记录」空状态 @error @ER-1 Scenario: ER-1 Given 加载更多发布失败 When 点击加载更多 Then 显示「加载更多发布失败」错误 @happy @HP-1 Scenario: HP-1 Given 产品已有发布记录 When ProductChanges 渲染 Then 按发布展示发布卡与变化条目,并提供导出 30 天变更索引链接 @happy @HP-2 Scenario: HP-2 Given 发布总数超过已加载数量 When 点击加载更多 Then 追加下一页发布与变化并更新剩余数量 Feature: 产品总览与产品页 # 登录用户在产品总览查看已接入产品、活跃功能与发布统计及最近变化。产品页展示标题、仓库链接、运行状态、漂移提示与各视图 Tab,公开演示产品可匿名只读浏览。 Feature: 产品总览仪表盘 # 按产品聚合当前规格与最新变化:能力清单、最新发布的行为变化统计与按功能分组、用户影响投影、发布时间线(连续零变化发布折叠)、任务入口与信任统计。 @edge @EC-1 Scenario: EC-1 Given 最近发布中某场景级变化的对侧证据全部来自测试/文档 When buildProductOverview 计算发布稿功能勾选计数 Then 该变化不计入 changeGroups,但发布时间线 changeStats 仍按全量统计 @edge @EC-14 Scenario: EC-14 Given 某功能影响文本已满每功能上限 When buildUserImpact 执行 Then 后续文本计入 moreCount,不再追加到 userImpacts @edge @EC-15 Scenario: EC-15 Given 界面语言为英文,某产品处于分析中且已有历史功能或发布 When 查看产品卡片 Then 卡片显示英文「分析中,历史规格可查看」文案并保持可点击链接 @edge @EC-2 Scenario: EC-2 Given 界面语言为英文,最近变化列表为空 When 查看最近变化区块 Then 显示英文空态提示「Release changes will appear here after the first code analysis completes.」 @edge @EC-3 Scenario: EC-3 Given 连续多条零行为变化发布 When buildProductOverview 执行 Then 折叠为 quiet-run 分组,单条零变化发布不分组 @edge @EC-4 Scenario: EC-4 Given 最新发布没有非空用户影响文本 When buildProductOverview 执行 Then 按确定性变化统计回退,不用更早发布的影响冒充 @error @ER-1 Scenario: ER-1 Given 产品尚无任何发布 When buildProductOverview 执行 Then userImpact.state 为 none,任务入口给出「尚无发布」等真实禁用原因 @happy @HP-1 Scenario: HP-1 Given 产品存在最近一次发布且含行为变化 When buildProductOverview 执行 Then 返回按功能聚合的发布稿勾选计数、发布时间线与用户影响投影 @happy @HP-10 Scenario: HP-10 Given 界面语言为 en 的用户打开仪表盘 When 访问 / Then 日期 chip 以英文格式显示当前日期(月/日/星期) @happy @HP-11 Scenario: HP-11 Given 用户以英文界面语言打开仪表盘 When 访问首页仪表盘 Then 标题、副标题、添加产品按钮、统计卡标签与单位、区块标题说明以英文渲染,日期 chip 按英文格式显示 @happy @HP-12 Scenario: HP-12 Given 界面语言为英文,产品总览存在已接入产品 When 打开产品总览页 Then 产品卡片的功能/发布计数单位与时间线日期以英文呈现 @happy @HP-13 Scenario: HP-13 Given 界面语言为英文,产品总览无任何产品 When 打开产品总览页 Then 空态标题、正文与尾部提示以英文呈现,仍包含 npm run db:seed 命令 @happy @HP-2 Scenario: HP-2 Given 仪表盘存在产品与最近发布 When 查看产品空间与最近变化区块 Then ProductGrid 与 ReleaseTimeline 按传入 locale 渲染卡片、时间线与空状态文案 @happy @HP-3 Scenario: HP-3 Given 产品已有最新发布与行为变化 When buildProductOverview 执行 Then 返回能力清单、最新发布变化统计、按功能分组与发布时间线 @happy @HP-4 Scenario: HP-4 Given 最新发布存在已落库的用户影响文本 When buildProductOverview 执行 Then 按功能合并影响文本,每组最多 3 条并给出溢出计数,最多 3 个功能组 @happy @HP-5 Scenario: HP-5 Given 最新发布的变化中场景证据全为测试/文档 When buildProductOverview 执行 Then 该变化不进发布稿功能勾选计数,changeStats 仍计入全量 @happy @HP-6 Scenario: HP-6 Given 最新发布同时存在实现证据与测试佐证的影响文本 When buildProductOverview 执行 Then 实现证据的影响文本排在前面,测试佐证文本在有余位时补充 Feature: 产品总览页 # 产品页聚合本次发布用户影响、规格问答、快速导航、发布时间线、核心能力、覆盖率披露与知识健康面板,连续零变化发布折叠展示。 @edge @EC-1 Scenario: EC-1 Given 最新发布没有任何非空用户影响 When 渲染用户影响区 Then 有行为变化时展示确定性功能级变化统计与发布深链,无行为变化时展示「本次未发现规格行为变化」 @edge @EC-2 Scenario: EC-2 Given 时间线中存在连续零行为变化发布 When 渲染发布时间线 Then 连续零变化发布折叠为一组 details,展开后保留每个原始发布条目 @edge @EC-3 Scenario: EC-3 Given 产品尚无任何功能规格 When 渲染核心能力面板 Then 显示「尚无功能规格。」 @happy @HP-1 Scenario: HP-1 Given 产品已有最新发布且落库了用户影响说明 When 渲染 ProductOverview Then 展示「本次发布对用户的影响」面板,按功能列出用户影响文本与前后差异深链 @happy @HP-2 Scenario: HP-2 Given 最新发布未附用户影响说明但检测到规格行为变化 When 渲染用户影响面板 Then 展示变化处数与前 3 个功能的变化分组,并给出查看前后差异链接 @happy @HP-3 Scenario: HP-3 Given 产品已有功能规格 When 渲染核心能力面板 Then 按场景覆盖排序列出功能名、场景数、来源(人工维护/代码生成)与简介,点击进入规格工作台 Feature: 产品成员与邀请 # 产品级成员邀请与移除:工作区 owner 邀请邮箱,已是用户则直接加入产品成员,否则生成 7 天邀请令牌并发邮件;成员面板列出工作区成员、产品成员与待接受邀请。 @edge @EC-1 Scenario: EC-1 Given 尝试移除属于工作区的成员 When removeProductMember 执行 Then 返回 organization_member,不删除产品成员行 @error @ER-1 Scenario: ER-1 Given 接受邀请时登录邮箱与邀请邮箱不一致 When acceptProductInvitation 执行 Then 抛出 invitation_email_mismatch @happy @HP-1 Scenario: HP-1 Given 工作区 owner 邀请的邮箱已是注册用户 When inviteProductMember 执行 Then 直接写入 product_members 并返回 member_added,不发邮件 @happy @HP-2 Scenario: HP-2 Given 被邀请邮箱尚未注册 When inviteProductMember 执行 Then 写入待接受邀请、签发 7 天令牌并发送邀请邮件,返回 invitation_pending 与邀请链接 @happy @HP-3 Scenario: HP-3 Given 受邀用户携带有效令牌登录 When acceptProductInvitation 执行 Then 校验邮箱一致后写入产品成员并把邀请标记 accepted Feature: 产品成员管理 # 设置页展示工作区成员与产品级成员,owner 可邀请成员、取消邀请,可移除产品级成员。 @edge @EC-1 Scenario: EC-1 Given 邀请邮箱为空 When 提交邀请 Then 提示「请输入邮箱」,不提交 @error @ER-1 Scenario: ER-1 Given 邀请或移除成员接口失败 When 提交操作 Then 显示对应失败错误消息 @happy @HP-1 Scenario: HP-1 Given owner 在成员管理表单填写邮箱与角色 When 点击邀请成员 Then 成员已加入或创建邀请并展示邀请链接 @happy @HP-2 Scenario: HP-2 Given 存在待接受邀请 When 点击取消 Then 邀请取消并刷新页面 Feature: 产品改名与旧链接重定向 # 更新产品显示名与 slug,slug 变更时校验同工作区唯一并写入旧 slug 历史;旧 slug 访问按调用者可访问工作区解析到 canonical 地址。 @edge @EC-1 Scenario: EC-1 Given 新 slug 与同工作区其他产品冲突 When updateProductIdentity 执行 Then 返回 slug_conflict,不更新 @happy @HP-1 Scenario: HP-1 Given 工作区内的产品需要改 slug When updateProductIdentity 执行 Then 更新 products 行并把旧 slug 写入 product_slug_history,返回 updated @happy @HP-2 Scenario: HP-2 Given 用户访问已改名的旧 slug 链接 When findProductByOldSlug 执行 Then 返回调用者可访问且优先当前工作区的现 slug 与 orgSlug @happy @HP-3 Scenario: HP-3 Given 产品需要切换分析输出语言 When updateProductIdentity 以 outputLanguage 执行部分更新 Then UPDATE 语句以 COALESCE 写入 output_language,返回 updated,名称与 slug 不变 Feature: 产品标识设置 # 设置页可修改产品显示名与 slug,保存后按新地址跳转,旧链接自动重定向。 @edge @EC-1 Scenario: EC-1 Given 产品名称为空白 When 点击保存 Then 提示「请输入产品名称」,不提交 @error @ER-1 Scenario: ER-1 Given 重命名接口失败 When 点击保存 Then 显示保存失败错误消息 @happy @HP-1 Scenario: HP-1 Given 编辑者在设置页修改产品名称 When 点击保存 Then 保存成功并提示「产品信息已保存」 @happy @HP-2 Scenario: HP-2 Given 编辑者修改了 slug When 点击保存 Then 跳转到新 slug 的设置页 Feature: 产品概况人工维护 # 人工编辑产品概况时写入 manual 来源并清空自动证据,自动分析不覆盖非空人工文本。 @error @ER-1 Scenario: ER-1 Given 产品不属于该工作区 When updateProductDescription 执行 Then 返回 null @happy @HP-1 Scenario: HP-1 Given 工作区成员编辑产品概况 When updateProductDescription 执行 Then 写入 description 并标记 description_source=manual Feature: 产品注册与移除 # 注册产品时解析仓库、读取默认分支头并在事务内写入产品与仓库,按工作区默认触发模式继承;移除产品时校验归属与进行中分析,并在无同名共享产物时清理生成文件。 @edge @EC-1 Scenario: EC-1 Given 首次分析入队被额度闸门拒绝 When registrationError 执行 Then quota_exceeded 映射为 402 并提示升级套餐或购买超额包 @edge @EC-2 Scenario: EC-2 Given 注册请求未指定输出语言且工作区默认输出语言为 en When registerProduct 执行 Then products 行继承工作区默认,写入 output_language=en @edge @EC-3 Scenario: EC-3 Given 注册请求未指定输出语言且工作区无默认值 When registerProduct 执行 Then 回退写入 output_language=zh-CN @edge @EC-4 Scenario: EC-4 Given 存量工作区未配置默认语言。 When 读取 default_output_language。 Then 取默认值 zh-CN。 @error @ER-1 Scenario: ER-1 Given 产品 slug 或仓库已注册 When registrationError 执行 Then 返回 409 产品 Slug 或代码仓库已经注册 @error @ER-2 Scenario: ER-2 Given 产品存在 queued 或 running 分析 When removeProduct 执行 Then 抛出 409 当前产品正在分析,请完成后再移除 @happy @HP-1 Scenario: HP-1 Given 仓库可读且输入合法 When registerProduct 执行 Then 事务内写入产品与仓库行,返回 slug、repositoryId 与 afterSha @happy @HP-2 Scenario: HP-2 Given 注册时勾选立即分析 When queueRegistrationAnalysis 执行 Then 以 manual 来源入队首次分析并返回 run @happy @HP-3 Scenario: HP-3 Given 注册请求显式指定输出语言 When registerProduct 执行 Then products 行以请求语言写入 output_language @happy @HP-4 Scenario: HP-4 Given 工作区已设置 default_output_language。 When 在该工作区注册新产品且未显式指定语言。 Then 新产品 output_language 继承工作区默认值。 Feature: 产品注册入口与凭据前置校验 # 添加产品页在无 GitHub 连接或服务器 Token 时引导先连接 GitHub,具备凭据后展示产品信息、仓库选择与立即分析选项的表单。 @edge @EC-1 Scenario: EC-1 Given 工作区无任何 GitHub 凭据 When 打开添加产品页 Then 显示「先连接 GitHub」引导卡片与 Integrations 链接 @edge @EC-2 Scenario: EC-2 Given 工作区未设置默认输出语言 When 打开新建产品页 Then defaultOutputLanguage 为可选属性,表单按缺省语言处理 @edge @EC-3 Scenario: EC-3 Given 界面语言为英文,已具备 GitHub 凭据 When 打开添加产品页 Then 渲染添加产品表单,表单内文案为英文 @error @ER-1 Scenario: ER-1 Given 创建产品接口失败 When 提交产品表单 Then 显示「添加产品失败」错误消息 @happy @HP-1 Scenario: HP-1 Given 工作区已有 GitHub 连接或服务器 Token When 打开添加产品页 Then 渲染产品创建表单 @happy @HP-2 Scenario: HP-2 Given 工作区默认输出语言为 en When 打开添加产品页 Then 表单「分析输出语言」下拉默认选中 English,提交时随创建请求携带 @happy @HP-3 Scenario: HP-3 Given 工作区成员打开新建产品页 When 页面加载并渲染新建产品表单 Then 表单预填工作区默认输出语言,注册产品继承该默认值 @happy @HP-4 Scenario: HP-4 Given editor 以英文界面语言打开新建产品页 When 访问 /products/new Then 返回链接、标题、副标题以英文渲染,NewProductContent 按传入 locale 渲染 @happy @HP-5 Scenario: HP-5 Given 界面语言为英文,未安装 GitHub App 且无服务端令牌 When 打开添加产品页 Then 显示英文「Connect GitHub first」标题、说明与「Open Integrations」链接 Feature: 产品注册提交区 # 添加产品表单提供「添加后立即分析」选项,提交按钮按是否立即分析显示不同文案,失败显示错误消息。 @error @ER-1 Scenario: ER-1 Given 创建产品接口失败 When 提交产品表单 Then 显示错误消息 @happy @HP-1 Scenario: HP-1 Given 用户填写产品信息 When 勾选添加后立即分析并提交 Then 提交按钮显示分析态文案,成功后进入产品页 Feature: 产品视图导航与工作区切换 # 产品页按 view 参数在概览、产品地图、产品规格、发布稿、发布变化与设置之间切换,发布稿入口仅在存在发布时出现,设置入口按权限展示。 @edge @EC-1 Scenario: EC-1 Given 产品尚无任何发布 When 渲染 ProductTabs Then 不渲染发布稿入口,发布变化入口计数为 0 @edge @EC-2 Scenario: EC-2 Given 当前用户无编辑权限 When 渲染 ProductTabs Then 不渲染设置入口 @edge @EC-3 Scenario: EC-3 Given 选择发布稿视图但产品没有最新发布 When 渲染 ProductView Then 显示「尚无发布,先生成一次分析。」 @happy @HP-1 Scenario: HP-1 Given 产品已有发布且当前用户可编辑 When 渲染 ProductTabs 与 ProductView Then 展示概览、产品地图、产品规格、发布稿、发布变化与设置入口,并按 view 渲染对应视图 Feature: 产品移除 # 设置页可移除产品:二次确认后调用删除接口,成功跳回首页,失败显示错误消息。 @edge @EC-1 Scenario: EC-1 Given 尚未确认移除 When 渲染移除控件 Then 只显示「移除产品」按钮,不显示确认框 @error @ER-1 Scenario: ER-1 Given 删除接口失败 When 点击确认移除 Then 显示接口错误消息或「移除产品失败」,停留在当前页 @happy @HP-1 Scenario: HP-1 Given 编辑者点击移除产品 When 在确认框点击确认移除 Then 调用删除接口成功后跳转首页并刷新 Feature: 产品简介编辑 # 在产品页头部展示产品简介全文,空简介对编辑者显示引导文案;编辑者可通过 ✎ 打开对话框手写或由 AI 生成简介,保存后刷新页面。 @edge @EC-1 Scenario: EC-1 Given 产品简介为空且当前用户可编辑 When 渲染产品页头部 Then 显示「尚无产品简介——点击 ✎ 由 AI 生成或手写。」与编辑入口 @edge @EC-2 Scenario: EC-2 Given 产品简介为空且当前用户不可编辑 When 渲染产品页头部 Then 不渲染简介行 @edge @EC-3 Scenario: EC-3 Given 产品简介超过 90 字 When 渲染产品页头部 Then 仍全文展示,不出现截断或「展开全文」按钮 @edge @EC-4 Scenario: EC-4 Given 只读或匿名用户查看有简介的产品页 When 查看简介行 Then 简介全文展示,不显示编辑入口,也不显示展开/收起按钮。 @error @ER-1 Scenario: ER-1 Given 保存简介时接口返回错误 When 在编辑对话框点击保存 Then 对话框内显示错误消息,保持打开可重试 @happy @HP-1 Scenario: HP-1 Given 产品已有非空简介且当前用户可编辑 When 渲染产品页头部 Then 简介全文展示,右侧显示 ✎ 编辑入口 @happy @HP-2 Scenario: HP-2 Given 编辑者点击 ✎ 打开编辑对话框 When 修改内容并点击保存 Then 调用 updateDescription 保存后关闭对话框并刷新页面 @happy @HP-3 Scenario: HP-3 Given 编辑者打开有简介的产品页 When 查看简介行 Then 简介全文展示,右侧显示 ✎ 编辑入口。 Feature: 产品管理与成员管理客户端 # 前端完成产品创建、删除、概况与改名、公开演示开关、成员与邀请管理、手动分析与仓库触发模式设置。 @edge @EC-1 Scenario: EC-1 Given 产品名含大写与符号 When slugifyProductName 执行 Then 转为小写连字符 slug 并截断 64 字符 @error @ER-1 Scenario: ER-1 Given 分析请求被额度闸门拒绝 When requestAnalysis 执行 Then 抛出带友好中文 message 的错误 @error @ER-2 Scenario: ER-2 Given 删除产品响应状态不是 deleted When deleteProduct 执行 Then 抛出接口 error 或「移除产品失败」 @error @ER-3 Scenario: ER-3 Given 保存输出语言接口返回错误 When setOutputLanguage 执行 Then 抛出接口 error 或「保存输出语言设置失败」 @happy @HP-1 Scenario: HP-1 Given 需要添加产品 When createProduct 执行 Then 返回新产品的 slug @happy @HP-2 Scenario: HP-2 Given 需要改产品名或 slug When renameProduct 执行 Then 返回新 slug 与 organizationSlug 供跳转 @happy @HP-3 Scenario: HP-3 Given 需要手动触发分析 When requestAnalysis 执行 Then 返回 runId @happy @HP-4 Scenario: HP-4 Given 需要切换产品分析输出语言 When setOutputLanguage 执行 Then PATCH /api/products/[slug] 携带 outputLanguage,返回 updated Feature: 产品行为地图 # 产品行为地图(B3.2,docs/product-map-plan.md §4):功能树节点 + B3 确定性连接边的产品级一页合成。 @edge @EC-1 Scenario: EC-1 Given 连接边总数超过 60 条 When buildProductMap 执行 Then 只保留权重前 60 条边,truncatedEdgeCount 显式记录被截断数量 @edge @EC-2 Scenario: EC-2 Given 顶层功能之间没有任何共享证据连接 When clusterOrder 执行 Then 保持功能树原有名称序,不做聚簇重排 @happy @HP-1 Scenario: HP-1 Given 产品已有功能、场景与共享证据文件 When buildProductMap 执行 Then 返回按连接强度聚簇分带的节点布局与按共享引用数降序的边,前 20 条强连接默认可见 Feature: 产品行为地图连线降噪开关 # 产品行为地图的客户端开关:默认用 CSS 隐藏前 N 条强连接之外的连线,点击后仅切换容器类名展开全部连线,不发起数据请求。 @edge @EC-1 Scenario: EC-1 Given 地图无被折叠的额外连线 When 渲染地图 Then 不渲染开关按钮 @happy @HP-1 Scenario: HP-1 Given 地图存在被折叠的额外连线 When 点击「显示全部连线(+N)」 Then 容器切换为 map--all-edges,全部连线显示,按钮变为「只看强连接」 Feature: 产品访问鉴权 # 工作区成员按组织角色、非成员按 product_members 授权行鉴权;URL 携带 org 段时以 URL 为权威,无权限一律返回 null 由调用方按 404 处理。 @edge @EC-1 Scenario: EC-1 Given URL 携带 org 段且调用者属于该工作区 When requireProductAccess 执行 Then 按 URL 工作区鉴权,不依赖 cookie 当前工作区 @error @ER-1 Scenario: ER-1 Given 产品不存在、跨工作区或角色不足 When requireProductAccess 执行 Then 返回 null,由调用方按 404 处理 @happy @HP-1 Scenario: HP-1 Given 调用者是产品所属工作区成员且角色足够 When requireProductAccess 执行 Then 返回产品、真实所属工作区与 via=organization 的访问结果 @happy @HP-2 Scenario: HP-2 Given 调用者不是工作区成员但持有产品级成员授权 When requireProductAccess 执行 Then 返回 via=product 的访问结果与产品级角色 Feature: 产品详情与发布变化读取 # 按产品读取功能、场景、发布、行为变化与折叠差异,为证据补 blob 链接并标注疑似改名;发布变化支持按 offset 分页加载。 @edge @EC-1 Scenario: EC-1 Given 发布对应的规格版本无覆盖率回执 When getProductDetails 执行 Then 沿版本链向上取最近有效回执,合成零回执视为无 @error @ER-1 Scenario: ER-1 Given 产品 slug 不存在或不属于指定工作区 When getProductDetails 执行 Then 返回 null @happy @HP-1 Scenario: HP-1 Given 产品已有功能、场景与发布数据 When getProductDetails 执行 Then 返回产品、仓库、最新运行、功能与场景、发布、行为变化、回归任务、折叠差异与覆盖率回执 @happy @HP-2 Scenario: HP-2 Given 需要加载更多发布变化 When loadReleasePage 执行 Then 按 offset 与 limit 返回与首屏同构的发布、变化与折叠差异 @happy @HP-3 Scenario: HP-3 Given 产品行已设置输出语言 When getProductDetails 执行 Then 返回的 product 含 outputLanguage 字段供设置页展示 Feature: 产品说明单页渲染 # 把产品总览数据渲染为自带主题切换与打印样式的纯文本 Explainer 单页,含能力、最近变化与证据覆盖率三块。 @edge @EC-1 Scenario: EC-1 Given 本次分析未发现规格行为变化 When renderProductExplainerHtml 执行 Then 最近变化区显示「本次分析未发现规格行为变化。」 @happy @HP-1 Scenario: HP-1 Given 产品已有能力、变化与可信度数据 When renderProductExplainerHtml 执行 Then 输出含核心能力表、最近变化表与证据覆盖率表的 HTML 单页 Feature: 产品输出语言设置面板 # 产品设置页提供分析输出语言面板:展示中文与 English 选项及全量重建提示,保存后刷新页面;只读成员不渲染该面板。 @edge @EC-1 Scenario: EC-1 Given 产品只读成员打开产品设置页 When 渲染 ProductOutputLanguageSettings Then canEdit 为 false 时组件返回 null,不渲染面板 @error @ER-1 Scenario: ER-1 Given 保存输出语言接口返回错误 When 点击保存 Then 显示接口错误消息或「保存输出语言设置失败」 @happy @HP-1 Scenario: HP-1 Given 产品编辑者打开产品设置页 When 选择 English 并保存 Then 调用 setOutputLanguage 提交 PATCH,成功后提示下一次分析将全量重建为新语言版本并刷新页面 Feature: 产品页地址构造 # 站内产品页链接统一经 helper 构造为 /products/{orgSlug}/{slug} 的 canonical 地址,slug 单段地址降级为重定向入口。 @happy @HP-1 Scenario: HP-1 Given 需要为产品页生成站内链接 When productBasePath 执行 Then 返回 /products/{orgSlug}/{slug},两段均做 URL 编码 Feature: 产品页头部与演示只读入口 # 产品页头部按访问者身份决定写入口:编辑者见可用的分析按钮,登录只读成员见禁用按钮与权限说明,匿名演示访客不渲染任何写入口,登录引导集中在演示外壳顶栏。 @edge @EC-1 Scenario: EC-1 Given 匿名演示访客打开演示产品页 When 渲染头部操作区 Then 不渲染分析按钮、只读提示与登录链接,页面标记 data-can-edit=false @edge @EC-2 Scenario: EC-2 Given 登录只读成员打开产品页 When 渲染头部操作区 Then 显示禁用的「发起分析 · 需编辑权限」按钮与只读权限说明 @error @ER-1 Scenario: ER-1 Given 分析请求失败 When 点击「立即分析」 Then 显示错误消息,按钮恢复可用 @error @ER-2 Scenario: ER-2 Given 分析因额度不足被拒绝 When 点击「立即分析」 Then 显示额度不足提示与升级套餐链接 @error @ER-3 Scenario: ER-3 Given 匿名 demo 访客尝试触发写操作 When 服务端校验写权限 Then 服务端拒绝写请求,demo guard 与服务端校验仍是最终边界 @happy @HP-1 Scenario: HP-1 Given 编辑者打开产品页 When 渲染头部操作区 Then 显示可点击的「立即分析」主按钮 @happy @HP-2 Scenario: HP-2 Given 编辑者点击「立即分析」 When 请求分析接口 Then 提示「分析已排队,稍后刷新查看」并刷新页面 @happy @HP-3 Scenario: HP-3 Given 编辑者访问产品页 When 渲染头部写操作入口 Then 显示可用的分析、编辑与发布入口 @happy @HP-5 Scenario: HP-5 Given 匿名演示访客打开公开演示产品页 When 渲染产品页头部 Then 页面以 data-can-edit=false 渲染,不出现分析登录引导与发布入口 Feature: 产品页头部与简介展示 # 产品页头部整合简介行:全文展示不截断,编辑者可见 ✎ 编辑入口,空简介对编辑者给生成提示、对只读用户不渲染占位。 @edge @EC-1 Scenario: EC-1 Given 产品简介为空 When buildProductOverview 执行 Then headline 回退为「产品概览」 @edge @EC-2 Scenario: EC-2 Given 产品简介为空且当前用户可编辑 When 渲染产品简介行 Then 显示「尚无产品简介——点击 ✎ 由 AI 生成或手写」与编辑入口 @edge @EC-3 Scenario: EC-3 Given 产品简介为空且当前用户只读 When 渲染产品简介行 Then 不渲染任何占位内容 @edge @EC-5 Scenario: EC-5 Given 场景证据为空数组 When isTestOnlyEvidence 执行 Then 返回 false,不显示测试佐证徽标 @error @ER-1 Scenario: ER-1 Given 保存产品简介接口失败 When 在编辑对话框点击保存 Then 对话框内显示错误消息,保持打开可重试 @happy @HP-1 Scenario: HP-1 Given 产品简介由分析生成并带证据 When buildProductOverview 执行 Then headline 取简介文本,headlineSource 为 generated,headlineEvidence 返回证据路径 @happy @HP-2 Scenario: HP-2 Given 产品已有简介且当前用户可编辑 When 渲染产品简介行 Then 展示完整简介文本与 ✎ 编辑入口 @happy @HP-3 Scenario: HP-3 Given 场景证据全部来自测试/文档 When 渲染规格工作台场景卡片 Then 场景标签行显示「测试佐证」徽标,悬停说明行为以人工核实为准 Feature: 介绍材料生成(休眠) # 从行为账本组装事实清单、生成带锚点的 PPT 大纲并做锚定校验与 Markdown/HTML 渲染;模块已按用户裁决暂停,保留为休眠资产。 @error @ER-1 Scenario: ER-1 Given 大纲某页锚定的功能或变化不在事实清单内 When validateIntroDraft 执行 Then 返回 ok=false 并指出该页锚定对象不在事实清单内 @error @ER-2 Scenario: ER-2 Given 模型输出不含 JSON 结构或不符合 schema When parseIntroMaterial 执行 Then 抛出「AI 输出不含 JSON 结构」或「AI 大纲结构不合格」错误 @happy @HP-1 Scenario: HP-1 Given 产品已有功能、场景与带 Highlight 的行为变化 When buildIntroFactPack 执行 Then 返回按场景数排序截断的功能事实、去重后的亮点变化、最新发布与可信度统计 @happy @HP-2 Scenario: HP-2 Given 已生成大纲草稿且事实清单完整 When validateIntroDraft 执行 Then 锚点均可解析、首页 COVER 末页 BACK_COVER 时返回 ok Feature: 仓库分析触发模式设置 # 设置页按仓库选择分析触发模式(每次 push / 每天低峰一次 / 每 N 次 push + 当天兜底),可设阈值与低峰小时并显示待分析推送数。 @edge @EC-1 Scenario: EC-1 Given 选择每 N 次 push 模式 When 渲染设置表单 Then 显示每 N 次推送下拉(3/5/10)与低峰分析时间下拉 @edge @EC-2 Scenario: EC-2 Given 当前用户无编辑权限或产品无仓库 When 渲染触发设置面板 Then 不渲染该面板 @error @ER-1 Scenario: ER-1 Given 保存触发设置接口失败 When 点击保存 Then 显示接口错误消息或「保存分析触发设置失败」 @happy @HP-1 Scenario: HP-1 Given 编辑者在设置页查看仓库触发设置 When 选择触发模式并点击保存 Then 保存成功提示「分析触发设置已保存」并刷新页面 Feature: 代码仓库连接管理 # 以访问令牌接入 Gitee、GitLab 或自有 Gitea 仓库,校验凭据后加密保存连接,支持列出、重新检查与删除;检查失败把连接标记为 disconnected。 @edge @EC-1 Scenario: EC-1 Given 自有 Git 服务地址不是 HTTPS 或含账号、查询参数 When normalizeRepositoryConnectionInput 执行 Then 抛出 400 提示必须使用 HTTPS 且地址不得含账号密码或查询参数 @edge @EC-2 Scenario: EC-2 Given 自有 Git 服务主机不在 REPOSITORY_HOST_ALLOWLIST When assertAllowedRepositoryHost 执行 Then 抛出 400 提示联系管理员配置主机白名单 @error @ER-1 Scenario: ER-1 Given 连接不存在或不属于当前工作区 When checkRepositoryConnection 执行 Then 返回 404 连接不存在或不属于当前工作区 @error @ER-2 Scenario: ER-2 Given 凭据校验失败或平台不可用 When checkRepositoryConnection 执行 Then 连接标记 disconnected 并返回 503 与错误消息 @happy @HP-1 Scenario: HP-1 Given 用户提交有效的平台类型、服务地址与访问令牌 When createRepositoryConnection 执行 Then 校验账号后加密保存连接并返回 active 与账号登录名 @happy @HP-2 Scenario: HP-2 Given 已保存的连接需要重新检查 When checkRepositoryConnection 执行 Then 校验通过后刷新账号信息并返回 active Feature: 令牌式代码托管连接 # 集成页以私人令牌连接 Gitee 或自有 Git 服务(GitLab/Gitea):填写平台类型、服务地址与令牌后验证并加密保存,列出已保存连接与状态,可执行连接操作。 @edge @EC-1 Scenario: EC-1 Given 尚无已保存连接 When 渲染连接列表 Then 显示「还没有已保存的连接。」 @edge @EC-2 Scenario: EC-2 Given 自有服务未配置主机白名单 When 渲染连接指引 Then 提示需联系管理员设置 REPOSITORY_HOST_ALLOWLIST,GitLab.com 可直接连接 @edge @EC-3 Scenario: EC-3 Given 当前用户为查看者 When 渲染集成卡片 Then 不显示连接表单,提示联系编辑者或所有者配置 @error @ER-1 Scenario: ER-1 Given 连接保存失败 When 提交连接表单 Then 显示接口错误消息或「连接保存失败」 @happy @HP-1 Scenario: HP-1 Given 编辑者在集成页填写令牌 When 点击验证并保存连接 Then 验证账号后保存并提示已连接账号,刷新连接列表 Feature: 公开演示开关 # 设置页可开启或关闭产品公开演示:开启后任何人可匿名查看产品全部内容,编辑操作只作演示不保存。 @edge @EC-1 Scenario: EC-1 Given 公开演示已开启 When 渲染设置面板 Then 显示公网可见风险提示 @edge @EC-2 Scenario: EC-2 Given 当前用户无管理权限 When 渲染设置面板 Then 不渲染该面板 @error @ER-1 Scenario: ER-1 Given 保存公开演示设置失败 When 点击开启或关闭 Then 显示接口错误消息或「保存公开演示设置失败」 @happy @HP-1 Scenario: HP-1 Given 有管理权限的成员在设置页 When 点击开启或关闭公开演示 Then 调用设置接口并刷新,状态显示公开中或未公开 Feature: 公开演示访问 # 产品可标记为公开演示,匿名访问按 (orgSlug, slug) 精确解析仅放行 demo 产品,环境变量白名单作为过渡兜底,登录页展示 demo 入口。 @edge @EC-1 Scenario: EC-1 Given 产品存在但未标记 demo 且不在环境变量白名单 When resolvePublicDemoProduct 执行 Then 返回 null,匿名访问不放行 @edge @EC-2 Scenario: EC-2 Given 旧一段地址无 org 段且跨工作区存在同名产品 When resolveLegacyPublicDemoProduct 执行 Then 取第一个公开 demo 行解析,非 demo 返回 null @happy @HP-1 Scenario: HP-1 Given 产品已在设置中标记为公开演示 When resolvePublicDemoProduct 执行 Then 返回该产品的 id、slug、名称与所属工作区 slug,供匿名只读渲染 @happy @HP-2 Scenario: HP-2 Given 登录页需要展示公开演示入口 When loadPublicDemoProduct 执行 Then 优先返回数据库标记的 demo 产品,无则取环境变量白名单首个,都没有返回 null Feature: 凭据加密存储 # 以 SESSION_SECRET 派生密钥对敏感凭据做 AES-256-GCM 加密,密文含随机 IV 与认证标签并以 base64url 存储,解密时校验完整性。 @error @ER-1 Scenario: ER-1 Given 密文被篡改或密钥不匹配 When decryptSecret 执行 Then 认证标签校验失败抛错,不返回明文 @happy @HP-1 Scenario: HP-1 Given 需要保存访问令牌等敏感凭据 When encryptSecret 执行 Then 返回含 IV、认证标签与密文的 base64url 字符串 @happy @HP-2 Scenario: HP-2 Given 需要读取已保存的凭据 When decryptSecret 执行 Then 校验认证标签后返回明文 Feature: 分析失败解释与上报 # 把分析失败的技术错误归类为可读人话与下一步指引,区分是否值得自动上报,并按类别与首因生成去重签名。 @edge @EC-1 Scenario: EC-1 Given 失败原因为被更新的分析取代 When explainRunFailure 执行 Then 返回 superseded 类别且 reportable=false,不自动上报 @edge @EC-2 Scenario: EC-2 Given 错误信息不属于任何已知类别 When explainRunFailure 执行 Then 兜底为 unknown 类别,提示意外错误并标记可上报 @happy @HP-1 Scenario: HP-1 Given 分析失败原因为 AI 输出结构不符合 schema When explainRunFailure 执行 Then 返回 ai_output_format 类别、可读提示与 reportable=true @happy @HP-2 Scenario: HP-2 Given 同类同因的失败需要合并上报 When runFailureSignature 执行 Then zod 错误取首个 path 与 code 生成签名,其余取首行前 120 字符 Feature: 分析完整性账本 # run 级汇总一次成功分析完成了什么、没完成什么:六维度各自 complete/partial/failed/not_applicable,整体状态取最差维度,禁止合并成单一百分比;账本随 run 成功 best-effort 落库,可按 run 或规格版本读取。 @edge @EC-1 Scenario: EC-1 Given 实现文件装配数为 0 When deriveSourceCoverage 执行 Then sourceCoverage 状态为 failed @edge @EC-2 Scenario: EC-2 Given 分区被截断或 diff 有省略路径 When deriveSourceCoverage 执行 Then sourceCoverage 状态为 partial @edge @EC-3 Scenario: EC-3 Given 某趟候选全部被证据清洗拒绝 When deriveEvidenceIntegrity 执行 Then evidenceIntegrity 状态为 failed,并累计 allCandidatesRejectedPasses @edge @EC-4 Scenario: EC-4 Given 增量 run 未执行 M9 影子且无结果冲突 When deriveVerification 执行 Then verification 状态为 not_applicable,humanVerification 固定为 external @error @ER-1 Scenario: ER-1 Given 账本写入数据库失败 When saveCompletenessLedger 执行 Then 异常上抛,由调用方 best-effort 处理,不回滚分析 @happy @HP-1 Scenario: HP-1 Given 一次分析 run 成功且各维度数据齐备 When saveCompletenessLedger 执行 Then 写入 analysis_completeness_ledger,六维度状态与 overall 一并落库 @happy @HP-2 Scenario: HP-2 Given 需要查看某次分析的完整性 When loadCompletenessLedger 或 loadCompletenessLedgerBySpecVersion 执行 Then 返回该 run 的六维度状态、overall 与 details;无记录返回 null Feature: 分析完整性账本展示 # 把分析完整性账本六维度(源码覆盖、行为枚举、提取完成度、证据完整性、合并完整性、核验)解析为视图形状,给出总体文案、问题短因清单与维度状态行。 @edge @EC-1 Scenario: EC-1 Given 账本 details 结构不符或状态值非法 When toLedgerDetailsView 执行 Then 返回 null,不渲染维度 @happy @HP-1 Scenario: HP-1 Given 账本六维度全部 complete When describeLedgerOverall 执行 Then 返回「完整分析」与 ok 语气 @happy @HP-2 Scenario: HP-2 Given 存在 partial/failed 维度 When summarizeLedgerIssues 执行 Then 按固定维度顺序返回「标签:短因」清单,可隐藏已在别处披露的维度 Feature: 分析覆盖率回执与继承 # 读取最新规格版本的场景级全文作为分析锚,加载失败降级为仅命名约束不阻塞分析;受影响功能优先装入锚,保证 24k 预算先覆盖它们。 @edge @EC-1 Scenario: EC-1 Given 分析运行无覆盖率回执 When buildProductOverview 执行 Then coverageByte 与 coverageFiles 为 null,不展示覆盖率行 @edge @EC-2 Scenario: EC-2 Given 本次分析无覆盖率回执 When 渲染总览信任行 Then 覆盖率位置显示「源码装配覆盖率 —(无覆盖率回执)」 @edge @EC-3 Scenario: EC-3 Given 上一版为回填快照或沿用版本,无有效回执 When loadInheritedCoverageReceipt 执行 Then 沿版本链向上最多回看 10 版,跳过 implFilesTotal=0 的合成零回执,取最近有效回执 @edge @EC-4 Scenario: EC-4 Given 提取趟数达到上限 When isCoverageCapped 执行 Then 返回 true,展示层显式警示本规格未覆盖全部实现面 @edge @EC-5 Scenario: EC-5 Given 分区被截断或 diff 有未选路径 When deriveSourceCoverage 执行 Then 状态为 partial,并保留 diffTotalPaths/diffSelectedPaths/diffOmittedPaths 计数 @edge @EC-6 Scenario: EC-6 Given 传入的受影响功能名在上一版规格中不存在 When loadPreviousSpecText 执行 Then 无命中,功能顺序保持原 position 序不变 @edge @EC-7 Scenario: EC-7 Given 仓库没有任何规格版本 When loadPreviousSpecText 执行 Then 返回空数组,锚定降级为仅命名约束 @error @ER-1 Scenario: ER-1 Given 版本链上全部回执均无效 When loadInheritedCoverageReceipt 执行 Then 返回 null @error @ER-2 Scenario: ER-2 Given 未装配任何实现文件 When deriveSourceCoverage 执行 Then 状态为 failed @error @ER-3 Scenario: ER-3 Given 读取上一版规格文本抛错 When runAnalysisBody 加载锚 Then 记录错误日志,previousSpecText 置空,本次分析不因锚加载失败而中断 @happy @HP-1 Scenario: HP-1 Given 分析运行带覆盖率回执 When buildProductOverview 执行 Then 返回 coverageByte 与 coverageFiles 展示标签 @happy @HP-2 Scenario: HP-2 Given 本次分析产生覆盖率回执 When 渲染总览信任行 Then 展示源码装配覆盖率字节、证据文件数与 Spec 版本号 @happy @HP-3 Scenario: HP-3 Given 分析完成并写入规格版本 When computeCoverageReceipt 与 saveCoverageReceipt 执行 Then 回执记录装配文件数/字节、总面、覆盖率、趟数与 diff 覆盖口径并落库 @happy @HP-4 Scenario: HP-4 Given 分析 run 完成源码装配 When deriveSourceCoverage 执行 Then 覆盖率回执含 implFilesAssembled/implFilesTotal/coverageRatio,全部装配且未截断时状态为 complete @happy @HP-5 Scenario: HP-5 Given 仓库已有上一版规格且本次为增量分析 When loadPreviousSpecText 传入受影响功能名执行 Then 受影响功能按传入顺序排最前,其余功能保持 position 序返回 @happy @HP-6 Scenario: HP-6 Given 仓库已有上一版规格且本次为全量分析 When loadPreviousSpecText 不传 prioritize 执行 Then 全部功能按 position 序返回场景全文锚 Feature: 分析触发模式与兜底 sweep # 按仓库触发模式(push/daily/push_count)决定 push 后立即分析或延后,首分析任何模式都立即派发;每小时 sweep 对当天仍有未处理 push 的仓库排队累计分析,失败保留 pending 下小时重试。 @edge @EC-1 Scenario: EC-1 Given sweep 时 before 与 after 相同或无提交 When runAnalysisTriggerSweepOnce 执行 Then noCommits 计数加一并清零 pending,不排队 @error @ER-1 Scenario: ER-1 Given 单仓库排队失败(如额度闸门拒绝) When runAnalysisTriggerSweepOnce 执行 Then deferredSkip 计数加一、错误入 errors,pending 保留下小时重试 @happy @HP-1 Scenario: HP-1 Given 仓库尚无成功分析 When resolvePushTriggerAction 执行 Then 返回 immediate,接入即见规格 @happy @HP-3 Scenario: HP-3 Given daily/push_count 仓库当天有未处理 push 且到达触发小时 When runAnalysisTriggerSweepOnce 执行 Then 以 before=上次成功 SHA、after=当前 HEAD 排队一次累计分析并清零 pending Feature: 分析输出语言设置 # 产品级与工作区级分析输出语言设置:语言枚举限定简体中文与 English,产品设置页可切换本产品语言并提示下一次分析全量重建,工作区设置页可改新仓库默认语言,接口按编辑权限校验。 @edge @EC-1 Scenario: EC-1 Given 只读成员(viewer)打开设置页或产品设置页 When 渲染输出语言面板 Then 面板返回空,不渲染任何语言选项 @edge @EC-2 Scenario: EC-2 Given 提交不受支持的语言值(如 fr 或 zh) When PATCH 产品或工作区默认语言接口 Then schema 校验失败返回 400,不写入数据库 @edge @EC-3 Scenario: EC-3 Given 注册时既未指定输出语言且工作区无默认值 When registerProduct 执行 Then 回退写入 zh-CN @edge @EC-6 Scenario: EC-6 Given viewer 调用工作区默认输出语言 PATCH When 请求 /api/settings/output-language-default Then 返回 403 只有工作区成员可以修改此设置,不更新 @error @ER-1 Scenario: ER-1 Given 非工作区编辑者调用工作区默认语言接口 When PATCH /api/settings/output-language-default Then 返回 403「只有工作区成员可以修改此设置」,不更新 @error @ER-2 Scenario: ER-2 Given 保存产品输出语言接口返回错误 When 产品设置页点击保存 Then 展示接口错误消息或「保存输出语言设置失败」 @error @ER-4 Scenario: ER-4 Given 保存工作区默认输出语言失败 When 设置页保存 Then 显示接口错误消息或「保存默认输出语言失败」 @happy @HP-1 Scenario: HP-1 Given 工作区编辑者打开设置页 When 渲染「新仓库默认输出语言」面板并选择 English 后保存 Then PATCH /api/settings/output-language-default 返回 updated,提示此后新接入产品继承此语言 @happy @HP-10 Scenario: HP-10 Given 编辑者打开产品设置页 When 渲染分析输出语言面板 Then 显示中文(简体)与 English 两个选项、当前语言选中,并提示切换后下一次分析全量重建、历史版本保持原语言 @happy @HP-11 Scenario: HP-11 Given 编辑者把产品输出语言从中文切换为 English When 点击保存 Then 调用 setOutputLanguage 提交 PATCH,成功后提示已保存并刷新页面 @happy @HP-12 Scenario: HP-12 Given 工作区编辑者打开设置页 When 渲染新仓库默认输出语言面板 Then 显示两个语言选项与当前默认值,说明此后新接入产品继承此设置、已有产品不受影响 @happy @HP-13 Scenario: HP-13 Given 工作区查看者读取默认输出语言 When GET /api/settings/output-language-default Then 返回 status=ok 与当前语言,缺省为 zh-CN @happy @HP-14 Scenario: HP-14 Given 注册新产品时未显式指定输出语言 When registerProduct 执行 Then 继承工作区 default_output_language 写入 products.output_language,均无则回退 zh-CN @happy @HP-15 Scenario: HP-15 Given 分析 run 已加载产品输出语言 When runAnalysisBody 调用 analyzeWithAI Then 提取上下文携带 outputLanguage,按产品语言生成规格与发布稿 @happy @HP-2 Scenario: HP-2 Given 产品编辑者打开产品设置页 When 在「分析输出语言」面板切换语言并保存 Then 调用 setOutputLanguage 提交 PATCH,提示下一次分析将全量重建为新语言版本、历史版本保持原语言 @happy @HP-3 Scenario: HP-3 Given 注册新产品且工作区默认输出语言为 en When 打开添加产品页 Then 表单「分析输出语言」默认选中 English,提交时随产品创建请求携带 outputLanguage Feature: 功能实现文件聚合 # 按实现文件聚合一组场景的证据:路径去重、行区间合并计数、场景编号归并,供规格页展示实现面。 @edge @EC-1 Scenario: EC-1 Given 证据条目缺少行号 When aggregateImplementationFiles 执行 Then 该条目只贡献路径与场景编号,行数计 0 @happy @HP-1 Scenario: HP-1 Given 多个场景引用同一实现文件的不同行区间 When aggregateImplementationFiles 执行 Then 按路径合并行区间并返回去重后的场景编号,结果按路径排序 Feature: 功能树与功能编辑 # 规格工作台左侧功能树支持筛选与新建功能,功能表单可编辑名称与说明,子功能按层级展示。 @error @ER-1 Scenario: ER-1 Given 保存功能接口失败 When 提交功能表单 Then 显示「保存功能失败」错误 @happy @HP-1 Scenario: HP-1 Given 规格工作台已加载功能树 When 在筛选框输入关键词 Then 只展示名称匹配的功能与子功能 @happy @HP-2 Scenario: HP-2 Given 需要新增功能 When 点击「+ 新功能」并提交功能表单 Then 保存功能并回调新功能 id Feature: 功能树与功能详情 # 规格工作台左侧功能树支持选择功能与新建顶级功能,右侧展示功能详情、场景列表与语义流程,未选择功能时给出空态与新建入口。 @edge @EC-1 Scenario: EC-1 Given 未选择任何功能 When 渲染 SpecDetail Then 显示「选择一个功能」空态与「+ 新功能」按钮 @error @ER-1 Scenario: ER-1 Given 删除功能接口失败 When 点击删除功能 Then 显示接口错误消息或「删除功能失败」 @happy @HP-1 Scenario: HP-1 Given 产品已有功能 When 在功能树选择功能 Then 右侧展示该功能详情、场景与实现面场景 Feature: 功能树与场景筛选 # 把功能列表按父子关系组装为树(父节点缺失时提升为根),默认选中根功能,并按关键词与类别筛选场景。 @edge @EC-1 Scenario: EC-1 Given 功能的父节点不在列表中 When buildFeatureTree 执行 Then 该功能被提升为根节点 @edge @EC-2 Scenario: EC-2 Given 未指定选中功能 When selectFeature 执行 Then 返回第一个根功能,无根时返回首个功能 @happy @HP-1 Scenario: HP-1 Given 功能存在父子关系 When buildFeatureTree 执行 Then 返回按名称排序的根节点与各自子节点 Feature: 功能流程图 # 按已验证的场景关系生成功能流程图,支持节点点击查看场景与代码证据,并列出不纳入主流程的场景。 @edge @EC-1 Scenario: EC-1 Given 自动布局计算失败 When FeatureFlow 渲染 Then 显示「自动布局暂时不可用」提示,场景清单仍可在场景标签查看 @edge @EC-2 Scenario: EC-2 Given 存在不纳入主流程的场景 When FeatureFlow 渲染 Then 折叠展示不纳入主流程的场景数量与原因 @happy @HP-1 Scenario: HP-1 Given 功能存在可布局的场景关系图 When FeatureFlow 渲染 Then 计算布局后渲染可缩放平移的流程图,点击节点展示关联场景与证据 Feature: 功能流程图布局与空态解释 # 用 ELK 分层布局把流程图排成自上而下的正交连线图并转为 React Flow 元素;无图时按护栏拒绝码输出面向用户的中文原因。 @edge @EC-1 Scenario: EC-1 Given 功能无流程图且无失败码 When describeFeatureFlowEmptyState 执行 Then 提示流程图尚未生成、将随下次分析自动生成 @edge @EC-2 Scenario: EC-2 Given 护栏拒绝码为 MAIN_CHAIN_MISSING When describeFeatureFlowEmptyState 执行 Then 解释场景间无先后主链、强行画图会制造假流程,并指回场景清单 @happy @HP-1 Scenario: HP-1 Given 已有通过校验的流程图 When layoutSemanticFeatureFlow 执行 Then 返回带坐标、尺寸与折线点的节点与边,画布宽高按内容计算 @happy @HP-2 Scenario: HP-2 Given 需要渲染流程图组件 When toReactFlowElements 执行 Then 节点按场景引用挂载场景数据,边带标签与箭头标记 Feature: 功能流程图生成与校验 # 把功能场景交给 LLM 生成可验证的用户执行图,经确定性护栏校验(节点/边/入口上限、场景引用、重试与终态证据),校验失败自动整图重生成一次;护栏拒绝码落库供空态解释。 @edge @EC-1 Scenario: EC-1 Given 首次输出未通过护栏校验 When generateFeatureFlowGraph 执行 Then 携带校验错误整图重新生成一次并再次校验 @edge @EC-2 Scenario: EC-2 Given 场景集未变且已有有效图,本次生成被护栏拒绝 When saveFeatureFlowError 执行 Then 保留旧图,仅记录失败码与时间 @edge @EC-3 Scenario: EC-3 Given 场景集已变化且生成被护栏拒绝 When loadFeatureFlowErrors 执行 Then 旧图作废,指纹一致时返回失败码供空态解释 @error @ER-1 Scenario: ER-1 Given 流程图未通过校验且重生成仍失败 When syncFeatureFlowsForVersion 执行 Then failed 计数加一,落库失败码,不中断其他功能生成 @happy @HP-1 Scenario: HP-1 Given 功能场景可形成主链 When generateFeatureFlowGraph 执行 Then 返回通过 validateFeatureFlowGraph 校验的流程图 @happy @HP-2 Scenario: HP-2 Given 新规格版本生成且上一版存在同场景集流程图 When syncFeatureFlowsForVersion 执行 Then 指纹一致的图被继承复制,其余按受影响键并发生成(最多 3 并发) Feature: 功能行为变迁史 # 按功能稳定键取跨发布的行为变化(排除仅措辞变化),按发布倒序分组,回答「这个行为最后一次变化是哪个版本、证据是什么」。 @edge @EC-1 Scenario: EC-1 Given 功能只有措辞变化记录 When loadFeatureHistory 执行 Then wording_only=1 的记录被过滤,不进入变迁史 @happy @HP-1 Scenario: HP-1 Given 功能在多个发布中有行为变化 When loadFeatureHistory 执行 Then 按发布倒序返回每个发布的变更条目,默认最多 15 条 Feature: 功能详情与场景工作台 # 功能详情页展示功能名、简介、来源、稳定链接、实现分布、行为变迁史与关联功能,并在场景与流程图两个视图间切换。 @edge @EC-1 Scenario: EC-1 Given 功能没有任何关联功能 When FeatureConnectionsPanel 加载 Then 不渲染关联功能面板 @error @ER-1 Scenario: ER-1 Given 关联功能接口失败 When FeatureConnectionsPanel 加载 Then 显示「功能连接加载失败」错误 @happy @HP-1 Scenario: HP-1 Given 打开某功能详情 When FeatureDetail 渲染 Then 展示功能头、实现分布、行为变迁史、关联功能与场景/流程图标签 @happy @HP-2 Scenario: HP-2 Given 功能存在共享证据文件的关联功能 When FeatureConnectionsPanel 加载 Then 展示关联功能数量与共享文件及两侧场景引用 Feature: 功能连接图与文件影响面反查 # 以共享证据文件构建功能间确定性连接(每对最多列 5 个共享文件),并支持按文件反查以该文件为证据的功能与场景,单功能场景截前 10 条且披露真实总数。 @edge @EC-1 Scenario: EC-1 Given 传入空文件清单 When lookupFilesImpact 或 buildFileImpact 执行 Then 直接返回空数组,不查询数据库 @edge @EC-2 Scenario: EC-2 Given 按路径过滤时 SQL LIKE 子串误中(lib/auth.tsx ← lib/auth.ts) When loadFeatureConnections 执行 Then 建图前按证据路径精确筛一次,误中行被剔除 @happy @HP-1 Scenario: HP-1 Given 产品已有功能、场景与共享证据文件 When loadFeatureConnections 执行 Then 返回按功能分组的连接,共享文件按共享场景数降序截前 5 @happy @HP-2 Scenario: HP-2 Given 改动前需要知道哪些功能以这些文件为证据 When lookupFilesImpact 执行 Then 按请求路径保序返回命中功能与场景,每功能场景截前 10 条并给出 scenarioCount Feature: 匿名演示态分析入口 # 匿名演示访客在产品页不渲染分析入口与只读提示,登录引导集中在演示外壳顶栏;登录只读成员仍看到禁用按钮与权限说明,服务端写校验是最终边界。 @edge @EC-1 Scenario: EC-1 Given 匿名演示访客查看演示外壳顶栏 When 渲染顶栏 Then 显示「使用 Chandler 登录」链接作为唯一登录引导 @edge @EC-2 Scenario: EC-2 Given 登录只读成员打开产品页 When 渲染分析入口组件 Then 显示禁用的「发起分析 · 需编辑权限」按钮与只读权限说明 @edge @EC-3 Scenario: EC-3 Given 匿名演示访客在演示页触发写操作 When 调用 demo guard Then 弹出 Demo 模式只读提示对话框,写操作被拦截 @error @ER-1 Scenario: ER-1 Given 分析请求返回错误 When 提交分析 Then 显示接口错误消息,不进入分析中状态 @error @ER-2 Scenario: ER-2 Given 分析请求被额度闸门拒绝 When 点击「立即分析」 Then 展示接口返回的额度提示消息 @happy @HP-1 Scenario: HP-1 Given 匿名演示访客打开演示产品页 When 渲染分析入口组件 Then 组件返回空,不输出任何按钮或提示 @happy @HP-2 Scenario: HP-2 Given 编辑者在产品页 When 点击「立即分析」 Then 提交分析请求并刷新页面 Feature: 发布卡与回归清单 # 发布卡展示发布日期、标题、摘要与提交链接,行为变化与变更说明可展开;回归清单按功能分组展示场景行为文本与命中理由,支持标记通过/未通过/跳过/待验证并附备注,可导出 Markdown;无行为变化时提示瘦身回归。 @edge @EC-1 Scenario: EC-1 Given 发布没有任何回归任务 When 渲染 BehaviorList Then 有规格版本时提示本次未发现规格行为变化并建议瘦身回归 @edge @EC-2 Scenario: EC-2 Given 发布存在仅措辞/编号调整的折叠差异 When 渲染 ReleaseCard Then 折叠区展示「另有 N 条差异判定为仅措辞/编号调整」,展开可见明细 @error @ER-1 Scenario: ER-1 Given 保存回归状态接口返回错误 When 选择回归状态 Then 显示接口错误消息或「保存验证状态失败」 @happy @HP-1 Scenario: HP-1 Given 发布存在行为变化与变更说明 When 展开 ReleaseCard 详情 Then 展示回归清单与「变更说明(AI)」列表,并附发布稿面板 @happy @HP-2 Scenario: HP-2 Given 回归任务需要标记状态 When 在回归清单选择状态 Then 提交状态到 regression-tasks 接口并刷新页面,跳过与未通过可填备注 @happy @HP-3 Scenario: HP-3 Given 需要导出发布回归清单 When 点击导出 Markdown Then 跳转 /api/products/{slug}/releases/{id}/regression 下载 Markdown Feature: 发布变化分页加载 # 发布变化页按固定页大小分页读取发布、变更与折叠变化,首屏与「加载更多」各取一页,失败时抛出接口错误。 @error @ER-1 Scenario: ER-1 Given 分页接口返回非 2xx When loadMoreReleases 执行 Then 抛出接口 error 或「加载更多发布失败」 @happy @HP-1 Scenario: HP-1 Given 发布变化页需要加载下一页 When loadMoreReleases 执行 Then 按 offset 与页大小请求接口并返回发布、变更、行为变化与折叠变化 Feature: 发布稿一级视图 # 发布稿一级视图(?view=notes)全宽展示最新一次发布的客户向说明,支持编辑公告标题与重点强调、按功能分组校对块文案、发布/下线、复制公开链接与导出 Markdown。 @edge @EC-1 Scenario: EC-1 Given 本次发布没有行为变化 When 渲染候选勾选列表 Then 显示「本次发布没有行为变化,无可宣布项。」 @error @ER-1 Scenario: ER-1 Given 加载发布稿失败 When 打开发布稿视图 Then 显示失败原因,未登录时提示登录后可在工作区生成并发布 @happy @HP-1 Scenario: HP-1 Given 最新发布尚无发布稿 When 打开发布稿视图并勾选变更生成 Then 生成草稿并按功能分组展示块文案与证据 @happy @HP-2 Scenario: HP-2 Given 草稿可编辑 When 修改公告标题或重点强调并失焦 Then 保存标题或强调语并刷新,提示已保存 @happy @HP-3 Scenario: HP-3 Given 发布稿已发布 When 点击复制公开链接或下线 Then 复制公开链接提示已复制,下线后公开链接不再展示 Feature: 发布稿文案 AI 改写 # 发布稿文案的 AI 通道:合成公告标题、按功能合并变化记录成文、改写导语与单块润色,只改写措辞不新增事实,机械剥除场景编号,单功能失败回退原文不阻塞整篇。 @edge @EC-1 Scenario: EC-1 Given 模型输出中残留 EC-n/ER-n/HP-n 场景编号 When parseEnhancedSummary 执行 Then stripScenarioRefs 确定性剥除编号与「场景」字样,锚定数据不变 @error @ER-1 Scenario: ER-1 Given 单个功能成文调用失败或结构不合格 When rewriteReleaseNoteCopy 执行 Then 该功能保留原始条目并计入 failed,不阻塞整篇文案 @happy @HP-1 Scenario: HP-1 Given 发布稿存在多个功能的变化记录 When rewriteReleaseNoteCopy 执行 Then 按功能各调用一次合并成文,再合成标题与导语并写回 release_notes @happy @HP-2 Scenario: HP-2 Given 需要手动润色单个发布稿块 When buildEnhancePrompt 执行 Then 生成只基于原文事实、20-60 字行为陈述式的改写 prompt Feature: 发布稿校对与发布 # 发布稿动线:勾选变更(仅预勾选带 Highlight 分类的项)生成草稿,按功能分组块级校对与 AI 润色,人工发布后生成公开链接,可下线与导出 Markdown。 @edge @EC-1 Scenario: EC-1 Given 本次发布没有行为变化 When 渲染候选勾选列表 Then 显示「本次发布没有行为变化,无可宣布项。」 @edge @EC-2 Scenario: EC-2 Given 发布稿已发布 When 渲染发布稿面板 Then 只读展示,需先下线才能修改或重新生成 @error @ER-1 Scenario: ER-1 Given 加载发布稿失败 When 打开发布稿面板 Then 显示失败原因,未登录时提示登录后可在工作区生成并发布 @happy @HP-1 Scenario: HP-1 Given 发布存在可宣布的行为变化且尚无发布稿 When 勾选变更并点击生成发布稿 Then 生成草稿并按功能分组展示块文案与证据路径 @happy @HP-2 Scenario: HP-2 Given 草稿需要人工校对 When 修改块文案失焦或点击 AI 润色 Then 失焦保存块级改写并标记已人工改写,AI 润色只替换文本框内容待失焦落库 @happy @HP-3 Scenario: HP-3 Given 草稿校对完成 When 点击发布 Then 状态变为已发布并展示公开页链接与复制链接按钮 Feature: 发布稿生成与发布 # 从发布的行为变化勾选生成发布稿草稿,块必须锚定真实 spec_change 且证据不越界,人工校对后可发布并生成公开分享链接,支持 Markdown 导出。 @edge @EC-1 Scenario: EC-1 Given 草稿块携带的证据路径不在锚定变化的证据集内 When validateDraft 执行 Then 返回 ok=false 与越界路径错误,草稿不允许落库 @error @ER-1 Scenario: ER-1 Given 草稿为空或存在锚定不存在的 spec_change When canPublish 执行 Then 返回 empty draft 或 anchor missing 错误,禁止发布 @happy @HP-1 Scenario: HP-1 Given 发布存在行为变化且已勾选要宣布的条目 When buildDraftBlocks 执行 Then 按 Highlight 固定顺序生成带 position 与锚定 specChangeId 的草稿块 @happy @HP-2 Scenario: HP-2 Given 草稿块均锚定本次发布的行为变化 When canPublish 执行 Then 返回 null 允许人工发布,发布后生成 share_token 公开链接 @happy @HP-3 Scenario: HP-3 Given 需要复制发布稿为 Markdown When renderReleaseNoteMarkdown 执行 Then 输出含产品、提交、公开页与逐块摘要及证据路径的 Markdown Feature: 变体场景合并提案脚本 # 一次性运维脚本:对指定仓库最新规格版本内的措辞变体场景生成合并提案,默认 dry-run,--apply 才写入;复用运行时同一变体判定,跳过人工维护与已核实条目。 @edge @EC-1 Scenario: EC-1 Given 功能或场景来源为 manual,或场景已被人工核实 When 脚本生成提案 Then 跳过该功能/场景,不产出合并提案 @error @ER-1 Scenario: ER-1 Given 未提供 --repository 参数 When 执行脚本 Then 打印用法并以退出码 1 结束 @error @ER-2 Scenario: ER-2 Given 指定仓库不存在或没有规格版本 When 执行脚本 Then 抛出错误并结束 @happy @HP-1 Scenario: HP-1 Given 指定仓库存在措辞变体场景 When 执行 merge-variant-scenarios.ts --repository Then 输出合并提案(保留哪条、丢弃哪条),不写库 @happy @HP-2 Scenario: HP-2 Given 人工审阅 dry-run 报告后确认合并 When 追加 --apply 执行 Then 按提案合并变体场景 Feature: 变体场景存量清理脚本 # 对指定仓库最新规格版本内的措辞变体场景生成合并提案,默认 dry-run 只打印,--apply 才删除被并入的重复场景;跳过人工功能/场景与已核实场景。 @edge @EC-1 Scenario: EC-1 Given 场景来源为 manual 或已人工核实 When 生成合并提案 Then 该场景列入跳过项,不参与合并 @error @ER-1 Scenario: ER-1 Given 未提供 --repository 参数 When 执行脚本 Then 打印用法并以退出码 1 结束 @error @ER-2 Scenario: ER-2 Given 指定仓库不存在或没有规格版本 When 执行脚本 Then 抛出「仓库不存在」或「该仓库没有规格版本」并以退出码 1 结束 @happy @HP-1 Scenario: HP-1 Given 仓库最新规格版本存在措辞变体场景 When 执行 merge-variant-scenarios.ts --repository Then 按功能打印删除→并入的提案与跳过项,dry-run 不改数据 @happy @HP-2 Scenario: HP-2 Given 人工审阅 dry-run 报告后确认执行 When 追加 --apply 执行 Then 删除被并入的变体场景行,历史 changes 的 before/after JSON 不动 Feature: 变体场景清理提案脚本 # 对指定仓库最新规格版本内的措辞变体场景做合并提案,默认 dry-run,复用运行时同一变体判定,跳过 manual 与人工核实过的场景。 @edge @EC-1 Scenario: EC-1 Given 功能来源为 manual 或场景已被人工核实 When 脚本生成提案 Then 跳过该功能或场景,不纳入合并提案 @edge @EC-2 Scenario: EC-2 Given 功能或场景来源为 manual,或场景已被人工核实 When 脚本生成提案 Then 跳过不碰 @error @ER-1 Scenario: ER-1 Given 未提供 --repository 参数 When 执行脚本 Then 打印用法并以退出码 1 结束 @error @ER-2 Scenario: ER-2 Given 指定仓库不存在或没有规格版本 When 执行脚本 Then 抛出仓库不存在或该仓库没有规格版本错误 @error @ER-3 Scenario: ER-3 Given 仓库不存在或该仓库没有规格版本 When 执行脚本 Then 抛出错误终止 @error @ER-4 Scenario: ER-4 Given 指定仓库不存在或该仓库没有规格版本 When 执行脚本 Then 抛出错误并结束 @happy @HP-1 Scenario: HP-1 Given 指定仓库存在最新规格版本且含措辞变体场景 When 执行 merge-variant-scenarios.ts --repository Then 输出合并提案(保留哪条、丢弃哪条),默认不写库 @happy @HP-2 Scenario: HP-2 Given 指定仓库存在措辞变体场景 When 执行 merge-variant-scenarios.ts --repository Then 输出合并提案报告,不写库 @happy @HP-3 Scenario: HP-3 Given 人工审阅 dry-run 报告后决定执行 When 追加 --apply 执行 Then 按提案合并变体场景 @happy @HP-4 Scenario: HP-4 Given 已人工审阅 dry-run 报告 When 执行 merge-variant-scenarios.ts --repository --apply Then 按提案执行合并 Feature: 变体场景清理脚本 # 对指定仓库最新规格版本内的措辞变体场景产出合并提案,默认 dry-run,--apply 才写库,跳过 manual 功能与已人工核实的场景。 @edge @EC-1 Scenario: EC-1 Given 功能来源为 manual 或场景已被人工核实 When 脚本生成提案 Then 跳过该功能或场景,不产出合并提案 @error @ER-1 Scenario: ER-1 Given 未提供 --repository 参数 When 执行脚本 Then 打印用法并以退出码 1 结束 @error @ER-2 Scenario: ER-2 Given 指定仓库不存在或该仓库没有规格版本 When 执行脚本 Then 抛出错误并终止 @happy @HP-1 Scenario: HP-1 Given 维护者对指定仓库运行清理脚本且未加 --apply When 执行 merge-variant-scenarios.ts --repository Then 输出变体合并提案报告,不写库 @happy @HP-2 Scenario: HP-2 Given 维护者审阅 dry-run 报告后决定执行 When 执行 merge-variant-scenarios.ts --repository --apply Then 按提案合并变体场景并写库 Feature: 只读演示下的分析入口 # 只读演示态下分析入口不渲染,编辑面板与保存动作由 demo guard 拦截,服务端校验兜底。 @edge @EC-1 Scenario: EC-1 Given 登录只读成员查看分析入口 When 渲染组件 Then 显示「当前账号对该产品为只读权限。」说明,不渲染主按钮 @edge @EC-2 Scenario: EC-2 Given 匿名 demo 访客尝试打开场景编辑面板 When 点击编辑或新建场景 Then demo guard 拦截,不打开编辑面板 @edge @EC-5 Scenario: EC-5 Given 演示产品按组织与 slug 精确解析 When 打开演示详情页 Then 按 (org, slug) 精确解析产品,不使用全局 slug 兜底 @error @ER-1 Scenario: ER-1 Given 匿名 demo 访客绕过前端提交写请求 When 服务端校验写权限 Then 服务端拒绝,写能力以服务端校验为准 @error @ER-2 Scenario: ER-2 Given 匿名访客访问非 demo 产品地址 When 解析公开演示产品 Then 返回 null,页面按 404 处理 @happy @HP-1 Scenario: HP-1 Given 登录只读成员打开产品页 When 渲染分析入口 Then 显示禁用的次要按钮「发起分析 · 需编辑权限」 @happy @HP-2 Scenario: HP-2 Given 匿名 demo 访客浏览产品规格 When 查看场景工作区 Then 可浏览规格内容,不出现分析入口 Feature: 可复制代码块组件 # 客户端组件 CopyBlock:渲染标题行、复制按钮与 pre/code 文本块,点击后把文本写入剪贴板并切换按钮文案为「已复制」。 @edge @EC-1 Scenario: EC-1 Given 用户尚未点击复制 When 查看代码块 Then 按钮显示「复制」,文本以 pre/code 完整展示 @error @ER-1 Scenario: ER-1 Given 浏览器不支持或拒绝剪贴板写入 When 点击「复制」按钮 Then 复制失败,按钮文案不切换为「已复制」 @happy @HP-1 Scenario: HP-1 Given 指南页展示可复制的配置或指令文本 When 点击「复制」按钮 Then 文本写入剪贴板,按钮文案变为「已复制」 Feature: 启动副作用防护与恢复 # 生产启动时恢复遗留 running 分析并启动账务 sweep;development 环境默认跳过这些副作用,需显式放行。 @edge @EC-1 Scenario: EC-1 Given development 环境未设置 ALLOW_PROD_SIDE_EFFECTS When register 执行 Then 跳过启动恢复与账务 sweep 并打印跳过日志 @happy @HP-1 Scenario: HP-1 Given 生产环境启动 When register 执行 Then 恢复中断分析并启动账务 sweep Feature: 场景导出 # 把功能与场景导出为 Gherkin 骨架(HP/EC/ER 编号作 tag)或 Markdown 复选框用例清单,按功能树层级组织。 @happy @HP-1 Scenario: HP-1 Given 需要把规格场景放进 Cucumber/Playwright 项目 When toGherkin 执行 Then 按功能树输出 Feature/Rule 与带分类 tag 的 Given/When/Then 场景块 @happy @HP-2 Scenario: HP-2 Given 需要把场景贴进测试计划或工单 When toMarkdownChecklist 执行 Then 按功能与子功能层级输出带复选框、分类标签与情境/操作/结果的清单 Feature: 增量分析模式判定 # 纯函数判定本次分析走增量还是全量:无上一版规格、受影响集为空、受影响占比超 60% 或 diff 文件超 200 时保守回退全量。 @edge @EC-1 Scenario: EC-1 Given 受影响集为空 When resolveAnalysisMode 执行 Then 回退 full,避免漏掉索引未覆盖的新功能 @edge @EC-2 Scenario: EC-2 Given 受影响功能占比超过 0.6 或 diff 文件超过 200 When resolveAnalysisMode 执行 Then 回退 full @happy @HP-1 Scenario: HP-1 Given 存在上一版规格且受影响功能占比未超阈值 When resolveAnalysisMode 执行 Then 返回 incremental Feature: 增量影子对比 # 对比增量分析与全量分析的功能 key 集合与 (reference, category) 集合,按功能 key 零容忍与宏平均 Jaccard ≥ 0.70 判定一致性。 @edge @EC-1 Scenario: EC-1 Given 增量分析缺少全量分析中的某个功能 When compareIncrementalShadow 执行 Then 该功能 key 进入 missingKeys,该功能 Jaccard 记 0 并列入 inconsistentFeatures,consistent 为 false @edge @EC-2 Scenario: EC-2 Given 两侧功能场景集合的 Jaccard 低于 0.70 门槛 When compareIncrementalShadow 执行 Then referenceConsistency 低于 SHADOW_REFERENCE_GATE,consistent 为 false,该功能列入 inconsistentFeatures @happy @HP-1 Scenario: HP-1 Given 增量与全量分析的功能 key 集合完全一致且各功能场景编号与类别集合相同 When compareIncrementalShadow 执行 Then consistent 为 true,missingKeys 与 extraKeys 为空,referenceConsistency 为 1 Feature: 多趟提取确定性合并 # 把多个分区窗口的提取结果按功能稳定键分组、场景行为等价与变体近似去重、引用冲突重编号、changes 去重并截断到 150 条,产出完整规格。 @edge @EC-1 Scenario: EC-1 Given 两场景骨架词共享但行为不同(如跳转 dashboard 与锁定账户) When scenarioVariantSimilar 判定 Then 返回 false,两条场景都保留 @edge @EC-2 Scenario: EC-2 Given 合并后 changes 超过 150 条 When mergeChunkResults 执行 Then 截断到前 150 条并打日志,onStats 回报 changesTruncated @edge @EC-3 Scenario: EC-3 Given 同一功能内两条场景行为确实不同 When mergeChunkResults 执行 Then 两条场景都保留,不误合 @edge @EC-4 Scenario: EC-4 Given 两条场景骨架词共享但行为不同(如「跳转 dashboard」与「锁定账户」) When mergeChunkResults 执行 Then 不折叠,两条场景均保留 @edge @EC-5 Scenario: EC-5 Given 两条场景骨架词共享但行为不同(如跳转 dashboard 与锁定账户) When mergeChunkResults 执行 Then 变体判定拒绝合并,两条均保留 @edge @EC-6 Scenario: EC-6 Given 两条场景骨架词共享但行为不同(如「跳转 dashboard」与「锁定账户」) When mergeChunkResults 执行 Then 变体判定拒绝折叠,两条场景都保留 @edge @EC-7 Scenario: EC-7 Given 两条场景情境/操作/结果同义且 category 相同 When mergeChunkResults 执行 Then scenarioBehaviorEqual 命中直接去重,不进入变体判定 @happy @HP-1 Scenario: HP-1 Given 多个分区返回同名功能 When mergeChunkResults 执行 Then 按功能稳定键合并,摘要取场景数最多的趟,场景按引用冲突规则重编号 @happy @HP-10 Scenario: HP-10 Given 同一分区输出内同一行为以不同措辞重复建条 When mergeChunkResults 执行 Then scenarioVariantSimilar 命中,后一条被去重,scenariosDeduped 计数加一 @happy @HP-2 Scenario: HP-2 Given 单分区输出中同一行为从不同角色重复建条(措辞变体) When mergeChunkResults 执行 Then 变体近似判定命中后折叠,scenariosDeduped 计数加一,不落库并存 @happy @HP-3 Scenario: HP-3 Given 多个分区各自提取出同一功能 When mergeChunkResults 执行 Then 按功能 key 归并为一条功能,场景合并保留 @happy @HP-4 Scenario: HP-4 Given 单分区输出内同一行为以不同措辞重复建条 When mergeChunkResults 执行 Then 变体场景折叠为一条,保留先到者,changes 不受影响 @happy @HP-5 Scenario: HP-5 Given 实现面被切分为多个提取分区 When mergeChunkResults 合并各趟 AnalysisResult Then 同名功能归并为一条,场景按顺序合并为完整规格 @happy @HP-6 Scenario: HP-6 Given 单分区输出内同一行为以不同角色或措辞重复建条 When mergeChunkResults 执行 Then scenarioVariantSimilar 判定为变体后折叠,scenariosDeduped 计数加一 @happy @HP-7 Scenario: HP-7 Given 多个分区窗口输出同一功能 When mergeChunkResults 执行 Then 按功能稳定键归并场景并统计去重数量 @happy @HP-8 Scenario: HP-8 Given 多个分区窗口各自产出功能与场景 When mergeChunkResults 执行 Then 按功能稳定键归并场景,返回合并后的规格与去重/截断统计 @happy @HP-9 Scenario: HP-9 Given 实现面超出单窗口被分成多个分区 When mergeChunkResults 执行 Then 各分区功能按 key 归并,场景合并为一份完整规格并回报合并统计 Feature: 套餐购买面板 # 定价页展示套餐与超额包,下单后展示微信扫码或 H5 支付参数并轮询订单至终态。 @edge @EC-1 Scenario: EC-1 Given 套餐列表为空 When 渲染定价面板 Then 显示「定价校准中」暂不开放购买 @error @ER-1 Scenario: ER-1 Given 下单接口返回未登录或支付不可用 When 点击订阅或购买 Then 显示「请先登录后再购买」或当前无法购买提示 @happy @HP-1 Scenario: HP-1 Given 定价页已加载套餐 When 点击订阅 Then 创建订单并展示支付二维码或 H5 收银台链接,轮询至支付成功 Feature: 子进程命令执行 # 以 execFile 且 shell=false 执行外部命令,失败时抛出携带 stdout 与 stderr 的错误。 @error @ER-1 Scenario: ER-1 Given 外部命令以非零码退出 When execCommand 执行 Then 抛出错误并在错误对象上附带 stdout 与 stderr @happy @HP-1 Scenario: HP-1 Given 外部命令正常退出 When execCommand 执行 Then 返回 stdout 与 stderr 字符串 Feature: 导出限流 # 导出接口按登录用户或匿名 IP 做 MySQL 固定窗口限流,键为 HMAC 摘要不落原始身份,行锁串行化防超发,超限返回 429 与 Retry-After,限流器故障时放行并告警。 @edge @EC-1 Scenario: EC-1 Given 同一身份在本窗口内已达上限 When consumeRateLimit 执行 Then 返回 allowed=false 与窗口剩余秒数,路由返回 429 与 Retry-After @edge @EC-2 Scenario: EC-2 Given 请求经反向代理携带 X-Forwarded-For When clientIpFrom 执行 Then 按 TRUSTED_PROXY_COUNT 从右数取可信跳的 IP,不无条件信任客户端头 @error @ER-1 Scenario: ER-1 Given 未配置 RATE_LIMIT_KEY_SECRET 或限流数据库操作失败 When consumeRateLimit 执行 Then 记录告警并放行(fail-open),不阻断导出 @happy @HP-1 Scenario: HP-1 Given 登录用户在本窗口内未超导出次数 When enforceExportRateLimit 执行 Then 消费一次配额并放行,返回 null 让导出继续 Feature: 工作区默认触发模式设置 # 设置页配置新接入仓库默认继承的分析触发模式,已有仓库不受影响。 @edge @EC-1 Scenario: EC-1 Given 选择每 N 次 push 或每天低峰模式 When 渲染设置表单 Then 显示每 N 次推送与低峰分析时间下拉 @error @ER-1 Scenario: ER-1 Given 保存默认触发设置接口失败 When 点击保存 Then 显示接口错误消息或「保存默认触发设置失败」 @happy @HP-1 Scenario: HP-1 Given 编辑者打开设置页 When 选择默认触发模式并保存 Then 提示默认触发设置已保存,此后新接入仓库继承该模式 Feature: 工作区默认输出语言设置 # 设置页提供工作区默认输出语言(中文/English)读写接口与面板:editor 可修改、viewer 可读取,非法语言返回 400,无权限返回 403;新接入产品注册时继承该默认值,已有产品不受影响。 @edge @EC-1 Scenario: EC-1 Given 提交的语言不在 zh-CN/en 枚举内 When PATCH /api/settings/output-language-default 执行 Then 返回 400 与「输出语言无效」,不更新 @error @ER-1 Scenario: ER-1 Given 调用者不是工作区 editor When PATCH /api/settings/output-language-default 执行 Then 返回 403「只有工作区成员可以修改此设置」,不更新 @happy @HP-1 Scenario: HP-1 Given 工作区 editor 在设置页选择默认输出语言 When PATCH /api/settings/output-language-default 提交 language=en Then 更新 organizations.default_output_language 并返回 status=updated 与 language @happy @HP-2 Scenario: HP-2 Given 工作区 viewer 打开设置页 When GET /api/settings/output-language-default 执行 Then 返回 status=ok 与当前默认语言 Feature: 平台管理员判定 # 按 CODELPHI_ADMIN_EMAILS 白名单判定平台管理员,未配置时回退为 default 工作区 owner。 @edge @EC-1 Scenario: EC-1 Given 未配置管理员邮箱白名单 When isPlatformAdmin 执行 Then 回退判定 default 工作区 owner 为管理员 @error @ER-1 Scenario: ER-1 Given 非管理员调用管理接口 When requirePlatformAdmin 执行 Then 抛出 forbidden @happy @HP-1 Scenario: HP-1 Given 配置了管理员邮箱白名单且用户邮箱在名单内 When isPlatformAdmin 执行 Then 返回 true Feature: 我的工作区页 # 展示用户创建或加入的工作区列表,所有者可邀请成员加入整个工作区,并可切换当前工作区;页面文案按界面语言渲染。 @edge @EC-1 Scenario: EC-1 Given 界面语言为 en 的成员打开我的工作区页 When 渲染页面 Then 标题、邀请表单、表头与角色标签取英文字典条目 @edge @EC-2 Scenario: EC-2 Given 当前工作区不是列表中的某个工作区 When 查看该行操作列 Then 显示「切换」按钮,提交后切换当前工作区 @edge @EC-3 Scenario: EC-3 Given 当前工作区对应行 When 查看操作列 Then 显示「当前工作区」徽标,不提供切换按钮 @happy @HP-1 Scenario: HP-1 Given 工作区成员打开我的工作区页 When 渲染页面 Then 显示工作区表格(名称、slug、角色、当前工作区徽章或切换按钮) @happy @HP-2 Scenario: HP-2 Given 工作区 owner 打开我的工作区页 When 渲染邀请表单 Then 显示邮箱与角色输入及邀请按钮,提交后展示成员已加入或邀请链接与邮件发送状态 @happy @HP-3 Scenario: HP-3 Given 工作区所有者打开我的工作区页 When 访问 /organizations Then 渲染邀请成员表单(邮箱、角色、邀请按钮)与工作区列表 @happy @HP-4 Scenario: HP-4 Given 所有者提交邀请且成员已加入 When 提交邀请表单 Then 显示「成员已加入。」提示 @happy @HP-5 Scenario: HP-5 Given 所有者提交邀请且对方尚未注册 When 提交邀请表单 Then 显示邀请已创建提示与邀请链接,按是否发送注册邮件给出对应说明 Feature: 我的工作区页面 # 我的工作区页展示当前工作区、所有者邀请成员表单与用户所属工作区列表,支持切换工作区并显示邀请结果。 @edge @EC-1 Scenario: EC-1 Given 当前用户不是工作区所有者 When 渲染我的工作区页 Then 不渲染邀请成员表单,仅显示工作区列表 @edge @EC-2 Scenario: EC-2 Given 列表中的工作区即当前工作区 When 查看成员表格操作列 Then 显示「当前工作区」徽标而非切换按钮 @happy @HP-1 Scenario: HP-1 Given 工作区所有者打开我的工作区页 When 渲染页面 Then 显示邀请成员表单(邮箱、角色、邀请按钮)与工作区列表表格 @happy @HP-2 Scenario: HP-2 Given 所有者提交邀请后返回页面 When 查看邀请结果 Then 显示「成员已加入」或「邀请已创建」提示,并给出邀请链接与邮件发送状态 @happy @HP-3 Scenario: HP-3 Given 用户属于多个工作区 When 查看工作区列表 Then 当前工作区显示「当前工作区」徽标,其他工作区显示「切换」按钮 @happy @HP-4 Scenario: HP-4 Given 工作区所有者打开我的工作区页 When 填写邮箱与角色后提交邀请表单 Then 显示成员已加入或邀请已创建提示,并给出邀请链接与邮件发送状态 @happy @HP-5 Scenario: HP-5 Given 用户打开我的工作区页 When 查看工作区表格 Then 列出所属工作区、角色(所有者/编辑者/查看者)与操作列,当前工作区显示徽标,其他工作区提供切换按钮 @happy @HP-6 Scenario: HP-6 Given 界面语言为 en 的用户打开我的工作区页 When 渲染 /organizations Then 标题、邀请表单、表头、角色标签与切换按钮取 ORGANIZATIONS 字典英文条目 Feature: 我的工作区页面双语 # 我的工作区页按界面语言渲染返回链接、标题、邀请成员表单、邀请结果提示、成员表格表头与角色标签、当前工作区徽标与切换按钮。 @happy @HP-1 Scenario: HP-1 Given 界面语言为 en 的工作区所有者打开我的工作区 When 访问 /organizations Then 标题、邀请成员表单、邮箱与角色标签、邀请按钮显示英文 @happy @HP-2 Scenario: HP-2 Given 界面语言为 en 且邀请已创建 When 查看邀请结果区块 Then 成员已加入或邀请已创建提示、打开邀请链接与邮件发送状态文案显示英文 @happy @HP-3 Scenario: HP-3 Given 界面语言为 en 的工作区成员查看工作区列表 When 渲染成员表格 Then 表头、所有者/编辑者/查看者角色标签、当前工作区徽标与切换按钮显示英文 Feature: 手动分析定位 # 按产品 slug(可选工作区)定位仓库,经代码托管适配器读取仓库元数据与默认分支头,返回 repositoryId 与 afterSha。 @edge @EC-1 Scenario: EC-1 Given 产品 slug 不存在或不属于指定工作区 When resolveManualAnalysis 执行 Then 返回 null @happy @HP-1 Scenario: HP-1 Given 产品 slug 存在且仓库可读 When resolveManualAnalysis 执行 Then 返回 repositoryId 与默认分支最新 afterSha Feature: 支付订单与退款 # 建单四段边界(本地 pending 复用 30 分钟内同参数单 → 远端建单 → 回写 → prepay 回写),查单超 20 秒未收 webhook 时向 Chandler 对账;退款按剩余天数/SAU 折算,明确 4xx 记 refund_failed,超时停 refund_submitted 由 sweep/webhook 推进确认并回收钱包。 @edge @EC-1 Scenario: EC-1 Given 30 分钟内存在同账户同参数 pending 单 When createPaymentOrder 执行 Then 复用原订单号并更新渠道,不新建订单 @edge @EC-2 Scenario: EC-2 Given 退款确认时钱包仍有预留未结清 When reclaimWalletIfSettled 执行 Then 保持 reclaiming 状态返回 false,等下一轮重试 @error @ER-1 Scenario: ER-1 Given 订单状态不是 paid/fulfilled 或已退款 When requestRefund 执行 Then 抛出 billing_state_conflict 表示不可退款 @error @ER-2 Scenario: ER-2 Given 退款金额非正整数或超过订单金额 When requestRefund 执行 Then 抛出 billing_state_conflict 拒绝退款 @happy @HP-1 Scenario: HP-1 Given 用户选择订阅或超额包并发起支付 When createPaymentOrder 执行 Then 写入本地 pending 单、以本地商户单号为幂等键远端建单并回写平台单号与 prepay 参数 @happy @HP-2 Scenario: HP-2 Given 订单已支付且平台回调 paid When markPaidAndFulfill 执行 Then 订单置为 paid 并调用履约函数发放权益 @happy @HP-3 Scenario: HP-3 Given 管理员对已支付订单发起退款 When requestRefund 执行 Then 订单置 refund_pending 后提交 Chandler,成功或超时置 refund_submitted,明确 4xx 置 refund_failed Feature: 数据库连接配置 # 从 DATABASE_URL 解析 MySQL 连接池配置(默认 3306、utf8mb4、UTC、连接上限 10),默认启用 TLSv1.2 且校验证书,可用 DATABASE_SSL=false 关闭。 @edge @EC-1 Scenario: EC-1 Given DATABASE_SSL 设为 false When databaseConfig 执行 Then ssl 为 undefined,不启用 TLS @error @ER-1 Scenario: ER-1 Given DATABASE_URL 协议不是 mysql: When databaseUrl 执行 Then 抛出 DATABASE_URL must be a mysql:// URL @happy @HP-1 Scenario: HP-1 Given DATABASE_URL 为合法 mysql:// 地址 When databaseConfig 执行 Then 返回含主机、端口、库名与 TLS 选项的连接池配置 Feature: 架构产物访问 # 仅对 accepted 状态的架构文档返回 HTML,附带沙箱 CSP 与 no-store 头;未验收返回 409,文件缺失返回 404。 @edge @EC-1 Scenario: EC-1 Given 架构文档状态不是 accepted When architectureArtifactResponse 执行 Then 返回 409 架构产物尚未通过验收 @error @ER-1 Scenario: ER-1 Given 产物文件不存在 When architectureArtifactResponse 执行 Then 返回 404 架构产物不存在 @happy @HP-1 Scenario: HP-1 Given 架构文档已通过验收 When architectureArtifactResponse 执行 Then 返回 HTML 与沙箱安全响应头 Feature: 架构文档生成与版本记录 # 按仓库证据构建确定性架构文档(Radar/AIVlog 专用模板或通用规则),调用 Archify 交付与视觉验收,成功记 accepted、失败按阶段记 rendered/failed 并保存回执与错误;产物路径限定在受管目录内。 @edge @EC-1 Scenario: EC-1 Given 仓库分析覆盖率不可发布 When prepareArchitecture 执行 Then 不生成架构文档,仅写分析回执并以 standard 质量返回 @edge @EC-2 Scenario: EC-2 Given 通用仓库识别到的组件少于 2 个或连接为空 When buildArchitectureDocument 执行 Then 抛出 No architecture components detected 或 no component relationships detected @error @ER-1 Scenario: ER-1 Given 交付或视觉验收阶段失败 When generateArchitecture 执行 Then 按阶段记录 rendered/failed 状态、回执与错误消息后返回 @error @ER-2 Scenario: ER-2 Given 产物路径越出受管 artifacts/products 目录 When safeArtifactAbsolutePath 执行 Then 抛出 Artifact path is outside managed artifact directory @happy @HP-1 Scenario: HP-1 Given 仓库源码可读且证据完整 When generateArchitectureVersion 执行 Then 写 spec、交付 HTML、视觉验收通过后以 accepted 落库 @happy @HP-2 Scenario: HP-2 Given 通用仓库存在至少两个可识别组件与连接 When buildArchitectureDocument 执行 Then 返回含组件、连接与证据行号的架构文档 Feature: 演示模式分析入口 # 演示外壳以 DemoModeProvider 包裹页面,写操作经 demo guard 拦截并弹出只读提示对话框;匿名演示访客不再看到分析或发布入口,登录引导集中在顶栏。 @edge @EC-1 Scenario: EC-1 Given 匿名演示访客查看产品页头部 When 渲染分析入口 Then 不渲染分析按钮与只读提示 @edge @EC-10 Scenario: EC-10 Given 登录页需要展示公开演示入口 When 加载公开演示产品 Then 优先取数据库标记的 demo 产品,无则取环境变量白名单首个,都没有则不渲染入口 @edge @EC-2 Scenario: EC-2 Given 匿名演示访客查看总览页 When 渲染发布入口 Then 不渲染发布按钮与登录链接 @edge @EC-6 Scenario: EC-6 Given 匿名演示访客查看发布入口 When 渲染发布入口组件 Then 组件输出为空字符串,不渲染发布链接 @edge @EC-7 Scenario: EC-7 Given 以 writeAccess=readonly 渲染 AnalyzeButton When 渲染组件 Then 渲染禁用按钮并写明需编辑权限 @error @ER-1 Scenario: ER-1 Given 绕过前端直接向写接口发请求 When 服务端处理写请求 Then 由服务端写权限校验拒绝,前端隐藏不改变强制边界 @happy @HP-1 Scenario: HP-1 Given 匿名演示访客打开演示产品页 When 渲染页面 Then 以演示外壳渲染,侧栏标注 DEMO MODE 只读演示 @happy @HP-2 Scenario: HP-2 Given 演示访客触发任一写操作 When 调用 useDemoGuard 返回的守卫 Then 弹出「Demo 模式」对话框说明编辑不会保存 @happy @HP-3 Scenario: HP-3 Given 匿名 demo 访客打开产品页 When 渲染页面头部 Then 不出现分析、发布等写入口,顶栏提供登录引导 Feature: 环境变量与功能开关 # 集中读取必需环境变量与可选开关:应用地址、Chandler 地址、邮箱白名单、公开演示 slug、PR 预演开关、增量影子、AI 上下文预算、分区上限与行为枚举参数,均带默认值。 @edge @EC-1 Scenario: EC-1 Given 邮箱白名单配置含空格与大写 When allowedEmails 执行 Then 去空格转小写后返回集合 @error @ER-1 Scenario: ER-1 Given 必需环境变量缺失或为空 When requiredEnv 执行 Then 抛出「变量名 is not configured」 @happy @HP-1 Scenario: HP-1 Given 未设置可选开关 When prPreviewEnabled 或 analysisMaxPartitions 执行 Then PR 预演默认开启,分区上限默认 6 Feature: 界面语言切换 # 界面语言由 codelphi_locale cookie 驱动:服务端组件读取 locale 渲染导航、外壳文案、html lang 与 metadata,顶栏语言切换 pill 提交同源重定向请求写入一年期 cookie 并刷新当前页;未设置或非法值回退 zh-CN,与分析输出语言相互独立。 @edge @EC-1 Scenario: EC-1 Given 请求未携带 codelphi_locale cookie 或 cookie 值非法 When resolveUiLocale 执行 Then 回退为 zh-CN @edge @EC-2 Scenario: EC-2 Given 切换请求的 locale 不在 zh-CN/en 内,或 redirectTo 为绝对地址或 // 开头 When POST /api/ui-locale Then 返回 400,不写 cookie 不重定向 @edge @EC-3 Scenario: EC-3 Given 点击当前已选中的语言 When LanguageSwitcher 的 switchTo 执行 Then 直接返回,不发请求不刷新 @error @ER-1 Scenario: ER-1 Given 切换请求进行中 When 再次点击任一语言按钮 Then 按钮 disabled 且 pending 期间不重复提交 @happy @HP-1 Scenario: HP-1 Given 用户在顶栏点击另一种界面语言 When LanguageSwitcher 读取当前路径并 POST /api/ui-locale Then 服务端写入 codelphi_locale cookie 并 303 重定向回原页,随后 router.refresh() 刷新 @happy @HP-2 Scenario: HP-2 Given 界面语言为 en 时渲染外壳 When AppShell/PublicShell/DemoShell/Brand 渲染 Then 导航、侧栏说明、演示徽标、登录/退出与品牌标语输出英文文案,切换器标记 English 为当前项 @happy @HP-3 Scenario: HP-3 Given 界面语言为 en 时加载页面 When 根布局与页面渲染 Then html lang 为 en,metadata 描述输出英文,日期按 en 格式化 Feature: 界面语言切换与双语外壳 # 界面语言(与分析输出语言独立)由 cookie 驱动服务端渲染:顶栏语言 pill 提交 POST /api/ui-locale 写一年期 SameSite=Lax cookie 并同源重定向,三种外壳与根布局按 locale 输出双语导航、品牌与 metadata,日期按 locale 格式化。 @edge @EC-1 Scenario: EC-1 Given 请求体 locale 为 fr 或 redirectTo 为 https://evil.example、//evil.example、null When POST /api/ui-locale Then 返回 400 与校验错误消息,不写 cookie、不重定向 @edge @EC-2 Scenario: EC-2 Given cookie 缺失或值为未知语言(如 fr) When resolveUiLocaleValue 执行 Then 回退为 zh-CN,页面按中文渲染 @edge @EC-3 Scenario: EC-3 Given 用户点击当前已选中的语言按钮或切换请求尚未完成 When 点击语言切换器按钮 Then 不发起请求(next === locale 或 pending 时直接返回),按钮在 pending 期间禁用 @error @ER-1 Scenario: ER-1 Given 提交的 locale 不在 zh-CN/en 内,或 redirectTo 为站外地址、协议相对地址、非 / 开头 When POST /api/ui-locale Then 返回 400 与校验错误消息,不写 cookie 不重定向 @error @ER-2 Scenario: ER-2 Given 切换请求网络失败 When LanguageSwitcher 的 switchTo 执行 Then finally 中恢复 pending 状态,按钮重新可用 @happy @HP-1 Scenario: HP-1 Given 界面语言为中文的用户在任意外壳页面 When 点击顶栏语言切换器中的 English Then POST /api/ui-locale 携带 locale=en 与当前路径,服务端写 codelphi_locale=en 并 303 重定向回原页,页面以英文文案重新渲染 @happy @HP-10 Scenario: HP-10 Given 界面语言为 en 时查看仪表盘、设置页、成本观测页或产品总览 When 页面调用 formatDateIn/formatDateTimeIn 渲染日期 Then 日期按 en 的 Intl 格式输出,不再使用硬编码 zh-CN @happy @HP-14 Scenario: HP-14 Given cookie 中 codelphi_locale 为 en When 渲染根布局与外壳 Then html lang 为 en,metadata 描述为英文,导航与品牌标语取英文字典条目 @happy @HP-2 Scenario: HP-2 Given 界面语言为英文的用户打开应用外壳页面 When 渲染侧栏与顶栏 Then 导航项显示 Products/Workspaces/Settings 等英文标签,品牌标语为 Product change radar,html lang 为 en @happy @HP-3 Scenario: HP-3 Given 匿名访客打开公开外壳页面 When 渲染顶栏 Then 显示语言切换器与英文/中文对应的登录入口文案,站点导航按 locale 取值 @happy @HP-4 Scenario: HP-4 Given 用户在任意外壳顶栏点击另一种界面语言 When LanguageSwitcher 提交 POST /api/ui-locale 携带当前路径 Then 接口写入一年期 codelphi_locale cookie 并 303 回跳,切换器调用 router.refresh 刷新当前页 @happy @HP-5 Scenario: HP-5 Given 界面语言 cookie 为 en When 根布局与外壳渲染 Then html lang 为 en,metadata description、导航、登录、演示提示与品牌标语取英文条目 @happy @HP-6 Scenario: HP-6 Given 界面语言为 en 且页面展示日期 When 仪表盘、设置页、成本页或产品总览渲染日期 Then 日期经 formatDateIn/formatDateTimeIn 按 en 的 Intl 格式输出 Feature: 登录与 403 页面 # 登录页展示品牌主张、Chandler 统一账号登录入口、数据说明与公开演示入口(含五个匿名导出链接);403 页说明账号无权限并引导返回登录。两页均按界面语言渲染中英文案。 @edge @EC-1 Scenario: EC-1 Given 部署未配置公开演示产品 When 打开登录页 Then 不渲染公开演示入口区块,仅显示登录卡片 @error @ER-1 Scenario: ER-1 Given 账号无 CoDelphi 访问权限 When 打开 /forbidden Then 显示 403 标题、受限说明与「返回登录」链接,文案按界面语言渲染 @happy @HP-1 Scenario: HP-1 Given 未登录访客打开登录页且存在公开演示产品 When 渲染 /login Then 显示登录卡片、Chandler 登录按钮、数据说明,以及公开演示入口与规格清单、Gherkin、Markdown、30 天变更索引、Explainer 五个导出链接 @happy @HP-2 Scenario: HP-2 Given 界面语言为 en 的访客打开登录页 When 渲染 /login Then 标题、说明、登录按钮、数据说明与演示入口文案取 LOGIN 字典英文条目 @happy @HP-3 Scenario: HP-3 Given 已登录用户访问登录页 When 打开 /login Then 重定向到首页 Feature: 登录页与 403 访问受限页 # 登录页展示品牌陈述、Chandler 统一账号登录入口、数据说明与公开演示入口(含五个匿名导出链接);403 页提示当前部署启用专用登录限制并引导返回登录。两页均按界面语言渲染文案。 @edge @EC-1 Scenario: EC-1 Given 界面语言为 en 的账号无访问权限 When 打开 403 页 Then 标题、说明与返回登录链接取英文字典条目 @edge @EC-2 Scenario: EC-2 Given 部署未配置公开演示产品 When 打开登录页 Then 不渲染公开演示入口区块,仅显示登录卡片 @edge @EC-3 Scenario: EC-3 Given 界面语言为英文的账号无访问权限 When 访问 /forbidden Then 显示英文 403 标题、说明与 Back to sign-in 链接 @error @ER-1 Scenario: ER-1 Given 账号无 CoDelphi 访问权限 When 访问受限页面被导向 403 页 Then 显示「这个账号还没有 CoDelphi 权限」提示与「返回登录」链接 @error @ER-2 Scenario: ER-2 Given 已登录用户访问登录页 When 访问 /login Then 重定向到首页,不渲染登录表单 @happy @HP-1 Scenario: HP-1 Given 未登录访客打开登录页且存在公开演示产品 When 渲染登录页 Then 显示登录陈述、Chandler 登录按钮、数据说明与公开演示入口及五个匿名导出链接 @happy @HP-2 Scenario: HP-2 Given 界面语言为 en 的访客打开登录页 When 渲染登录页 Then 标题、陈述、登录按钮、数据说明与演示导出名称均取英文字典条目 @happy @HP-3 Scenario: HP-3 Given 账号无 CoDelphi 访问权限 When 打开 403 页 Then 显示无权限标题与说明,并提供「返回登录」链接 @happy @HP-4 Scenario: HP-4 Given 未登录访客打开登录页且存在公开演示产品 When 访问 /login Then 渲染登录卡片与公开演示入口,列出规格清单、Gherkin 回归用例、Markdown 规格书、30 天变更索引与 Explainer 五个匿名导出链接 @happy @HP-5 Scenario: HP-5 Given 界面语言为 en 的访客打开登录页 When 访问 /login Then 标题、说明、Chandler 登录按钮、数据说明与演示入口文案以英文渲染 @happy @HP-6 Scenario: HP-6 Given 已登录用户访问登录页 When 访问 /login Then 重定向到首页 @happy @HP-7 Scenario: HP-7 Given 界面语言为英文的访客打开登录页 When 访问 /login Then 渲染英文标题 Understand every product change.、Sign in with Chandler 按钮与英文数据说明 @happy @HP-8 Scenario: HP-8 Given 存在公开演示产品且访客打开登录页 When 查看公开演示区块 Then 显示演示入口与五个匿名导出链接,链接名称按界面语言渲染 Feature: 登录页与 403 页双语入口 # 登录页与 403 页按界面语言渲染文案:登录页含品牌标题、Chandler 登录按钮、数据说明、公开演示入口与五个匿名导出标签;403 页含权限说明与返回登录链接。 @edge @EC-1 Scenario: EC-1 Given 未配置公开演示产品 When 渲染登录页 Then 不渲染公开演示区块,仍显示 Chandler 登录按钮 @edge @EC-2 Scenario: EC-2 Given 账号无 CoDelphi 权限 When 打开 /forbidden Then 按界面语言显示 403 标题、说明与「返回登录」链接 @edge @EC-3 Scenario: EC-3 Given 界面语言为 zh-CN 的访客打开登录页 When 访问 /login Then 页面文案与默认中文一致,不出现英文条目 @happy @HP-1 Scenario: HP-1 Given 界面语言为中文的访客打开 /login When 渲染登录页 Then 显示中文标题「读懂每一次产品变化。」、Chandler 登录按钮与数据说明 @happy @HP-2 Scenario: HP-2 Given 界面语言为 en 的访客打开 /login When 渲染登录页 Then 标题、说明、登录按钮、数据说明与公开演示入口按英文字典条目渲染 @happy @HP-3 Scenario: HP-3 Given 存在公开演示产品且访客未登录 When 查看登录页公开演示区块 Then 显示演示入口链接与五个匿名导出入口(spec.json、Gherkin、Markdown、30 天变更索引、Explainer) @happy @HP-4 Scenario: HP-4 Given 存在公开演示产品且界面语言为 en When 登录页渲染公开演示区块 Then 演示标题、说明、进入链接与五个匿名导出链接名称显示英文 @happy @HP-5 Scenario: HP-5 Given 界面语言为 en 的用户访问 403 页 When 打开 /forbidden Then 标题、说明与「返回登录」链接显示英文文案 Feature: 知识健康统计 # 只展示事实性统计:漂移条目、已核实条目、最新发布回归任务四态与近 90 天编辑归因,不计算综合健康分数。 @edge @EC-1 Scenario: EC-1 Given 编辑记录缺少编辑者归因 When buildKnowledgeHealth 执行 Then 该编辑计入 unattributedEdits,不进入 maintenance 分布 @happy @HP-1 Scenario: HP-1 Given 产品存在漂移条目、已核实条目与最新发布回归任务 When buildKnowledgeHealth 执行 Then 返回 staleCount 与最多 10 条 staleItems、verifiedCount、回归四态计数与按编辑数降序的维护分布 Feature: 知识健康面板 # 产品页展示知识健康事实统计:待核实漂移、核实覆盖、最近核实时间、最新发布回归四态与人工编辑分布,无综合分。 @edge @EC-1 Scenario: EC-1 Given 知识健康接口失败 When KnowledgeHealthPanel 加载 Then 面板不渲染,不显示错误占位 @happy @HP-1 Scenario: HP-1 Given 产品已有规格与核实数据 When KnowledgeHealthPanel 加载 Then 展示漂移数、核实覆盖比例、最近核实日期与回归通过统计 Feature: 组织邀请与工作区引导 # owner 邀请邮箱:已是用户直接加入组织成员,否则写入待接受邀请、签发 7 天令牌并发邮件;登录时自动认领未签发令牌的邀请并幂等创建个人工作区与 Trial 额度。 @edge @EC-1 Scenario: EC-1 Given 邀请邮箱尚未签发令牌 When claimOrganizationInvitations 执行 Then 登录时按邮箱认领邀请并写入组织成员,标记 accepted @error @ER-1 Scenario: ER-1 Given 非 owner 发起邀请 When inviteOrganizationMember 执行 Then 抛出 forbidden @error @ER-2 Scenario: ER-2 Given 请求角色低于所需最低角色 When requireOrganization 执行 Then 抛出 forbidden @happy @HP-1 Scenario: HP-1 Given owner 邀请的邮箱已是注册用户 When inviteOrganizationMember 执行 Then 直接写入 organization_members 并返回 member_added,不发邮件 @happy @HP-2 Scenario: HP-2 Given 被邀请邮箱尚未注册 When inviteOrganizationMember 执行 Then 写入待接受邀请、签发 7 天令牌并发送邀请邮件,返回 invitation_pending 与邀请链接 @happy @HP-3 Scenario: HP-3 Given 用户首次登录且无个人工作区 When ensureUserWorkspace 执行 Then 创建 u- 前缀随机 slug 的个人工作区、写入 owner 成员并幂等发放 Trial 额度 Feature: 行为面两步提取 # 把窗口 diff 的行为文件按路径序分组做定向微任务提取,逐文件显式提问行为或无行为,按 hunk 公平预算读取变更区上下文,失败重试一次后跳过并披露枚举指标。 @edge @EC-1 Scenario: EC-1 Given 复述输出含未知路径或畸形条目 When parseBehavioralDescriptions 执行 Then 只保留已知路径的合法条目,畸形条目丢弃 @edge @EC-2 Scenario: EC-2 Given 判定输出缺 product 或 release 且 features 为空数组 When analyzeBehavioralGroup 执行 Then 注入占位 product/release,features 空数组合法 @edge @EC-3 Scenario: EC-3 Given 窗口内无实现级行为变更(纯文档/注释/格式提交) When runAnalysisBody 执行 Then 跳过全部 AI 提取,上一版规格原样沿用、零变化 @edge @EC-4 Scenario: EC-4 Given 行为文件数超过组数上限 When enumerateBehavioralChunks 执行 Then 只处理前 maxGroups 组,其余计入 pathsOmitted,状态为 partial @edge @EC-5 Scenario: EC-5 Given 单文件 hunk 总需求超过 400 行预算 When allocateHunkReads 执行 Then 每 hunk 先保底核心区间,剩余按需求比例分配,未满足的 hunk 标记 truncated 并计数披露 @error @ER-1 Scenario: ER-1 Given 某组定向提取连续两次失败 When enumerateBehavioralChunks 执行 Then 跳过该组,groupsFailed 计数增加,状态为 partial 而非完整成功 @happy @HP-1 Scenario: HP-1 Given 行为变更文件含多个变更块 When analyzeBehavioralGroup 执行 Then 先调用复述 prompt 逐块给出改前改后,再调用判定 prompt 输出场景 @happy @HP-2 Scenario: HP-2 Given push 变更包含行为文件 When runAnalysisBody 计算 behavioralPaths 并进入提取 Then 行为文件清单跨模式共享,供 RC2 与定向枚举使用 @happy @HP-3 Scenario: HP-3 Given 窗口存在实现级行为变更 When runAnalysisBody 执行 Then 按行为面文件清单分区逐趟提取后合并为完整规格 @happy @HP-4 Scenario: HP-4 Given 窗口 diff 含行为文件 When enumerateBehavioralChunks 执行 Then 按组定向提取,每组结果用组文件与全部 diff 文件作证据允许集清洗后返回 Feature: 行为面枚举提取 # 把窗口 diff 的行为文件按路径稳定分组,逐文件定向读取变更区上下文并显式提问行为/无行为,组失败重试一次后跳过;输出枚举完整性指标(complete/partial/failed/not_applicable)与 hunk 公平预算截断计数。 @edge @EC-1 Scenario: EC-1 Given 行为文件组数超过上限 When enumerateBehavioralChunks 执行 Then 只处理前 maxGroups 组,打 warn 日志,状态记为 partial @edge @EC-2 Scenario: EC-2 Given 行为文件在窗口内被删除或不可读 When enumerateBehavioralChunks 执行 Then 跳过该文件读取,patch 仍在组 prompt 中提供 @error @ER-1 Scenario: ER-1 Given 某组定向提取重试后仍失败 When enumerateBehavioralChunks 执行 Then 跳过该组,groupsFailed 计数增加,不阻塞主提取产出 @happy @HP-1 Scenario: HP-1 Given 窗口存在行为变更文件且枚举开关开启 When enumerateBehavioralChunks 执行 Then 按组定向提取并清洗证据,返回 chunks 与 pathsEnumerated/groupsCompleted 等指标 @happy @HP-2 Scenario: HP-2 Given 单文件多个 hunk 且总需求超过 400 行预算 When allocateHunkReads 执行 Then 每 hunk 先保底核心区间,剩余按需求比例分配,未满足的标记 truncated Feature: 覆盖率展示口径 # 覆盖率展示唯一入口:按文件字节计算百分比并固定一位小数,显式声明字节口径,文件装配数作为独立事实并排展示,不合成百分比。 @happy @HP-1 Scenario: HP-1 Given 已有覆盖率回执 When coverageByteLabel 与 coverageFilesLabel 执行 Then 输出「源码装配覆盖率(按文件字节)12.1%」与「已装配实现文件 X/Y 个」 Feature: 规格工作区场景卡片 # 在规格工作区渲染单个场景的类别、编号、来源与证据链接,并对证据全部来自测试/文档的场景显示「测试佐证」徽标。 @edge @EC-1 Scenario: EC-1 Given 场景同时带 staleSince 与测试佐证 When 渲染 ScenarioCard Then 「测试佐证」与「可能过时」两个徽标同时显示,互不覆盖 @happy @HP-1 Scenario: HP-1 Given 场景证据包含实现代码路径 When 渲染 ScenarioCard Then 正常展示类别、编号、情境/操作/结果与证据链接,不显示测试佐证徽标 @happy @HP-2 Scenario: HP-2 Given 场景证据全部来自测试或文档 When 渲染 ScenarioCard Then 编号旁显示「测试佐证」徽标,悬停提示行为以人工核实为准 Feature: 规格工作台与场景编辑 # 规格工作台展示功能树与功能详情,支持新建/编辑/删除功能与场景,场景按 happy/edge/error 筛选、按核实状态筛选、搜索与列表/表格切换,并可标记已核实。 @edge @EC-1 Scenario: EC-1 Given 筛选后没有匹配场景 When 渲染场景列表 Then 显示「没有匹配场景,调整筛选或新建一个场景。」 @edge @EC-2 Scenario: EC-2 Given 只读 demo 下点击编辑入口 When 打开场景或功能编辑 Then demo guard 拦截,不打开编辑面板 @error @ER-1 Scenario: ER-1 Given 保存场景接口失败 When 提交场景表单 Then 显示接口错误消息或「保存场景失败」 @error @ER-2 Scenario: ER-2 Given 删除场景接口失败 When 点击删除场景 Then 显示接口错误消息或「删除场景失败」 @happy @HP-1 Scenario: HP-1 Given 编辑者在功能详情点击新场景 When 填写情境、操作、结果与分类并保存 Then 保存场景后关闭编辑面板并刷新页面 @happy @HP-2 Scenario: HP-2 Given 功能下存在多个场景 When 切换分类筛选、搜索或列表/表格视图 Then 按分类计数与搜索词过滤场景,并以所选视图展示 @happy @HP-3 Scenario: HP-3 Given 场景需要人工核实 When 点击标记已核实 Then 调用核实接口并刷新,徽章显示已核实与核实人 Feature: 规格工作台场景卡片 # 在规格工作台逐条展示场景的类别、编号、来源与情境/操作/结果,附证据链接与校验、编辑、删除操作,并对可能过时与仅有测试/文档佐证的场景给出标记。 @edge @EC-1 Scenario: EC-1 Given 场景证据全部来自测试或文档路径 When 渲染 ScenarioCard Then 场景标签行显示「测试佐证」徽标,悬停提示行为以人工核实为准 @edge @EC-2 Scenario: EC-2 Given 场景证据为空数组 When isTestOnlyEvidence 判定 Then 返回 false,不显示「测试佐证」徽标 @edge @EC-3 Scenario: EC-3 Given 场景被标记可能过时 When 渲染场景卡片 Then 同时显示「可能过时」徽标,与「测试佐证」徽标并列 @happy @HP-1 Scenario: HP-1 Given 场景证据包含实现代码路径 When 渲染 ScenarioCard Then 正常展示场景标签与证据链接,不显示「测试佐证」徽标 @happy @HP-2 Scenario: HP-2 Given 场景证据全部来自测试或文档 When 渲染场景卡片 Then 标签行显示「测试佐证」徽标,悬停提示行为以人工核实为准 @happy @HP-3 Scenario: HP-3 Given 场景证据含实现代码 When 渲染场景卡片 Then 不显示「测试佐证」徽标,其余标签与证据链接照常展示 Feature: 规格工作台披露与影响面查询 # 规格工作台顶部披露分析完整性与源码装配覆盖率,并说明静态提取盲区的人工补充入口;同时提供按文件反查受影响功能与场景的影响面查询。 @edge @EC-1 Scenario: EC-1 Given 产品尚无完整性账本或覆盖率回执 When 渲染 SpecWorkspace Then 不渲染对应披露行 @happy @HP-1 Scenario: HP-1 Given 产品存在完整性账本与覆盖率回执 When 渲染 SpecWorkspace Then 展示分析完整性状态、覆盖率字节与文件数、提取趟数及未覆盖提示 Feature: 规格差异变体折叠 # 比较前后两版规格产出变化行,并把行为等价、换编号与措辞近似的差异折叠进审计面;变体判定宁漏勿错合,折叠不删除数据。 @edge @EC-1 Scenario: EC-1 Given 两条场景 category 不同(happy 与 edge) When scenarioVariantSimilar 执行 Then 返回 false,不折叠 @edge @EC-2 Scenario: EC-2 Given 两条场景结果数字不同(404 与 400) When scenarioVariantSimilar 执行 Then 数字签名不一致,返回 false,不折叠 @edge @EC-3 Scenario: EC-3 Given 两条场景结果否定词计数不同(可以重试 与 不可以重试) When scenarioVariantSimilar 执行 Then 返回 false,不折叠 @edge @EC-4 Scenario: EC-4 Given 两条场景情境被同义改写但操作与结果行为等价 When scenarioVariantSimilar 执行 Then 返回 true,判为同一行为 @error @ER-1 Scenario: ER-1 Given 两条场景骨架词共享但结果语义相反(跳转 dashboard 与 锁定账户) When scenarioVariantSimilar 执行 Then 返回 false,真差异不被误合 @happy @HP-1 Scenario: HP-1 Given 同功能内一条场景被移除、另一条措辞近似的新场景被加入 When diffSpecWithFolded 执行 Then 该对折叠为 reason=variant 进入 folded,changes 中不产生变化行 @happy @HP-2 Scenario: HP-2 Given 同功能内 removed↔added 且行为字段逐项等价 When diffSpecWithFolded 执行 Then 折叠为 reason=renumber,不产生变化行 Feature: 规格检索与问答 # 规格工作台提供产品内搜索(功能名/摘要、场景文本与证据路径,防抖并支持点击外部或 Escape 收起)与检索式问答(一次问句返回规格原文卡片,未命中明确声明未覆盖,限流明示恢复时间)。 @edge @EC-1 Scenario: EC-1 Given 搜索无匹配结果 When 渲染搜索结果 Then 显示「没有匹配的功能或场景。」 @edge @EC-2 Scenario: EC-2 Given 提问频率超过每 IP 每小时 10 次 When 点击查规格 Then 提示提问频率已达上限与可再试时间 @error @ER-1 Scenario: ER-1 Given 搜索接口失败 When 输入关键词 Then 显示「搜索暂时不可用,请稍后重试。」 @error @ER-2 Scenario: ER-2 Given 问答接口失败 When 点击查规格 Then 显示「查询暂时不可用,请稍后重试。」 @happy @HP-1 Scenario: HP-1 Given 用户在产品内搜索关键词 When 在搜索框输入 Then 防抖后请求搜索接口,下拉展示命中的功能与场景并直达对应功能视图 @happy @HP-2 Scenario: HP-2 Given 用户向规格提问 When 输入问句并点击查规格 Then 返回规格原文卡片,命中时展示功能与最多 8 条场景及证据,并标注基于的规格版本 Feature: 规格版本落库与发布记录 # 分析结果在单事务内写入规格版本、重建文件索引、标记漂移、保存覆盖率回执、下线旧生成规格并写入发布与改动文件清单,失败整体回滚。 @edge @EC-1 Scenario: EC-1 Given 发布已存在 changes 记录 When upsertRelease 执行 Then 跳过 changes 插入,直接确保回归任务后返回 releaseId @edge @EC-10 Scenario: EC-10 Given 仓库没有任何规格版本 When loadPreviousSpecText 执行 Then 返回空数组,锚定降级为仅命名约束 @edge @EC-2 Scenario: EC-2 Given 增量模式下 AI 输出命中 carried 键 When insertSpecVersion 执行 Then 该 AI 功能被丢弃并计数,carried 内容保全 @edge @EC-3 Scenario: EC-3 Given 新版场景数较上一版骤降超过阈值 When detectSpecCollapse 执行 Then 返回坍缩告警,只告警不阻断落库 @edge @EC-4 Scenario: EC-4 Given 前后场景行为相反或数字/否定冲突(如 404 与 400、可以重试与不可以重试) When scenarioVariantSimilar 判定 Then 返回 false,不折叠,两条变化均保留 @edge @EC-5 Scenario: EC-5 Given 前后场景 category 不一致(happy 与 edge) When scenarioVariantSimilar 判定 Then 直接返回 false,不折叠 @edge @EC-6 Scenario: EC-6 Given 同功能内 removed↔added 且行为字段逐项等价 When scenarioChanges 执行 Then 折叠为 reason=renumber,不产生变化行 @edge @EC-7 Scenario: EC-7 Given 同功能内 removed↔added 措辞近似但语义相同 When scenarioChanges 执行 Then 折叠为 reason=variant 进 folded 审计面,不产生变化行、不删数据 @edge @EC-8 Scenario: EC-8 Given 两条场景行为相反、主体互换、数字或否定冲突 When scenarioVariantSimilar 执行 Then 返回 false,保留为真实变化 @edge @EC-9 Scenario: EC-9 Given prioritize 中的功能名在上一版规格中不存在 When loadPreviousSpecText 执行 Then 顺序不变,全部功能按 position 序返回 @error @ER-1 Scenario: ER-1 Given 保存过程中任一步抛错 When saveResult 执行 Then 事务回滚并抛出错误,run 不标记成功 @error @ER-2 Scenario: ER-2 Given 当前 run 已被更新的 run 取代 When assertLatestRun 执行 Then 抛出 Analysis superseded by run N,事务回滚 @error @ER-3 Scenario: ER-3 Given 新增功能的实现证据文件本次均无行为性行变更 When diffFeature 执行 Then 折叠为 reason=tests-only,不落行为变化 @happy @HP-1 Scenario: HP-1 Given 分析成功且存在上一版规格 When saveResult 执行 Then 事务内写入规格版本、覆盖率回执、发布记录与改动文件清单,run 标记 succeeded @happy @HP-2 Scenario: HP-2 Given 发布已存在同 sha 记录 When upsertRelease 执行 Then 按唯一键更新 analysis_run_id 与 spec_version_id,不重复插入 @happy @HP-3 Scenario: HP-3 Given 增量模式存在受影响集之外的既有功能 When insertSpecVersion 执行 Then 未受影响功能原样沿用上一版,AI 输出与人工条目按优先级合并 @happy @HP-4 Scenario: HP-4 Given 同功能内 removed↔added 场景措辞近似但语义相同 When diffSpec 计算变化 Then 该对折叠为 reason=variant 的审计明细,不产生变化行 @happy @HP-5 Scenario: HP-5 Given 分析产出的场景与上一版行为等价但编号变化 When diffSpec 计算场景差异 Then removed↔added 对折叠为 reason=renumber,不产生变化行 @happy @HP-6 Scenario: HP-6 Given 分析产出的场景与上一版为同义改写(如「演示访客」改为「demo 访客」) When diffSpec 计算场景差异 Then removed↔added 对折叠为 reason=variant,写入 folded 审计,changes 为空 @happy @HP-7 Scenario: HP-7 Given 分析产出新规格 When insertSpecVersion 执行 Then 写入规格版本并在同事务内重建 path→规格反向索引 @happy @HP-8 Scenario: HP-8 Given 仓库已有最新规格版本 When loadPreviousSpecText 执行 Then 返回按 position 序的功能与场景全文,供 prompt 锚定 @happy @HP-9 Scenario: HP-9 Given 增量分析已确定受影响功能清单 When loadPreviousSpecText 传入 prioritize 执行 Then 受影响功能稳定排到最前,其余保持 position 序 Feature: 规格编辑接口错误映射 # 规格编辑路由统一解析实体 ID 与错误:非法 ID 抛 400,服务错误按 specEditorErrorInfo 映射状态码与消息。 @error @ER-1 Scenario: ER-1 Given 实体 ID 非正整数 When routeEntityId 执行 Then 抛出 400「无效的资源 ID」 @error @ER-2 Scenario: ER-2 Given 编辑操作抛错 When editorAction 执行 Then 返回映射后的状态码与错误消息 @happy @HP-1 Scenario: HP-1 Given 编辑操作成功 When editorAction 执行 Then 返回操作结果与指定成功状态码 Feature: 证据清洗与提取重试 # 对每趟提取结果按分区允许集清洗证据,剔除不可溯源声明并留日志;单趟硬失败先重试一次,两次都失败才让 run 失败。 @edge @EC-1 Scenario: EC-1 Given 模型引用了本趟未读的文件 When sanitizeEvidence 执行 Then 剔除该证据及整条场景/变化,打 warn 日志,其余产出保留 @edge @EC-2 Scenario: EC-2 Given 候选全部被清洗后为空 When sanitizeEvidence 执行 Then 标记 allCandidatesRejected 并打 error 日志,不伪装成正常空结果 @edge @EC-3 Scenario: EC-3 Given 模型输出缺 changes 键 When parseAnalysisContent 执行 Then changes 归一化为空数组 @edge @EC-4 Scenario: EC-4 Given 证据数组为空 When isTestOnlyEvidence 执行 Then 返回 false,不标记为测试佐证 @edge @EC-5 Scenario: EC-5 Given 证据以 JSON 字符串或带 evidence 字段的场景对象传入 When isTestOnlyEvidence 执行 Then 解析出证据数组后按同一口径判定 @edge @EC-6 Scenario: EC-6 Given 证据含 CI/锁文件等基础设施文件 When evidenceKind 执行 Then 仍判为 implementation,不标记为测试佐证 @edge @EC-7 Scenario: EC-7 Given 部分证据路径不在允许集内 When sanitizeEvidence 执行 Then 剔除不可溯源声明,droppedClaims 计数增加,证据完整性维度为 partial @error @ER-1 Scenario: ER-1 Given 单趟提取重试后仍失败 When extractPassWithRetry 执行 Then 错误向上抛出,run 失败 @error @ER-2 Scenario: ER-2 Given 响应不含完整 JSON 对象 When parseAnalysisContent 执行 Then 抛出 AI response did not contain a JSON object @error @ER-3 Scenario: ER-3 Given 模型输出结构不可解析 When parseAnalysisContent 解析 Then schema 校验失败并抛错,交由重试与 run 失败如实暴露 @error @ER-4 Scenario: ER-4 Given 某趟候选全部被清洗 When deriveEvidenceIntegrity 执行 Then allCandidatesRejectedPasses 计数增加,证据完整性维度为 failed @happy @HP-1 Scenario: HP-1 Given 模型引用的证据路径都在本分区允许集内 When sanitizeEvidence 执行 Then 证据全部保留,返回清洗摘要且 droppedClaims 为 0 @happy @HP-2 Scenario: HP-2 Given 单趟提取首次调用抛错 When extractPassWithRetry 执行 Then 重试一次,重试调用照常落 LLM 回执,成功则继续 @happy @HP-3 Scenario: HP-3 Given 模型在合法 diff 路径前后带零宽字符 When parseAnalysisContent 执行 Then 证据路径去除不可见字符后通过允许集校验 @happy @HP-4 Scenario: HP-4 Given 模型输出证据列表超过上限 When parseAnalysisContent 在 schema 校验前执行 capEvidence Then 证据截断为前 5 条,解析继续成功 @happy @HP-5 Scenario: HP-5 Given 模型输出证据为 JSON 字符串或带 evidence 字段的场景对象 When isTestOnlyEvidence 读取证据 Then 兼容三种形状解析出证据数组并判定是否全部非实现代码 @happy @HP-6 Scenario: HP-6 Given 场景证据含实现代码路径 When isTestOnlyEvidence 执行 Then 返回 false,按实现证据对待 @happy @HP-7 Scenario: HP-7 Given 模型输出的证据路径都在允许集内 When sanitizeEvidence 执行 Then 证据保留,keptClaims 计数增加,证据完整性维度为 complete Feature: 证据链接与证据强度分级 # 按路径把证据分为实现、测试、文档等强度等级,并识别证据全部来自测试/文档的场景,供展示标记与发布影响降权使用。 @edge @EC-1 Scenario: EC-1 Given 证据入参为带 evidence 字段的场景对象 When isTestOnlyEvidence 执行 Then 递归取出 evidence 数组后判定,全部非实现代码时返回 true @edge @EC-10 Scenario: EC-10 Given 证据数组为空 When isTestOnlyEvidence 执行 Then 返回 false,不把无证据场景判为测试佐证 @edge @EC-11 Scenario: EC-11 Given 证据路径为 CI 工作流或锁文件等基础设施文件 When isTestOnlyEvidence 执行 Then evidenceKind 仍判为 implementation,返回 false,保持可见不冒进 @edge @EC-2 Scenario: EC-2 Given 证据路径为 CI 工作流或锁文件 When isTestOnlyEvidence 执行 Then evidenceKind 仍判为 implementation,不视为测试佐证 @edge @EC-3 Scenario: EC-3 Given 传入空证据数组 When isTestOnlyEvidence 执行 Then 返回 false,不判为测试佐证 @edge @EC-4 Scenario: EC-4 Given 传入带 evidence 字段的场景对象或 JSON 字符串 When isTestOnlyEvidence 执行 Then 递归取出证据数组后按同一口径判定 @edge @EC-5 Scenario: EC-5 Given 证据包含任一实现代码文件 When isTestOnlyEvidence 执行 Then 返回 false,不标记为测试佐证 @edge @EC-6 Scenario: EC-6 Given 证据路径为测试或文档文件 When evidenceKind 执行 Then 返回 test 或 document,全部非实现时 isTestOnlyEvidence 为 true @edge @EC-7 Scenario: EC-7 Given 证据路径为绝对路径、含反斜杠或含 .. When evidenceUrl 执行 Then 返回 null,不生成链接 @edge @EC-8 Scenario: EC-8 Given 证据路径为 CI 配置或锁文件 When evidenceKind 判定 Then 仍按实现代码处理,isTestOnlyEvidence 返回 false @edge @EC-9 Scenario: EC-9 Given 传入的是 behavior_changes 的 before/after 场景对象 When isTestOnlyEvidence 执行 Then 读取对象上的 evidence 字段继续判定,不因非数组形状返回 false @happy @HP-1 Scenario: HP-1 Given 证据路径为普通源码文件 When evidenceKind 执行 Then 返回 implementation @happy @HP-2 Scenario: HP-2 Given 证据路径为 tests/ 目录或 .test./.spec. 文件 When evidenceKind 执行 Then 返回 test @happy @HP-3 Scenario: HP-3 Given 场景证据全部来自测试或文档 When isTestOnlyEvidence 执行 Then 返回 true,展示层可打「测试佐证」标 @happy @HP-4 Scenario: HP-4 Given 场景证据含至少一条实现代码路径 When isTestOnlyEvidence 执行 Then 返回 false,按实现证据对待 @happy @HP-5 Scenario: HP-5 Given 证据路径指向测试或文档文件 When EvidenceLinks 渲染证据 Then 链接前显示 [测试] 或 [文档] 标记 @happy @HP-6 Scenario: HP-6 Given 场景证据全部来自测试/文档 When isTestOnlyEvidence 执行 Then 返回 true,供场景卡片打「测试佐证」标与总览影响降权 @happy @HP-7 Scenario: HP-7 Given 证据路径指向实现代码且提交 SHA 合法 When evidenceUrl 执行 Then 返回带 #L 行号锚点的 GitHub blob 链接 @happy @HP-8 Scenario: HP-8 Given 场景证据全部来自测试/文档 When isTestOnlyEvidence 执行 Then 返回 true,供展示层打「测试佐证」标与影响投影降权 @happy @HP-9 Scenario: HP-9 Given 场景证据全部来自测试或文档路径 When isTestOnlyEvidence 执行 Then 返回 true,场景被标记为测试佐证 Feature: 超管手工充值 # 超管按邮箱给用户个人工作区 Trial 钱包发放 SAU,同一 reason 幂等不重复发放。 @edge @EC-1 Scenario: EC-1 Given 邮箱、数量或原因不合法 When 渲染充值表单 Then 发放按钮禁用并提示待填写项 @error @ER-1 Scenario: ER-1 Given 充值接口返回错误 When 点击发放 Then 显示失败状态码与错误消息 @happy @HP-1 Scenario: HP-1 Given 超管填写合法邮箱、SAU 数量与原因 When 点击发放 Then 发放成功并显示钱包余额,或提示同 reason 已发放过 Feature: 超管支付操作面板 # 超管支付页可创建 0.01 元联调测试单并展示支付参数,查看最近 20 笔订单台账并对已支付订单发起退款。 @edge @EC-1 Scenario: EC-1 Given 订单台账为空 When 渲染订单表格 Then 显示「暂无订单。先创建一笔测试单。」 @error @ER-1 Scenario: ER-1 Given 测试单创建或退款接口失败 When 提交操作 Then 显示对应失败消息 @happy @HP-1 Scenario: HP-1 Given 超管需要联调支付链路 When 点击创建 0.01 元测试单 Then 展示微信二维码或 H5/支付宝链接并提示 webhook 自动履约 @happy @HP-2 Scenario: HP-2 Given 订单已支付且未退款 When 点击退款 Then 提交退款请求并提示退款状态与金额 Feature: 远程源码分区装配 # 按仓库树与预算规划源码切片,多趟分区提取并排除已选路径,按字节硬顶整文件包含,证据允许集与实际进 prompt 的文件对齐。 @edge @EC-1 Scenario: EC-1 Given 装配后文件总字节超过 AI 上下文预算 When sliceToSourceFiles 执行 Then 贪心整文件包含,超预算即停,被排除文件不留在证据允许集 @error @ER-1 Scenario: ER-1 Given 仓库没有任何可分析源码 When getPartitionedAnalysisSources 执行 Then 抛出 No supported source files found @happy @HP-1 Scenario: HP-1 Given 仓库存在可分析源码且预算充足 When getPartitionedAnalysisSources 执行 Then 返回按规划顺序装配的分区源码与共享索引 @happy @HP-2 Scenario: HP-2 Given 单窗仓库实现面不超过窗口 When getRemoteAnalysisSources 执行 Then 返回恰好 1 个分区,行为与分区装配一致 Feature: 连接检查接口 # 按连接 ID 与工作区校验编辑权限后执行连接检查,ID 非法返回 404。 @error @ER-1 Scenario: ER-1 Given 连接 ID 非正整数 When POST 连接检查接口 Then 返回 404 与「连接 ID 无效」 @happy @HP-1 Scenario: HP-1 Given 编辑者请求检查连接 When POST 连接检查接口 Then 返回检查结果状态与响应体 Feature: 通知渠道与投递 # 管理产品级 Slack/webhook 通知渠道(目标加密落库、UI 只显示 host+path)与成员邮件偏好;分析成功后 best-effort 投递行为变化摘要,单渠道失败只记 deliveries 不回滚分析。 @edge @EC-1 Scenario: EC-1 Given 本次分析没有行为变化 When renderNotificationText 执行 Then 返回 null,不发送任何通知 @edge @EC-2 Scenario: EC-2 Given 检测到规格坍缩或单功能坍缩 When renderNotificationText 执行 Then 通知正文附带坍缩警告行,提示核实变化明细 @error @ER-1 Scenario: ER-1 Given 单个渠道投递失败 When deliverAnalysisNotifications 执行 Then 记录 failed 投递与错误,继续投递其他渠道,不回滚分析状态 @happy @HP-1 Scenario: HP-1 Given 产品配置了启用的通知渠道且本次分析产生行为变化 When deliverAnalysisNotifications 执行 Then 按渠道发送 Slack 文本或 webhook payload,并记录 sent 投递 @happy @HP-2 Scenario: HP-2 Given 成员开启了该产品的邮件偏好 When deliverAnalysisNotifications 执行 Then 经 Chandler /v1/mail/send 发送摘要邮件并记录 sent 投递 Feature: 通知渠道设置 # 产品设置页配置 Slack 与通用 Webhook 通知渠道,可启停、测试、删除,并可开关个人邮件摘要。 @edge @EC-1 Scenario: EC-1 Given 尚未配置任何通知渠道 When 渲染渠道列表 Then 显示「尚未配置通知渠道。」 @error @ER-1 Scenario: ER-1 Given 通知渠道操作接口失败 When 执行保存、测试、启停或删除 Then 显示操作失败错误消息 @happy @HP-1 Scenario: HP-1 Given 编辑者填写 Webhook 地址并保存渠道 When 提交通知渠道表单 Then 渠道保存并刷新列表 @happy @HP-2 Scenario: HP-2 Given 已配置通知渠道 When 点击测试 Then 发送测试消息并提示「测试消息已发送」 Feature: 邀请邮件与邀请令牌 # 邀请令牌以 SHA-256 哈希落库、7 天有效;邀请邮件经 Chandler 注册验证码接口发送,接受邀请时校验邮箱一致并写入组织成员。 @edge @EC-1 Scenario: EC-1 Given 令牌不存在、已接受或已过期 When getInvitationByToken 执行 Then 返回 null,接受邀请时抛 invitation_invalid @error @ER-1 Scenario: ER-1 Given 接受邀请时登录邮箱与邀请邮箱不一致 When acceptOrganizationInvitation 执行 Then 抛出 invitation_email_mismatch @error @ER-2 Scenario: ER-2 Given Chandler 注册邮件接口返回 400 或 409 When sendInvitationEmail 执行 Then 返回 false 表示邮件未发送 @happy @HP-1 Scenario: HP-1 Given 受邀用户携带有效令牌登录且邮箱与邀请一致 When acceptOrganizationInvitation 执行 Then 写入 organization_members 并把邀请标记 accepted Feature: 集成连接客户端 # 前端调用仓库列表、GitHub 连接检查/断开、仓库连接检查/删除与第三方仓库连接保存接口,统一解析错误消息。 @error @ER-1 Scenario: ER-1 Given 仓库列表接口返回非 2xx When fetchGitHubRepositories 执行 Then 抛出接口 error 或「仓库列表读取失败」 @error @ER-2 Scenario: ER-2 Given 连接保存响应缺少 accountLogin When saveRepositoryConnection 执行 Then 抛出接口 error 或「连接保存失败」 @happy @HP-1 Scenario: HP-1 Given 已选择 GitHub 连接且需要浏览仓库 When fetchGitHubRepositories 执行 Then 按 connectionId 与 page 请求 /api/repositories 并返回仓库列表与 hasMore @happy @HP-2 Scenario: HP-2 Given 需要保存 Gitee/GitLab/Gitea 仓库连接 When saveRepositoryConnection 执行 Then POST /api/integrations/repositories 并返回含 accountLogin 的连接结果 Feature: 集成页反馈与连接状态展示 # 集成页把接口错误码或成功码映射为提示文案并区分样式,按连接状态显示已连接/已断开,按是否具备凭据决定展示 App 安装还是 PAT 回退。 @edge @EC-1 Scenario: EC-1 Given 连接状态非 active When connectionStatusLabel 执行 Then 显示「已断开」 @edge @EC-2 Scenario: EC-2 Given 未配置 Personal Access Token When patPresentation 执行 Then 返回禁用态、未配置标签与「选择 GitHub App 安装」选项 @happy @HP-1 Scenario: HP-1 Given 连接操作返回成功码 When integrationFeedback 执行 Then 返回成功文案且 isError 为 false Feature: 零行为变更窗口沿用 # 窗口内无实现级行为变更(纯文档/注释/格式提交)时跳过全部 AI 提取,上一版规格原样沿用、零变化,并沿版本链继承覆盖率回执。 @edge @EC-1 Scenario: EC-1 Given 零行为变更窗口但整条版本链无覆盖率回执 When runAnalysisBody 执行 Then 写入诚实的零回执并打 warn 日志 @edge @EC-2 Scenario: EC-2 Given 仓库尚无上一版规格 When loadLatestSpecFeatures 执行 Then 返回 null,无法沿用 @edge @EC-3 Scenario: EC-3 Given 零行为变更窗口且仓库无上一版规格 When runAnalysisBody 执行 Then 无规格可沿用,按既有空态处理 @edge @EC-4 Scenario: EC-4 Given 零行为变更窗口且上一版无有效覆盖率回执 When loadInheritedCoverageReceipt 执行 Then 沿版本链向上取最近有效回执,避免落合成零回执 @edge @EC-5 Scenario: EC-5 Given 窗口内无实现级行为变更但 diff 命中功能证据面 When runAnalysisBody 执行 Then 不激活增量重提取,避免文档内容被写成假场景 @error @ER-1 Scenario: ER-1 Given 零行为变更窗口但找不到上一版规格 When runAnalysisBody 执行 Then 抛出 no-behavioral-change window but no previous spec found @happy @HP-1 Scenario: HP-1 Given 已有上一版规格且本窗口无行为变更文件 When runAnalysisBody 执行 Then 跳过 AI 提取,规格原样沿用上一版,release 说明声明无行为变化 @happy @HP-2 Scenario: HP-2 Given 零行为变更窗口且版本链上存在有效覆盖率回执 When loadInheritedCoverageReceipt 执行 Then 继承最近有效回执作为本版覆盖率 @happy @HP-3 Scenario: HP-3 Given 窗口内只有文档或注释变更 When runAnalysisBody 执行 Then 跳过 AI 提取,上一版规格整体沿用,不产生行为变化 @happy @HP-4 Scenario: HP-4 Given 窗口内仅有文档/注释/格式提交 When runAnalysisBody 执行 Then 上一版规格原样沿用,零变化,不调用 AI 提取 @happy @HP-5 Scenario: HP-5 Given 窗口无实现级行为变更且存在上一版规格 When runAnalysisBody 执行 Then 上一版规格原样沿用,零变化