十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Beads 的 `bd create --graph`:用一份 JSON 计划原子化创建整张 Issue 依赖图

Beads 的 `bd create --graph`:用一份 JSON 计划原子化创建整张 Issue 依赖图 Beads 的bd create --graph用一份 JSON 计划原子化创建整张 Issue 依赖图【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beadsbd create --graph plan.json是 Beads 中一次创建一整张 Issue 依赖图的高阶入口它把多个节点、父子层级、阻塞边、扇出门fanout gate等全部声明在一份 JSON 计划文件中然后在单个事务里原子化落地要么全部创建成功、要么全部回滚。本文以 计划文件 schema 文档 为骨架结合 graph_apply.go 的源码实现完整讲解计划文件的结构、全部字段语义、校验规则、干跑预览与源码级落地原理读完即可动手编写可运行、可复用的计划文件。--graph的定位单条 Issue、Markdown 批量与图计划的三种入口bd create是 Beads 的建单命令源码中它同时承载三种输入形态create.go入口命令形态适用场景单条创建bd create 标题手工建一条 issue可用--parent、--deps、--waits-for等单条 flagMarkdown 批量bd create --file issues.md从 Markdown 模板批量生成多条 issue图计划bd create --graph plan.json从 JSON 计划一次性创建带依赖关系的整张图原子提交--graphflag 在 create.go 注册命令行语义为Create a graph of issues with dependencies from JSON plan file。当指定--graph时位置参数标题不再允许同时出现代码会明确拒绝create.gocannot specify both title and --graph flag与--graph兼容的计划级 flag 主要有Flag含义--dry-run只解析、校验并输出预演不写入任何数据--ephemeral计划级默认所有节点落在 wisp 平面可被单个节点覆盖--no-history计划级默认所有节点不写历史可被单个节点覆盖--force允许显式id使用非数据库前缀--ephemeral与--no-history互斥由GraphApplyOptions.Validate()强制执行graph_apply.go单条 create 的其它 flag如--title、--assignee、--waits-for等在图计划模式下会被rejectSingleIssueFlagsForGraph拒绝因为它们的语义已由计划文件内的字段承担。计划文件的三层结构commit_message / nodes / edges计划文件是一个 JSON 对象对应源码中的GraphApplyPlan结构体graph_apply.go只含三个顶层键{ commit_message: optional Dolt commit message, nodes: [ ... ], edges: [ ... ] }commit_message可选的 Dolt 提交信息缺省时自动生成bd: graph-apply N nodesN 为节点数。nodes必填至少一个节点否则校验报plan has no nodes。edges可选的依赖边数组。官方文档中的完整示例即帮助补充文档 create_graph_plan.md 内的示例如下{ commit_message: optional Dolt commit message, nodes: [ { key: api, title: Design the API, type: task, description: …, design: …, acceptance_criteria: …, notes: …, status: in_progress, priority: 1, assignee: alice, assign_after_create: false, owner: aliceexample.com, labels: [backend], estimated_minutes: 45, due_at: 2030-01-02T15:04:05Z, defer_until: 2030-01-01T00:00:00Z, spec_id: bd-spec1, external_ref: gh-42, metadata: {any: json, count: 3}, metadata_refs: {tracker: impl}, id: bd-a1b2c3, ephemeral: false, no_history: false, wisp_type: heartbeat, mol_type: swarm, pinned: false }, { key: impl, title: Implement it, parent_key: api }, { key: gate, title: Fanout gate }, { key: launch, title: Launch announced, type: event, event_kind: agent.started, actor: agent://a, target: bead://b, payload: {} } ], edges: [ { from_key: gate, to_key: api, type: blocks }, { from_key: gate, to_key: impl, type: waits-for, gate: any-children, spawner_key: impl } ] }节点字段名与 issue 模型的 JSON tag 一一对应——即bd show --json输出所用的同一套名字单条bd create能设置的每个字段在计划里都可寻址另外还多了status与pinned两个单条创建没有 flag 的字段。这一点在源码里有专门注释保证GraphApplyNode的字段声明明确写着Field names follow the types.Issue JSON tags (the namesbd show --jsonemits), covering every field single-issuebd createcan set plus initial status and pinnedgraph_apply.go。nodes 字段全解每个节点对应GraphApplyNode结构体graph_apply.go下表按语义分组列出全部字段字段类型语义与约束keystring计划内局部符号名必填且唯一创建成功后结果会把每个 key 映射到铸造出的 issue IDidstring可选显式钉住 issue ID必须匹配数据库前缀同bd create --id规则且该 ID 不能已存在titlestring标题必填校验报empty titletypestringissue 类型缺省task内建bug\|feature\|task\|epic\|chore\|decision\|spike\|story\|milestone自定义类型需types.custom配置statusstring初始状态缺省open当defer_until在未来时缺省为deferreddescription/design/acceptance_criteria/notesstring正文类长文本spec_idstring关联规范文档 IDexternal_refstring外部引用如gh-42assigneestring处理人配合assign_after_create: false默认在创建时直接写入true则创建成功后再延迟指派ownerstring所有者priorityint优先级缺省2P2源码中Priority为指针nil 时graphApplyNodeIssue回落到 2graph_apply.goestimated_minutes/estimateint预估分钟数estimate是estimated_minutes的别名两者同时出现时以规范字段canonicalestimated_minutes为准负值会被拒绝due_at/defer_untilstringRFC3339 时间戳不接受6h这类相对时间labels[]string标签数组metadataobject任意 JSON 值metadata_refsmap[string]string值为其它节点的key创建后会被替换为对应铸造出的 ID 写入 metadataparent/parent_key/parent_idstring建立parent-child依赖parent是parent_key的别名规范字段优先deps[]{type, target}内联依赖target先按计划 key 解析再按字面 issue ID 处理ephemeral/no_historybool按节点覆盖计划级--ephemeral/--no-history每个节点内两者互斥storage_classstring显式存储类ephemeral 路由到 wisp 平面wisp_type/mol_typestring节点在 wisp / molecule 平面中的类型pinnedbool是否置顶单条 create 无对应 flagevent_kind/actor/target/payloadstring事件节点专用仅在type: event时合法key 与 id符号名与显式 ID 的取舍key是计划文件内部的坐标系统边、父子关系、metadata_refs都用它来指代节点最终由执行器统一映射为真实 issue ID结果以map[key]ID返回。若需要可预测的稳定 ID可以用id显式钉住——但它必须满足两条硬约束前缀匹配与bd create --id一样ID 必须以数据库前缀或allowed_prefixes中的前缀开头除非带--force。前缀校验由validateGraphApplyExplicitIDPrefixes对每个计划节点执行graph_apply.go。必须不存在计划永远不覆盖已有 issue。源码专门实现了validateGraphApplyExplicitIDCollisionsgraph_apply.go在插入前预检每个显式 ID 是否已被占用——因为图创建走的是原子批插入若允许重复 ID一次创建就可能静默改写既有 issue。该函数注释明确--force只担保外部前缀不担保覆盖已有数据碰撞检查刻意不被--force绕过。parent / parent_key / parent_id层级依赖parent_key或别名parent指向计划内另一个节点的 keyparent_id指向一个已存在的 issue ID。两者都会在被创建节点上附加一条parent-child依赖。源码中effectiveParentKey()graph_apply.go统一了别名解析parent_key优先其次parent所有消费方校验、干跑、落地都走这一入口保证别名在任何路径上都不会丢失。需要注意与单条bd create --parent的差异图节点不会继承父节点的 labels并且获得的是扁平非层级式ID。计划是显式产物——你要什么它就给什么不做隐式继承。create_deps_atomic_test.go等测试专门覆盖了别名解析与未知 key 拒绝如TestValidateGraphApplyPlanParentAliasResolvesCorrectly、TestValidateGraphApplyPlanParentAliasRejectsUnknownKey。status / pinned单条 create 没有 flag 的两个字段status直接设置初始状态缺省逻辑为未指定 →opendefer_until在未来 →deferred显式创建为closed的节点closed_at会被自动回填时间戳一律 RFC33396h这类相对时间在计划里不被接受。源码在graphApplyNodeIssue中额外回填了两个状态耦合时间戳graph_apply.go状态为closed且无closed_at时补当前 UTC 时间状态为in_progress且无started_at时同样补记。ephemeral / no_history / storage_class逐节点存储类三个字段共同决定节点落哪个存储平面。优先级规则注释中引用 Protocol v0.1 C1.3为节点显式storage_classstorage-class.type配置默认 未设置graph_apply.go。几个关键行为storage_class: ephemeral是把ephemeral: true拼写全的等价形式节点路由到 wisp 平面ephemeral/no_history按节点覆盖计划级 flag二者在单个节点内互斥显式 durable 类versioned/unversioned与生效的 wisp 平面冲突时直接报错拒绝而不是静默抹掉——这是flag 优先于配置的体现显式意图必须被保留只有配置级默认才让位于生效平面wisp_type与mol_type有各自合法的取值枚举非法值在validateGraphApplyNodeFields中被拒绝。metadata 与 metadata_refs带符号引用的元数据metadata接受任意 JSONmetadata_refs的值写的是其它节点的 key。执行器在批量创建拿到全部真实 ID 之后用types.MergeMetadataRefs把引用替换为铸造 ID 再写回graph_apply.go。校验期会预检每个 ref 指向的 key 必须存在于计划中metadata ref %q references unknown key。event 类型节点event_kind、actor、target、payload四个字段必须与type: event同时出现否则校验直接拒绝graph_apply.go。示例中的agent.started事件即用type: event声明。edges 字段全解边数组对应GraphApplyEdge结构体graph_apply.go字段类型语义与约束from_key/from_idstring起点key 指向计划内节点id 指向已存在 issue二者至少填一个to_key/to_idstring终点同上至少填一个typestring依赖类型缺省blocksgatestring仅waits-for边all-children或any-childrenspawner_key/spawner_idstring仅waits-for边标注扇出门的孵化节点thread_idstring对话线程 ID用于replies-to类会话边type 缺省与合法值type缺省为blocks源码graphApplyDependencyType对空串回落DepBlocksgraph_apply.go。内建依赖类型定义在 internal/types/types.go其中影响就绪度计算的类型AffectsReadyWork是blocks、parent-child、conditional-blocks、waits-for四类此外还有related、discovered-from、replies-to、relates-to、duplicates、supersedes、tracks、until、caused-by、validates、delegated-from等关联/链接类边。校验接受任意非空且长度不超过 32 的字符串允许自定义依赖类型但边上的gate/spawner字段只对waits-for生效。gate 与 spawner扇出门元数据waits-for边承载扇出门语义元数据由WaitsForMeta定义internal/types/types.go。四条硬约束值得注意gate只能取all-children等全部子节点完成或any-children等第一个子节点完成源码注释标注为 future 能力非法值被IsValidWaitsForGate拒绝waits-for的to 端点本身就是孵化节点spawner——门求值盯的是它的子节点因此spawner_key必须等于to_key、spawner_id必须等于to_id它们的存在是为了让计划自文档化由于显式to_id在落地时会覆盖to_key作为目标resolveEdgeRef逻辑id 优先于 keygraph_apply.go所以spawner_key不能与to_id组合——此时请改用spawner_idspawner_key与spawner_id本身互斥不能同时出现。这与单条命令的--waits-for/--waits-for-gateflag 语义完全镜像源码注释明确说明这一点。与父子关系的冲突约束parent_key/parent_id在节点上已经创建parent-child边因此不要在edges里重复声明它们。校验规则graph_apply.go影响就绪度的边blocks、waits-for、conditional-blocks等不得重复一条parent-child关系也不得反向一条parent-child关系即父→子方向的 ready-work 边父节点不得通过这类边连回自己的子节点。落地时边按父子优先两阶段插入先落parent-child再分两轮落显式边与内联depsgraph_apply.go保证每条阻塞边在存储中都看得到计划的完整层级。deps节点内联依赖除了顶层edges每个节点还能带deps: [{type: ..., target: key-or-id}]。type缺省为blockstarget先按计划 key 解析、再按字面 issue ID 处理GraphApplyNodeDep注释。若字段拼写不熟悉源码还内置了纠错提示例如想写blocks数组时会提示改用顶层edges或节点depsgraphFieldHints映射graph_apply.go。源码视角计划如何被解析、校验并原子化落地整个流程的入口是createIssuesFromGraphgraph_apply.go分四步读取并预检未知字段detectUnknownGraphFields扫描计划 JSON对照从结构体 JSON tag 反射生成的已知字段集合jsonTagSet从源码派生所以不会漂移未知字段会向 stderr 输出警告和纠错 hint随后被静默丢弃严格解析json.Unmarshal到GraphApplyPlan全量校验validateFullGraphPlan串起节点校验、存储类校验、显式 ID 前缀校验与碰撞预检执行或干跑非 dry-run 时进入executeGraphApply。校验管线validateFullGraphPlangraph_apply.go把计划级检查收拢在一处保证 embedded 与 proxied 两条路径不可能各自漏检validateGraphApplyPlankey 非空且唯一、title 非空、metadata_refs与parent_key指向已知 key、deps的 target 非空、依赖类型合法、事件字段只随typeevent出现、节点级 issue 模型校验issue.ValidateWithCustomvalidateGraphApplyStorageClasses解析每个节点的生效存储类冲突在校验期暴露而不是落地中途validateGraphApplyExplicitIDPrefixes逐节点执行bd create --id同款前缀检查validateGraphApplyExplicitIDCollisions显式 ID 不覆盖已有 issuevalidateGraphApplyLocalCycles对纯计划内的阻塞依赖做内存环检测报出确定性的成环节点。原子落地executeGraphApplygraph_apply.go把一切包进store.RunInTransaction批量创建全部 issue → 解析metadata_refs写回 → 预检计划内父→子阻塞路径与计划内阻塞环validateGraphApplyPlannedParentBlockingPaths/validateGraphApplyPlannedBlockingCycles→ 两阶段插入父子边与显式边/内联依赖 →CycleThroughEdges最终整图环检测 → 最后应用延迟指派assign_after_create。任一环节失败整个事务回滚不会出现建了一半的中间态。节点字段到 issue 的物化走graphApplyNodeIssuegraph_apply.go它复用单条bd create的createIssueParams/buildCreateIssue路径——这正是文档所说单条 create 能设置的每个字段计划都能寻址的实现保证计划节点与单条命令在字段级共享同一套物化逻辑行为不会漂移。--dry-run不落地的预演bd create --graph plan.json --dry-run只解析、校验并输出预演不产生任何写入。预演输出GraphApplyDryRun/emitGraphApplyDryRungraph_apply.go包含node_count、edge_count、parent_deps汇总逐节点行key [type] P优先级 title并附id、status、parent_key/parent_id等附加信息--json输出时给出结构化的GraphApplyDryRunJSON固定附注dry-run validates the graph structure only; live create may still reject parent-child blocking paths after resolving stored dependencies——即干跑只验证图结构真实创建仍可能因已存储依赖解析出新的父子阻塞路径而拒绝。测试TestCreateIssuesFromGraph_DryRunDoesNotPersist专门验证干跑不落盘TestEmitGraphApplyDryRun_Counts/TestEmitGraphApplyDryRun_JSON验证预演行与 JSON 输出。两种后端embedded 与 proxied 的统一约束Beads 支持 embedded 直连与 proxied-server 两种运行形态。图计划在两条路径上共享同一套GraphApplyPlan解析与校验embedded 路径create.go直接读 flag 组装GraphApplyOptionsgraphApplyOptionsFromFlagscreate_input.go走createIssuesFromGraphproxied 路径create_proxied_server.go通过gatherCreateInput收集输入validateProxiedGraphPlan在事务内完成校验碰撞预检在事务内执行避免并发创建同一显式 ID 的竞态然后按解析出的存储类调用ApplyIssueGraph或ApplyWispGraphcreate_proxied_server.go。关键差异在存储类约束proxied 模式下整个计划必须使用统一的存储类整张图路由到同一张表混合 durable/wisp 节点的计划会被拒绝requireUniform参数graph_apply.go。--ephemeral/--no-history计划级 flag 与逐节点覆盖都需要遵守这一统一性。边界与限制计划不能表达什么计划文件是创建类操作的显式声明以下内容不能从计划创建Comments计划不能创建评论需在创建后使用bd comments add补充没有bd createflag 的模型字段同样不可设置sender、work_type、is_template、await_*、bonded_from、source_*等相对时间如6h不接受时间戳一律 RFC3339计划不继承父标签、不做隐式扁平化外的任何贴心行为——一切以计划字段为准。测试与文档如何防止 schema 漂移计划 schema 的可信度由三层机制共同保证文档即源码create_graph_plan.md通过//go:embed编译进二进制help_supplements.go作为bd create命令帮助的补充章节bd help --docs-root及按命令生成的文档树都会附上任何改动都必须重新生成文档漂移门禁保持字节级一致示例即测试TestDocsGraphPlanExampleValidatesgraph_apply_test.go会从嵌入的文档中取出 JSON 示例执行未知字段检测、严格解析、计划校验与存储类校验——文档里的示例如果失效测试直接失败已知字段集合由反射派生knownGraphPlanFields/knownGraphNodeFields/knownGraphEdgeFields直接从结构体 JSON tag 生成schema 声明与实际解析永远同源。快速上手三步写出第一份图计划起草计划按上文nodesedges结构写 JSON用key组织符号名父子关系用parent_key阻塞/扇出用edges干跑验证bd create --graph plan.json --dry-run检查节点数、优先级、状态与父子统计是否符合预期正式创建bd create --graph plan.json成功后输出Created N issues及逐条key - ID映射需要机器可读结果时加--json。计划创建的每个 issue 都可用bd show --json查看其字段名与计划节点字段同源同一套 JSON tag这让计划 → 结果 → 复查形成闭环。【免费下载链接】beadsBeads - A memory upgrade for your coding agent项目地址: https://gitcode.com/GitHub_Trending/beads1/beads创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表