FiftyOne 仓库 AI 编码代理开发指南:Python 核心与 React App 的双栈协作规范全解析
【免费下载链接】fiftyoneRefine high-quality datasets and visual AI models项目地址: https://gitcode.com/GitHub_Trending/fi/fiftyone
FiftyOne 是一个"Python 核心(fiftyone/)+ React 应用(app/)"的双栈仓库,其 AGENTS.md 与 app/AGENTS.md 是为 AI 编码代理(如 Claude、Copilot 等)量身编写的仓库级操作指引:进入仓库后先读什么、改动哪一侧代码遵守哪份规范、新增 UI 用什么组件库。读完本文,你将掌握该仓库对 Python 后端与 TypeScript 前端的全部强制约束(行宽、导入分组、docstring、VOODO 组件优先、MUI 迁移冻结、状态管理分层等),并能据此写出风格一致、可通过 CI 检查的提交。
一、进入仓库的第一件事:AGENTS.md 教你如何"认路"
根目录 AGENTS.md 全文虽短,却是整个仓库协作规则的入口。它给出了三条决定性指令:
- FiftyOne 由Python 核心(
fiftyone/)与React App(app/)两部分组成,任何改动先分清归属; - 改动
fiftyone/下的任何内容:必须阅读 STYLE_GUIDE.md; - 改动
app/下的任何内容:必须先读 app/AGENTS.md,并把 app/CODING_STANDARDS.md 视为具有约束力(binding)的规范;同时明确要求新 App UI 使用 VOODO(@voxel51/voodo),不得新增 Material UI。
从仓库布局看,这条"认路"指令与目录结构完全对应:fiftyone/下是core/(数据集、视图、标签、聚合等核心实现)、utils/、operators/、server/、plugins/等 Python 包;app/则是包含packages/core、packages/state、packages/components等十几个子包的 TypeScript monorepo。两条规范线(Python 风格 vs App 编码标准)彼此独立、互不越界,这正是双栈仓库最需要 AI 代理遵守的纪律。
二、Python 侧规范:STYLE_GUIDE.md 的核心约束
STYLE_GUIDE.md 规定了 FiftyOne 主要语言(Python、TypeScript、RST、Markdown)的风格。对fiftyone/的改动,以下 Python 约束是硬性的:
格式化与行宽
- 最大行宽79 字符(不可拆分的超长 URL 除外);
- 使用4 空格缩进,禁止 Tab;
- 删除所有行尾空白;
- 顶层定义之间空两行,类方法之间空一行。
导入分组(Import 顺序)导入必须位于模块 docstring 之后,按"最通用 → 最不通用"分组,组间空一行、组内按完整包路径字母序排序:
- 标准库(最通用);
- 第三方依赖(如
cv2、numpy); - Voxel51 出品但非 FiftyOne 的包(如
eta.core.*); - FiftyOne 自身模块(最不通用)。
核心 FiftyOne 模块按fox缩写规则导入(x为模块首字母,首字母冲突时用foxy区分),例如import fiftyone.core.labels as fol、import fiftyone.core.media as fom。STYLE_GUIDE 给出的完整示例:
import os import sys import cv2 import numpy as np import eta.core.image as etai import eta.core.video as etav import fiftyone as fo from fiftyone.core.document import Document import fiftyone.core.labels as fol import fiftyone.core.media as fom命名与私有化
- 命名遵循
module_name、ClassName、function_name、GLOBAL_CONSTANT_NAME、instance_var_name、function_parameter_name等约定; - 所有私有变量、常量、函数、方法、类都必须以
_开头; - 无基类的类显式继承
object。
Docstring 与文档系统联动公共类与方法的 docstring 会被 Sphinx + Sphinx-Napoleon 自动收录进 docs,因此必须使用 Sphinx 构造(:ref:、:class:、:func:、.. note::)并遵守 Google 风格、不使用类型注解。函数 docstring 需包含Args:(带默认值如tolerance (2))、Returns:、Raises:等段落;类 docstring 直接记录__init__()的参数。
日志与异常使用标准库logging,按logger = logging.getLogger(__name__)获取模块级 logger,用debug/info/warning输出日志;错误一律用原生异常raise Exception(...)抛出,而不是logger.error();循环中需要"只提示一次"的警告使用warnings.warn()。
工具链落地Python 代码经 pre-commit hooks 用black和pylint自动格式化与检查:black 不自定义([tool.black]仅在 pyproject.toml 中保留有限配置),pylint 消息的永久禁用写入 pylintrc 的disable字段,局部禁用使用# pylint: disable=rule行内注释。
三、App 侧的第一原则:VOODO 优先,MUI 是历史包袱
app/AGENTS.md 开宗明义:App UI 用@voxel51/voodo(Voxel51 的组件库)构建,永远优先找 VOODO。原因很直接——该代码库大量代码早于 VOODO 诞生、是用 Material UI 写的,因此"模仿周围代码的写法"恰恰会产出 MUI,而这正是团队正在迁离的方向。周边代码不是可靠的风格指南,这是 AGENTS.md 对 AI 代理最反直觉也最重要的一条提醒。
从 app/package.json 可以看到@voxel51/voodo: "^0.1.0"被声明在根依赖中,且packages/core、packages/state、packages/components、packages/multimodal、packages/playback等 12 个子包都以catalog:或*方式引用它,印证了 VOODO 已铺到整个 App 面。
如何确认 VOODO 是否有某组件:不要靠猜,直接查已安装包的真实导出。在app/目录下执行:
grep -F 'export * from' node_modules/@voxel51/voodo/dist/components/index.d.ts注意两点:barrel 文件列出的是模块路径而非导出名,导入前必须到对应组件目录自己的.d.ts里确认精确标识符——例如Datepicker/目录导出的是DatePicker,而Slider/导出SingleValueSlider与MultiValueSlider。这份声明文件对实际安装的版本是权威的,因为 VOODO 的组件集在不同版本间会变化。
样式 token 的使用:使用 VOODO 导出的 tokens 与枚举,禁止硬编码var(--...)字符串,禁止使用字符串字面量:
import { Text, TextVariant, TextColor, Icon, IconName, Size } from "@voxel51/voodo"; <Text variant={TextVariant.Md} color={TextColor.Fg}>{label}</Text> <Icon name={IconName.CaretDown} size={Size.Sm} color={TextColor.Secondary} />兜底规则:如果确实需要 VOODO 没有的组件,才可以用 MUI,且必须在 PR 描述中说明命中了哪个缺口,绝不悄悄替换。
四、MUI 迁移冻结:一条"只缩不增"的 ESLint 白名单
VOODO 优先不是一句口号,而是被 ESLint 规则机械强制执行的。app/CODING_STANDARDS.md 把 MUI 使用分成两档:
Tier 1:禁止(forbidden)——VOODO 有等价物时,新代码/重写代码不得用 MUI,且没有"赶发布"豁免。文档给出一张完整的替换对照表,核心映射如下(节选):
禁用的@mui/material导入 | 必须替换为@voxel51/voodo |
|---|---|
Typography | Text、Heading |
Box、Stack、Grid | Stack |
Button | Button、RichButton |
IconButton | Clickable |
TextField、OutlinedInput | Input/TextArea(配FormField承载 label/error) |
Select、Autocomplete | Select、Dropdown |
Menu、MenuList、MenuItem、Popover | ContextMenu、Dropdown |
Card、Paper系列 | Card |
Chip | Pill、TextBadge |
CircularProgress等加载类 | Spinner |
Slider | SingleValueSlider、MultiValueSlider |
Snackbar | Toast、ActivityToast |
任意@mui/icons-material图标 | Icon+IconName |
注意同名陷阱:Stack、Button、Tooltip在两个库里都存在,务必确认 import 解析到的是哪个。且部分替换是"近似而非直接替换":Stack是一维 flexbox(真正的二维布局可用div+ CSS grid,但不用 MUIGrid);Dropdown/ContextMenu是菜单而非通用锚定 popover;Select支持 typeahead 与多选但不支持异步加载。迁移时必须保留原有的布局、交互与无障碍行为。
Tier 2:不鼓励(discouraged)——VOODO 暂无等价物的已知缺口:Alert/AlertTitle、Dialog系列与Modal、Tabs、Link、MUI 主题工具(useTheme、styled、sx)。这些允许"为了发布继续使用 MUI",但应尽量用 VOODO 原语组合,并向 VOODO 维护者反馈缺口、在 PR 中说明。
落地机制:Tier 2 的使用仍会触发 app/.eslintrc.js 中no-restricted-imports的警告——这是预期且可接受的,不要为了消音把文件加进 app/.mui-allowlist.txt。该白名单只为冻结开始前(2026-08-21 快照)遗留的 MUI 文件服务,它的定位是只缩不增:每迁移一个文件就从名单里删掉一行,名单清空之日就是 MUI 彻底退出之时。
审查口径:存量 MUI 不算违规,code review 时只标记本次变更新增的@mui/*导入或新增的 MUI 组件用法;仅修改 prop、移动/重排 import 的存量维护不算违规。存量 MUI 代码也不要"顺手重构"——除非你本来就在重写该组件。
五、冻结规则的源码级实现:.eslintrc.js 的双重白名单
app/.eslintrc.js 把上述规则落成了可运行的代码,它同时管理两条冻结线:
- Recoil → Jotai 迁移冻结:
recoil与recoil-relay的新用法被no-restricted-imports拦截,白名单是 app/.recoil-allowlist.txt; - MUI → Voodoo 迁移冻结:拦截
@mui/icons-material、@mui/icons-material/*、@mui/material、@mui/material/*全部入口,白名单是 app/.mui-allowlist.txt。
实现上有两个值得注意的细节:
- 两条冻结共用同一个规则名,而 ESLint 的
overrides替换规则配置而非合并,因此 app/.eslintrc.js 把两份配置分开维护,让 override 只重新应用仍生效的那一条:只在 MUI 白名单上的文件重新声明 Recoil 冻结,只在 Recoil 白名单上的文件重新声明 MUI 冻结,同时命中两个名单的文件则对两条冻结都豁免(bothAllowlist)。 - 冻结拦截的是导入路径而非组件使用,这正是 CODING_STANDARDS 强调"从
@mui/system、@mui/base、@mui/lab导入同一原语仍是 Material UI"的原因——规则必须封死所有@mui/*入口,否则换一个子包导入即可绕过。
此外,app/.eslintrc.js 还配置了only-warn插件(把错误降级为警告、分阶段清零)、React/React Hooks/TypeScript/Prettier 推荐规则,以及针对packages/looker-3d/**的 react-three-fiber 专有属性豁免(attach、rotation、args等 three.js 对象属性可作 JSX props)。
六、TypeScript 纪律:严格类型,不留抑制
app/AGENTS.md 对 App 侧 TypeScript 的要求非常明确:新代码必须严格类型化——不允许any;不允许无注释的@ts-ignore、@ts-expect-error、eslint-disable;确有必要时必须附带解释原因的注释。这与 ESLint 配置中@typescript-eslint/no-unused-vars对_前缀参数的豁免(argsIgnorePattern: "^_")配合,形成"严格但可表达"的写作环境。
七、App 状态管理规范:CODING_STANDARDS.md 的分层模型
app/CODING_STANDARDS.md 是 App 侧第二份约束力文档,其状态管理部分贯彻"least capability"(最小能力)原则:能用简单方案就不用复杂方案。
状态选型三档
- 本地状态:仅 UI 用、随组件销毁重置 →
useState、useReducer; - Context API:小而受限的组件树、静态数据 →
useContext(context 保持最小,避免多余重渲染); - Atoms:响应式全局状态 →Jotai(首选)或Recoil(遗留)。
Atom 四条铁律
- 绝不直接导出 atoms,把 atom 视为实现细节;
- 只导出领域 hooks来读写 atom,组件内禁止裸用
useAtomValue、useSetAtomValue、useRecoilValue、useSetRecoilValue; - 领域 hook 命名约定:
use<Feature>()为读取 API、必须幂等(如useLighter()、useTimeline());use<Feature><Action>()或use<Action><Feature>()为命令、可带副作用(如useCreateTimeline()、useLighterSetup()); - 文件布局固定:
packages/<domain>/model/atoms.ts(不导出)packages/<domain>/model/selectors.ts(不导出)packages/<domain>/hooks.ts(导出)packages/<domain>/bridge.ts(可选,供 JS 互操作)
从源码看,该规范已在仓库落地:app/packages/state/src/jotai/下真实存在jotai-store.ts、modal.ts、modalBridge.ts、group-annotation.ts等文件,其中modalBridge.ts正是"bridge 用于非 React 访问"的实例。JS 互操作要求使用显式命名的 bridge API(如lighterBridge、annotationBridge);如果必须把 atom 暴露到全局,前缀__unsafe(如__unsafeGlobalFeatureAtom)。
八、AI 编码代理实操清单:一次合规提交的检查顺序
综合两份 AGENTS.md 与配套规范,一个 AI 编码代理在提交前应依次自检:
- 判断归属:改动在
fiftyone/还是app/?前者遵循 STYLE_GUIDE.md,后者必读 app/AGENTS.md 与 app/CODING_STANDARDS.md; - Python 侧:79 字符行宽、4 空格缩进、导入四组排序、
_私有前缀、Sphinx docstring、原生异常而非logger.error; - App UI 侧:先查 VOODO(
grep -F 'export * from' node_modules/@voxel51/voodo/dist/components/index.d.ts),确认导出名后再导入;用 VOODO tokens/enums 而非var(--...);无 VOODO 等价物才用 MUI,并在 PR 描述说明缺口; - 冻结规则:新
@mui/*导入会被 app/.eslintrc.js 的no-restricted-imports警告;不要往 app/.mui-allowlist.txt 或 app/.recoil-allowlist.txt 加文件来消音——这两份名单只缩不增; - TypeScript:无
any、无裸抑制注释;新状态走atoms.ts+selectors.ts+hooks.ts布局,组件只消费领域 hooks; - 迁移纪律:不要"顺手"改写存量 MUI/Recoil 代码;真正迁移某文件时,把它从对应白名单中删除。
这套规范的价值在于:它把"风格一致性"从约定俗成变成了可被 lint 强制、可被 review 审计、可被白名单追踪的工程事实。对任何要在这个双栈仓库中长期工作的 AI 代理或人类开发者而言,按上述顺序执行,即可避免"提交被 CI 拦下、review 反复打回"的常见返工路径。
【免费下载链接】fiftyoneRefine high-quality datasets and visual AI models项目地址: https://gitcode.com/GitHub_Trending/fi/fiftyone
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考