text-to-cad 仓库工程实战:Agent Skill 开发的分支布局、发布管线与 CAD Viewer 调试全解
【免费下载链接】text-to-cadA library of agent skills for CAD, CAE and CAM项目地址: https://gitcode.com/GitHub_Trending/tex/text-to-cad
text-to-cad 是一个面向 CAD、CAE、CAM 的 Agent Skill 库(当前规范版本见 VERSION),仓库根目录本身即 Agent 插件包。本文以仓库工程约定文件 AGENTS.md(由 CLAUDE.md 以@AGENTS.md指令引入)为骨架,系统拆解其分支与目录布局、以 GitHub Actions 为中心的单次发布工作流、Skill 自包含的边界规则、检查体系,以及 CAD Viewer 的本地调试方法;所有结论均以仓库内脚本、配置与测试为证据,读者可据此直接开展分支开发、跑通发布流水线并在本地把 CAD Viewer 跑起来。
仓库定位:一个“以 Skill 为产品”的 Workbench
仓库把skills/视为产品本体(每个目录是一份可独立安装的 Agent Skill),把models/视为共享的 fixture/artifact 区域(STEP、STL、GLB、DXF、URDF、SRDF、SDF 等样本与生成产物统一落在这里)。围绕这两块核心,仓库还维护了:
viewer/:可编辑的 CAD Viewer 应用源码,作为本地文件系统应用被逐字镜像到独立的 cad-viewer 仓库;packages/cadjs:与 UI 框架无关的共享 JS CAD / 渲染 / 运行时代码;packages/implicitjs:独立的自包含 implicit CAD 模型、着色器渲染、快照、网格采样与导出运行时;packages/cadgen:共享的 Python STEP/GLB/topology 工件生成代码;docs/、tests/、scripts/:文档站、根级测试套件与长期有效的仓库命令。
从文件组织可以看出两条硬约束:共享运行时一律放packages/作为唯一事实源,再由打包流程 vendor 进各 Skill 的运行时目录;models/是唯一允许写 CAD/机器人描述产物的地方,禁止在仓库其他位置随手创建产物目录。
分支与布局策略:develop 用 symlink 开发,main 只做发布
分支纪律
- 所有开发从
develop分支切出,PR 一律打回develop,禁止从main起步开发; main是纯发布分支:只接受Release工作流的发布提交,禁止向其开 PR 或直接 push;develop分支刻意使用 symlink 布局(跨生成的运行时路径与 viewer 本地包路径),遇到 symlink 时要沿链接追踪并编辑真正的源目标。
两种布局的本质区别
develop(开发布局):把生产打包产物路径以 symlink 指回规范源码,贡献者只需编辑skills/、viewer/、packages/下的一份源码。建立与校验布局使用 scripts/dev/setup-symlinks.sh:
scripts/dev/setup-symlinks.sh # 建立开发 symlink 布局 scripts/dev/setup-symlinks.sh --check # 校验布局无损,不改动文件该脚本内部委托 scripts/dev/setup-skill-symlink.sh 与各 Skill 的setup-*-skill-symlink.sh逐个建立链接。
main(发布布局):必须能被“裸 checkout”直接安装,因此里面是真实的生成产物副本,而非 symlink。把 symlink 替换成真实副本在main上是正确性要求而非惯例。
为什么 symlink 不能进入发布树
不同 Agent 安装器对 symlink 的处理方式互不相同,且其中一种会静默丢文件。这一点在 scripts/github-workflows/check-builds.sh 中有明确注释佐证:
- Skills CLI(
npx skills):把 symlink 解引用为真实文件; - Claude Code 插件安装:原样保留 symlink;
- Codex
plugin add:静默丢弃 symlink 且无任何报错——其copy_dir_recursive只按is_dir()/is_file()分支判断,symlink 条目两个分支都不命中,于是该文件直接缺失,Skill 装上后运行时缺文件。
因此发布树中一旦出现 symlink,check-builds.sh会直接失败(Production bundle paths must not contain symlinks),这是 CI 红线,不可放宽。
main 的“裁剪”语义
仓库根即插件包,main上任何源码路径都会被复制进每次安装,所以发布任务在打包、全部检查通过之后会裁剪发布树:移除models/、viewer/、tests/、docs/、packages/、requirements-dev.txt,让main尽量只保留插件包本身。这些内容并不丢失,因为各自都有“读源码而非读发布树”的消费方:
viewer/的运行时是解引用后的skills/cad-viewer/scripts/viewer副本,镜像仓库同步自发布源提交;docs/与packages/由Deploy Docs工作流从发布源提交构建部署;models/、tests/、requirements-dev.txt只在源码检出时使用。
单次发布的完整管线:Release / Deploy Docs / Sync CAD Viewer
规范版本号的唯一事实源:VERSION
VERSION(当前为0.4.10)是规范发布版本号,普通开发 PR 一律不碰它。所有衍生版本元数据(各package.json/package-lock.json的version字段、.claude-plugin/与.codex-plugin/插件清单版本、packages/cadgen/pyproject.toml等 TOML 的version行)由 scripts/release/sync-version.mjs 从VERSION统一盖章。
值得注意的工程细节:由于 develop 布局下镜像路径与规范路径是 symlink,多个目标可能指向同一个真实文件,sync-version.mjs用mergeTargetsByRealPath按realpath合并目标后再统一写回,避免“后写的镜像目标覆盖掉规范目标独有字段”这类问题——这正是 0.4.10 发布门禁曾因packages/cadjs/package-lock.json被 symlink 覆盖而自锁的教训修复。因此永远不要手工编辑这些重复的版本字段,scripts/release/bump-version.sh与 scripts/bundle/bundle.sh 会负责盖章。
Release 工作流:一次运行完成整个发布
发布只通过单一的ReleaseGitHub Actions 工作流完成,默认参数即真实发布配置:
gh workflow run release.yml --ref develop -f bump=patch一次运行依次完成:在release/<version>分支上 bumpVERSION与衍生元数据 → 打开发布 PR 并立即合入develop→ 执行发布(生产打包、校验、写main)→ 部署文档 → 打 semver tag 与 GitHub Release。关键约束:
- 不要自行选择 semver:请求未指明 patch / minor / major 或精确版本时必须先确认再派发;
- 发布 PR 不等自身 CI;发布任务会对“最终要发布的东西”重新跑完整打包与测试门禁;
- 只有源版本新于
main与最新 semver tag、且源包含上一次发布源提交时才允许发布;写main时以“生成的生产合并提交”叠加在上一发布目标之上、以发布源为第二父提交,既保证main可 fast-forward,又保留源码提交用于 release notes 与贡献者归属; - GitHub Release 默认立即发布;
publish=false可先以草稿审阅。
PyPI 发布 cadgen
发布任务同时把packages/cadgen上传到 PyPI:先校验生产 bundle,再 pushmain之前上传——发布树用 scripts/release/pin-cadgen-requirements.sh 把 editable 依赖行改写为固定的cadgen==<version>(来自 PyPI),因此 PyPI 上传失败必须阻塞发布,否则会发布一个依赖无法解析的main。PyPI 版本恒等于VERSION,上传使用skip-existing,失败重跑幂等可续。首次发布需在 PyPI 为cadgen配置 trusted publisher(GitHub OIDC),无需存储 API token。
bump=none:不是发布设置
bump=none表示“把base_branch原样发布”:不改版本、不开发布 PR、直接进入发布任务。它用于续跑失败发布和对build-test做管线彩排。set_version只用于指定新版本,不是“保持版本不动”的说法。即使bump=none,sync-version.mjs仍会运行,若衍生元数据与VERSION漂移会被拦截走发布 PR 流程。
彩排与续跑
- CI/CD 或构建变更测试:只有用户明确要求测试时才用
target_branch=build-test,并必须搭配bump=none,避免彩排消耗版本号:
gh workflow run release.yml --ref <branch> \ -f bump=none -f base_branch=<branch> -f target_branch=build-testdry_run=true只预览版本变化;auto_merge=false停在“准备发布 PR”这一步。
- 续跑失败发布:任何环节失败(包括
main已前进但 tag 未打)后,用bump=none重跑即可——版本已到base_branch,无需再 bump,直接进发布任务,发布门禁同时处理“main 未动”与“main 已动但缺 tag”两种形态。
Deploy Docs:独立重部署文档站
独立的Deploy Docs工作流不触发发布,只把文档站重新部署到 Vercel 生产:
gh workflow run deploy-docs.yml -f ref=develop它只能部署源码 ref(默认develop),不能部署main——发布树已删掉docs/与packages/,而 docs 应用是依赖根目录packages/构建的(docs/tsconfig.json把cadjs/*映射到../packages/cadjs/src/*)。工作流前置检查这两者并给出明确报错。要重放某次历史发布的站点,用该发布源提交:每次发布提交的第二父提交即发布源,例如git rev-parse <tag>^2。
镜像 CAD Viewer 仓库
viewer/会被发布为独立的 cad-viewer 仓库:Release在发布main之后调用Sync CAD Viewer Repo,从发布源提交(而非不含viewer/的main)镜像。可单独派发:
gh workflow run sync-cad-viewer.yml -f ref=develop gh workflow run sync-cad-viewer.yml -f ref=develop -f dry_run=true该镜像是一次直拷贝:不重写任何路径/命令/文本,唯一结构性变化是把viewer/packages/*解引用为真实目录,脚本拒绝发布仍含 symlink 的树。推送前会在镜像内跑npm ci、npm run test、npm run build、pip install -r requirements.txt与server_py测试,镜像站不住脚则发布失败。它需要CAD_VIEWER_SYNC_TOKEN(对镜像仓库有contents:write)。本地同步/查漂移可用 scripts/viewer/sync-cad-viewer-repo.sh。
本地手动兜底
与工作流同款脚本的本地发布准备:
git fetch origin develop git fetch --tags origin scripts/release/bump-version.sh patch --no-commit node scripts/release/sync-version.mjs scripts/release/check-version.sh --incremented-from origin/main node scripts/release/sync-version.mjs --checkscripts/release/publish-github-release.sh 是 tag 与 GitHub Release 步骤的手动兜底,与工作流不同,它默认创建草稿,除非传--publish。同时建议配置仓库规则集:main拒绝 PR 与直接 push(只留Release发布任务这个唯一写入口)、为[0-9]*.[0-9]*.[0-9]*开启 tag 规则集。
仓库硬规则:Skill 自包含、包边界与产物纪律
Skill 运行时自包含
每条 Skill 在运行时必须自包含且独立:不得引用、导入或依赖另一条 Skill、skills/根目录或仓库根模块的代码。禁止把skills/、仓库根或兄弟 Skill 目录加入sys.path、PYTHONPATH、NODE_PATH等运行时查找路径。共享运行时助手必须放在packages/作为唯一事实源,再通过打包/生成流程 vendor 进各消费 Skill 的运行时;packages/cadgen这类共享 Python 工件也应走捆绑包路径,而非兄弟 Skill 导入。开发 symlink 只是 checkout 布局便利,不改变这条运行时约束。
包间依赖方向
packages/cadjs必须保持可复用、非 React;App 的 UI 与工作流状态属于viewer/;packages/implicitjs必须保持可复用、非 React,且绝不 importcadjs。依赖单向流动:cadjs依赖implicitjs并在cadjs/implicit/*下再导出其共享渲染/导出 API,因此消费方(CAD Viewer、快照工具)只装cadjs即可;viewer/必须完全自包含:其下任何内容不得引用上层路径/命令/文档,因为它是被逐字镜像进独立 cad-viewer 仓库的,无重写步骤。viewer/scripts/selfContained.test.mjs 在每次测试运行中强制这一点。
产物与脚本纪律
- 所有测试、样本、永久与生成的 CAD/机器人描述产物(STEP/STP、STL、GLB、DXF、URDF、SRDF、SDF)一律写入
models/,禁止另建临时产物目录; scripts/只放长期有效的仓库命令,临时一次性脚本放tmp/或/tmp;- 源码变更影响生成运行时后,用主打包包装器 scripts/bundle/bundle.sh 刷新或校验:
scripts/bundle/bundle.sh # 同步版本元数据并打包所有生产输出 scripts/bundle/bundle.sh --check # 打包到 tmp/,若已提交输出过期则失败 scripts/bundle/bundle.sh --clean # 先清理临时打包目录bundle.sh先调用sync-version.mjs同步版本元数据,再委托 scripts/bundle/bundle-skill.sh 按scripts/bundle/skills/bundle-<skill-id>.sh逐个 Skill 打包(--all遍历全部、--print-outputs输出生成路径供检查脚本使用)。低层 bundle 脚本只在调试包装器本身时使用。
- Git 卫生:不提交
.venv/、node_modules/、缓存、tmp/、本地凭据与打印机配置;生成运行时的变更应来自生产输出流程而非手工编辑生成目录。
开发环境与轻量 worktree
- CAD Python 工作优先使用
./.venv/bin/python(CONTRIBUTING.md 建议python3.12 -m venv .venv后安装requirements-dev.txt); - 默认保持新分支 checkout 与 git worktree 轻量化:不通过
.worktreeinclude复制.venv/或models/;仅当工作流需要 Python 依赖时才在 worktree 内重建.venv/; - worktree 中若明确需要开发 symlink 布局,先
scripts/dev/setup-symlinks.sh --check再有意执行scripts/dev/setup-symlinks.sh;不要在 Codex / Claude Code 的启动 hook 里自动修复布局; models/只在用户要求或任务明确针对其下文件时才水合:优先用本地 Git LFS 缓存git lfs checkout <path>或git lfs checkout models,仅在明确需要且本地缓存缺失时下载 LFS 对象;- 只为正在改动的工作流安装依赖。
检查体系:先小范围,后全量
原则是“跑覆盖本次改动的最小路径定向检查”,触碰共享面或交接前再跑全量包装器:
scripts/test/test.sh # 代码测试总入口 scripts/test/test-js.sh # 聚焦 JS 测试 scripts/test/test-docs.sh # 聚焦文档测试 scripts/test/test-python.sh # 聚焦 Python 测试 scripts/test/test-global.sh # 聚焦全局测试 scripts/dev/setup-symlinks.sh --check # 开发 symlink 布局 scripts/release/check-version.sh # 规范版本号 scripts/bundle/bundle.sh --check # 生成运行时新鲜度包级与站点检查:npm --prefix packages/cadjs test、npm --prefix packages/implicitjs test、npm --prefix viewer run test、npm --prefix viewer run build、npm --prefix docs run check;定向 Python 测试用./.venv/bin/python -m unittest <changed test paths>(测试按tests/python/下skills/<skill>、packages/<package>、viewer/<service>、global分组)。CI 中test.yml在独立 job 校验规范版本号(版本元数据错了代码测试仍能跑),并在测试 job 里校验 develop symlink 布局、对比生成输出与源码、临时打包生产输出、再对产物跑文档与代码测试。
CAD Viewer 调试实战
URL 语义:PATH 即绝对目录
Viewer 的 URL PATH 就是它打开的绝对目录,与file://URL 一致;?file=在该目录内选择一个工件:
http://127.0.0.1:3245/absolute/model/root?file=path/relative/to/that/rootWindows 下盘符是路径的一部分(前导斜杠之后、使用正斜杠):D:\project\models对应.../3245/D:/project/models。Viewer不是针对某个目录启动的——它打开 URL 命名的任何内容,因此一个实例可服务其服务根下的任意文件夹。worktree 场景下这很关键:从另一个 checkout 启动的实例按它自己的根解析路径,指向别的 clone 的绝对路径会直接“not found”,面板报告该文件在此 viewer 根之外。如果其他 checkout 的 Viewer 已占用默认端口,请为本工作区另起一个空闲端口实例(--port <n>),而不是把运行中的实例指向你的路径。
查看仓库 fixture 时,一律用models/目录作为路径并保持永久/生成文件放此处,且必须用绝对路径——Viewer 从任意工作目录启动,相对路径会解析错位置。除非用户要求,不要停掉别人的 Viewer。
改了源码不生效?先怀疑 Vite 缓存
编辑viewer/或packages/cadjs源码却看不到变化,很可能是 Vite 的服务端 transform 缓存比 HMR 和硬刷新都“长寿”——磁盘文件已正确,浏览器却一直服务旧模块。重启 dev server 并删除viewer/node_modules/.vite即可。
Dev 默认、Prod 只用于 e2e
迭代用devserver——Vite 从源码直接服务客户端并带 HMR,viewer/、packages/cadjs、packages/implicitjs的改动即时可见:
npm --prefix viewer run dev -- --host 127.0.0.1 --port <n> # 然后打开 http://127.0.0.1:<port><repo>/models?file=<path>prod路径只用于对发布 bundle 的端到端测试,或用户明确要求测 prod。它由 Python 后端(cad-viewer Skill 的start命令)服务构建好的dist/,所以先构建:
npm --prefix viewer run build npm --prefix viewer run start -- --host 127.0.0.1 --port <n>端口行为
dev与start都监听--port,默认3245;两者都不会滚动到其他端口——端口被占则直接报错退出,所以 Viewer 总在你要求的端口上。传--port <n>可同时跑多个实例。打包版 Viewer 的运行时与交接细节见 skills/cad-viewer/SKILL.md,其检查属于生成输出检查,走主打包包装器。
轻量 worktree 里启动 Viewer 的四个坑
cad-viewer Skill 文档描述的是 PRODUCTION 运行时,假设是已水合的 checkout;而轻量 worktree 刻意不带node_modules和构建产物,其“一行命令”会连续失败四次,且每次报错都不指向真正原因:
npm --prefix skills/cad-viewer/scripts/viewer run start报Cannot find package 'cadjs'——skills/cad-viewer/scripts/viewer是指向viewer/的 symlink,打包运行时仍需要 worktree 自己的模块;- 链接好模块后服务能起、CAD API 有响应,但
/返回 404——start服务的是预构建 bundle,而 worktree 还没有viewer/dist。活着的后端配缺失的前端,看起来像坏链接而非缺构建; npm --prefix viewer run build再逐个裸 specifier 失败——implicitjs、three、meshoptimizer,都来自packages/cadjs/src/...;meshoptimizer在packages/cadjs/node_modules下任何位置都不存在,仓库里唯一副本在docs/node_modules/meshoptimizer。
从 worktree 根目录(<main>为主 checkout)正确补救:
ln -s <main>/viewer/node_modules viewer/node_modules mkdir -p packages/cadjs/node_modules ln -s ../../implicitjs packages/cadjs/node_modules/implicitjs ln -s <main>/packages/cadjs/node_modules/three packages/cadjs/node_modules/three ln -s <main>/docs/node_modules/meshoptimizer packages/cadjs/node_modules/meshoptimizer npm --prefix viewer run build npm --prefix skills/cad-viewer/scripts/viewer run start -- --host 127.0.0.1 --port <n>务必用显式的空闲--port:从别的 checkout 启动的 Viewer 会按它自己的根解析路径,永远找不到这个 worktree 里的模型。
两个容易误判“模型坏了”的行为
- 目录扫描跳过点目录:
.review/或其他点开头路径下的可构建条目,用直接?dir=查询能解析,但永远不会出现在项目根扫描结果里,Viewer 会报告“文件不存在”。可构建条目不要放进点目录; - 验证 Viewer 链接要“开页面”,不要 curl
/__cad/asset:该路由只服务原始文件;生成条目的渲染包由另一条路由服务,所以探测它无论有无问题都返回 404。
Git 与 LFS 纪律
CAD 交换文件、生成渲染/topology 资源与assets/**可能由 Git LFS 跟踪。绝不为git add、提交或其他写对象操作禁用 LFS 过滤器。本地 hooks 位于.githooks,通过 scripts/git-hooks/pre-commit 委托构建检查。assets/**存放重型演示 GIF,被排除在默认 LFS 拉取之外,仅在本地需要演示素材时执行git lfs pull --include="assets/**"水合。
上手路径小结
- 开发环境:从
develop切分支 → 建.venv→scripts/dev/setup-symlinks.sh建立 symlink 布局 → 编辑skills/<skill>/与packages/源码; - 本地联调 Skill:用 scripts/install/install-skills.sh(
--agent codex|claude|gemini|universal|project,--all全装)把 checkout 的 Skill symlink 进 Agent,改完立即生效;卸载用 scripts/install/uninstall-skills.sh,只移除指向本 checkout 的链接; - 验证:先跑最小定向检查,再
scripts/test/test.sh、scripts/bundle/bundle.sh --check; - 发布:只通过
Release工作流,由develop构建、main发布,VERSION是唯一版本事实源,绝不手工改衍生版本。
生产用户从main克隆安装,贡献者则把develop加Release工作流当作通往main的唯一路径——这条“分支开发、symlink 迭代、单管线发布、检查前置”的工程闭环,正是 text-to-cad 能同时服务多种 Agent 安装器而又不丢文件的关键所在。
【免费下载链接】text-to-cadA library of agent skills for CAD, CAE and CAM项目地址: https://gitcode.com/GitHub_Trending/tex/text-to-cad
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考