CLI Orphan & Duplicate Commands
这个模块是 bd 命令行里的“仓库卫生员”:它专门处理两类会长期污染 issue 系统的数据问题——该关没关的 orphan issue,以及重复创建的 issue(精确重复与语义近重复)。它的重要性不在于功能炫技,而在于保持任务系统的“真实度”:如果状态和语义不干净,后续的排期、依赖分析、自动化同步都会被噪音拖垮。
架构总览
叙事化理解(它在系统里的角色)
把它想象成“城市垃圾分拣站”:
bd orphans负责找“已经施工完但工单没结案”的单子;bd duplicates负责找“完全同一件事被重复登记”的单子,并能执行合并;bd find-duplicates负责找“描述不同但可能是一回事”的单子(机械或 AI 语义判定)。
共同点是:它们都不构建新的领域对象,而是基于现有 types.Issue、store 接口和 CLI 运行时做治理型编排。换句话说,这个模块是策略执行层(policy enforcer + orchestrator),不是底层存储层。
1) 这个模块解决什么问题?
问题一:状态漂移(Orphan Issues)
现实里经常出现:开发者在 commit message 里提到了某 issue,但 issue 仍是 open / in_progress。这会让看板高估未完成工作量。bd orphans 通过复用 doctor.FindOrphanedIssues,把 Git 证据链和数据库状态做交叉验证。
问题二:内容重复(Exact Duplicates)
多人协作下,同一个任务被重复创建是常态。bd duplicates 用 contentKey(title/description/design/acceptanceCriteria/status)做严格分组,保证“自动合并”建立在可解释、低误报的规则上。
问题三:语义重复(Semantic Duplicates)
仅靠字符串全等会漏掉“同义不同词”。bd find-duplicates 用两段式策略:先机械相似度筛候选,再可选 LLM 语义判定。
为什么不是“一把梭:全交给 AI”?
没这么做是因为 CLI 工具场景更强调可控性:
- 成本与时延不可忽视;
- AI 输出格式和稳定性有波动;
- 自动治理动作(尤其 merge/close)需要高可解释性。
所以作者选择了分层策略:精确规则用于可执行修复,语义模型用于发现线索。
2) 心智模型:三条检测管线 + 一个共享执行环境
可以把整个模块放进这个脑图:
- 管线 A(Orphan)=
Git log证据 ×IssueProvider开放问题集 - 管线 B(Exact duplicate)= 内容键分桶 × 结构/引用打分 × 可选自动合并
- 管线 C(Semantic duplicate)= token 相似度预筛 × AI 复核
三条管线共享:
- 全局
store(读写 issue / 依赖 / 配置) - CLI 运行时上下文(
rootCtx,jsonOutput,FatalError,outputJSON) types.Issue数据契约(见 Core Domain Types)
这种设计像“同一条生产线上的不同质检工位”:工具链一致,但判定规则和副作用等级不同。
3) 关键数据流(按命令端到端)
A. bd orphans
orphansCmd.Run调findOrphanedIssues(".")findOrphanedIssues通过getIssueProvider获取doltStoreProviderdoltStoreProvider.GetOpenIssues调两次store.SearchIssues(StatusOpen与StatusInProgress)doltStoreProvider.GetIssuePrefix调store.GetConfig("issue_prefix"),失败/空值回退"bd"doctorFindOrphanedIssues(默认指向doctor.FindOrphanedIssues)执行检测- 结果映射为
[]orphanIssueOutput,按IssueID排序输出 - 若
--fix,逐条closeIssue->closeIssueRunner->exec.Command("bd", "close", ...)
关键耦合点:orphan 算法在 doctor 包,CLI 这里是适配器。好处是复用;代价是行为变化受 doctor 改动影响。
B. bd duplicates
duplicatesCmd.Run读取--auto-merge / --dry-runstore.SearchIssues(ctx, "", types.IssueFilter{})拉全量 issue- 内存中过滤掉
StatusClosed(当前实现只在 open 集合里找重复) findDuplicateGroups用contentKey分桶,保留 size>1 的组countReferences用正则统计文本字段中的 issue ID 引用countStructuralRelationships批量调用store.GetDependencyCounts填issueScorechooseMergeTarget决定保留目标(结构权重 > 文本引用 > ID 字典序)- 输出建议;若自动合并则
performMerge:store.CloseIssuestore.AddDependency(Type: "related")
关键耦合点:依赖 Storage 的读写契约;无事务包装,存在部分成功状态。
C. bd find-duplicates
runFindDuplicates校验--method(mechanical/ai)--method ai时校验ANTHROPIC_API_KEYstore.SearchIssues拉 issue,默认(未传--status)排除 closed- 机械模式:
findMechanicalDuplicatesissueText->tokenize- 逐对计算
jaccardSimilarity与cosineSimilarity - 平均值 >= threshold 进入结果
- AI 模式:
findAIDuplicates- 先机械预筛(
threshold*0.5,下限 0.15) - 候选上限 100,分批 10
analyzeWithAI调client.Messages.New- 解析 JSON,映射回
duplicatePair
- 先机械预筛(
- 最后统一排序、limit、文本或 JSON 输出
关键耦合点:AI 调用失败时会回退机械候选,保证可用性但会造成“方法参数是 ai、结果可能是机械降级”的语义差异。
4) 关键设计决策与取舍
决策 A:Orphan 复用 doctor 算法,而非命令内重写
- 选中方案:
doctorFindOrphanedIssues变量默认绑定doctor.FindOrphanedIssues - 好处:规则单一来源,
bd doctor与bd orphans不漂移 - 代价:命令自治性降低;doctor 行为变更会传导到此命令
决策 B:Exact duplicate 用严格内容键,而非模糊匹配
- 选中方案:
contentKey全字段精确匹配 - 好处:解释简单,适合自动合并
- 代价:召回率有限(空白差异、大小写差异都会漏)
决策 C:Merge target 先看结构连接,再看文本引用
- 选中方案:
dependent+dependsOn优先,textRefs次之 - 好处:优先保留依赖图“枢纽节点”,减少图断裂风险
- 代价:某些“文本语义上更权威”的 issue 可能不被选为目标
决策 D:AI 路径采用“机械预筛 + LLM 复核”
- 选中方案:分层调用,带候选上限与分批
- 好处:成本可控、响应时间可控
- 代价:可能漏掉机械分低但语义近的边界样本
决策 E:CLI 工具偏可用性,允许降级与部分成功
- 体现:AI 失败回退机械;
performMerge非事务 - 好处:命令尽量“有结果”
- 代价:一致性需要操作者复核
errors与输出Method
5) 子模块深度解析
为了更深入地理解每个命令的实现细节和设计意图,请参考以下子模块文档:
-
orphan_detection_command
聚焦bd orphans的适配层实现:如何把全局store包装成types.IssueProvider,如何复用 doctor 检测逻辑,以及--fix如何通过子进程调用bd close完成收口。包含orphanIssueOutput和doltStoreProvider的完整解析。 -
exact_duplicate_detection_and_merge
聚焦bd duplicates的精确分组、打分与自动合并执行链路,解释contentKey与issueScore背后的策略,以及无事务写入的工程取舍。详细说明了合并目标选择算法和批量操作的实现。 -
semantic_duplicate_detection
聚焦bd find-duplicates的机械算法与 AI 复核协同机制,包含阈值、候选裁剪、批处理、telemetry、回退策略等关键实现细节。深入解析了duplicatePair结构和两种相似度计算方法。
跨模块依赖与边界
本模块与以下模块存在直接耦合:
- Core Domain Types:依赖
types.Issue,types.IssueFilter,types.Dependency等核心契约 - Storage Interfaces:通过
store使用SearchIssues/GetConfig/GetDependencyCounts/CloseIssue/AddDependency - CLI Doctor Commands:
bd orphans复用doctor.FindOrphanedIssues - Configuration:
find-duplicates读取config.DefaultAIModel() - Telemetry:AI 路径通过
telemetry.Tracer打点
如果上游契约变化,会坏在哪里?
- 若
types.Issue字段语义变化(比如文本字段更名),重复检测会直接失真。 - 若
Storage的依赖计数或写入语义变化,chooseMergeTarget与performMerge的行为会偏移。 - 若 doctor 的 orphan 判定规则改变,
bd orphans输出会同步变化(这通常是预期,但要注意回归测试)。
新贡献者必看:隐式契约与坑
bd duplicates的描述提到“按状态分组”,但当前入口先过滤 closed;实际主要处理非 closed。修改前请先统一产品语义与帮助文案。performMerge不是事务:可能出现“已 close,未 add dependency”的部分成功。自动化脚本必须检查errors。bd orphans --fix是 shell-out 到bd close,不是函数内直接复用 close 逻辑;任何 close 命令行为变化都会波及这里。find-duplicates --method ai可能因 API/解析失败回退到机械结果;判断结果来源请看每条duplicatePair.Method。findMechanicalDuplicates是O(n²),仓库规模大时要谨慎阈值与--limit设置。
实战建议
- 治理顺序建议:先
bd orphans收状态,再bd duplicates收精确重复,最后bd find-duplicates做语义巡检。 - 批量自动化优先 JSON 输出:
--json下游更容易做审计与回滚策略。 - 做功能扩展时尽量保持现有三层边界:检测策略与CLI 编排分离,避免把输出/交互逻辑混进核心判定函数。