Trellis 教程

Trellis 相对与 superpowers,是一个比较重的框架,特别适合于团队开发特定项目(支持定义自己的spec)。科研的代码我感觉暂时不用,等 Trellis 先发展一下

Claude Code 下,Trellis 的两个基础机制是:会话开始时由 SessionStart Hook 自动触发 session-start.pysession-start.py 还会把 start.md 写进 additionalContext,并明确告诉 agent:前面上下文已注入,从 Step 4 开始,等用户第一条消息;若有活跃任务,则先问是否继续。

从任务数据结构看,Trellis 的底层不是"全靠记忆",而是靠 .trellis/tasks/ 下的任务目录和 jsonl 文件驱动。标准任务生命周期是:create → init-context → add-context → start → implement/check → finish → archive;其中 implement.jsonl 给 implement 用,check.jsonl 给 check 用,debug.jsonl 给 debug 用,.trellis/.current-task 则是当前任务指针,Hook 会据此自动注入上下文。(Trellis 文档)

1)会话刚开始时发生什么

Hook 会在新会话开始时自动跑 session-start.pysession-start.py 会把 workflow、guidelines、spec 索引和 start.md 拼进 additionalContext,作为给 agent 的上下文。接下来有两种工作模式可以选择:/start/parallel(从这里其实可以看出来,这里的设计并不是很精细,因为start.md一定会被注入上下文,但是其已经是一个完整的工作流了,而parallel.md也是一个完整的工作流)。如果选择/start,那么是不用手动运行/start的,因为前面已经注入了start.md了;如果选择/parallel才需要手动运行。

2)/start 的完整工作流

start.md 的作用是把开发会话拉到 Trellis 的标准单进程(同一时间只有一个 agent 在运行)串行路径上。它会读取 workflow.md、运行 get-context.py 取回身份 / Git 状态 / 活跃任务 / 历史对话元信息、读取 spec 索引,然后向用户汇报上下文并问 "What would you like to work on?"

然后它会先做任务分类。当前 start.md 明确分成四类:Question、Trivial Fix、Simple Task、Complex Task。其中 Question 是答疑;Trivial Fix 是错字、注释或单行之类的小改动;Simple Task 是目标清晰、范围小、1--2 个文件内的问题;Complex Task 是目标模糊、涉及多个文件或架构判断的问题。

对于 Question / Trivial Fix ,Trellis 允许直接处理,不必先走完整任务化流程;但如果在这个过程中真的改了代码,start.md 明确要求提醒用户运行 /trellis:finish-work。这一点只适用于这两个分支,不是整个 /start 流程结束时的统一提示。

对于 Simple Task ,流程是:先做一次快速确认,确认目标、开发类型和约束;然后创建 task 目录,写 prd.md;再进入共享工作流。start.md 还特别强调:Research 必须在需求清楚、PRD 已存在之后再做 。如果变更触及跨层契约、数据库、API 签名或 infra 约束,还要先过一遍 code-spec depth check,再进入实现。(GitHub)

对于 Complex Task ,不会直接实现,而是先进入 /trellis:brainstorm。brainstorm 的步骤是:

  • 先建 task,再 seed 初始 PRD
  • 然后做 Auto-Context,优先自己从 repo / docs / config 中拿能拿到的信息;实在有不确定的并且无法自己获取的信息,对用户进行提问
  • 一次只问一个高价值问题,避免一条消息里堆一串问题
  • 每次用户回答后都要立即更新 PRD,最后做一次结构化最终确认,用户确认后才并入共享的任务工作流

无论是 Simple Task 还是 Complex Task,最终都会汇入同一条标准 Task Workflow:研究代码库、配置 jsonl 上下文、激活任务、调用 implement agent、再调用 check agent,最后进入 complete。start.md 的 Complete 步骤要求的是:确认 lint/typecheck 通过、汇报实现内容、提醒用户测试变更、准备好后 commit,并运行 /trellis:record-session 记录本次会话 。这里不会显式提醒 用户再跑一次 /trellis:finish-work/finish-work 在命令参考中被定义为提交前的收尾检查命令,而不是 /start 完成步骤里的标准提示项。所以记住在提交之前跑一次/finish-work

/trellis:record-session 本身也是一个独立收尾动作。文档写得很清楚:它通常在用户已经测试并 commit 之后使用,底层会追加会话记录到 journal(历史对话),必要时滚动新日志文件,并更新 workspace 的 index.md。换句话说,/start 负责把任务做完;会话沉淀/record-session 单独负责。在开启一个新对话的时候,历史对话的元信息会被注入到上下文中,至于是否读取具体的历史记录,由 agent 自己决定

3)/trellis:parallel 的完整工作流

/trellis:parallelClaude Code 专属 的多 agent 并行编排入口,适用于复杂度高、预计超过 30 分钟、可以拆成独立子任务、并且需要在独立 worktree 中隔离开发的情况。多会话文档和命令文档都把它定义成 worktree / multi-session 模式:每个任务一个 worktree、一个 Claude Code 会话、一个分支,各会话独立运行。

这个命令启动后,先读取当前上下文和项目规范,然后向用户确认三件事:要开发什么 feature、涉及哪些模块、开发类型是 backend / frontend / fullstack。确认后再根据复杂度分流:复杂需求走 Plan Agent ,简单而清晰的需求走 Manual Configuration

Plan Agent 时,Plan 会先做需求合理性判断;不清晰、过大、不合理或有风险的需求可以被直接拒绝。若接受,则 Plan 会调用 Research Agent 分析代码库,创建并配置 task 目录,写 prd.md,产出 task.json / prd.md / implement.jsonl / check.jsonl / debug.jsonl,然后把这个 task 交给 multi_agent/start.py 去启动 worktree 会话。

Manual Configuration 时,主 orchestrator 自己做这些准备:创建 task 目录、init-context、设置 branch 和 scope、按需添加上下文、写 prd.mdvalidate,最后调用 multi_agent/start.py "$TASK_DIR" 启动 worktree 会话。也就是说,简单且边界清晰的任务不一定非得先过 Plan Agent。

每个 worktree 会话都有自己的 dispatch agent。

Dispatch 是 worktree 里的纯调度器 。它自己不读 specs 或 requirements,不负责复杂分析,只负责:读取 .trellis/.current-task 指向的 task 目录、读取其中 task.jsonnext_action 数组,然后按 phase 顺序 调用子 agent 或脚本。注意,dispatch.md中的Phase Handling 只是"各 action 的处理方式说明",不是"每次必须把所有 action 都跑一遍"。是否执行某个阶段,取决于 next_action 写了什么。也就是说执行的阶段(phase)取决于next_action

一般来说,dispatch 通常会按这个顺序推进:implement → check → finish → create-pr 。这里最容易误解的是 finish:它是再次调用 check agent ,只是 prompt 带 [finish] 标记。

debug 虽然和 implement/check 一样,都是 dispatch 可调用的并列 subagent,但它不是默认主线里的固定阶段dispatch.md 的错误处理写得很明确:如果某个 subagent 失败,而 dispatch 认为问题可恢复,就调用 debug agent 去修 ;如果不可恢复,再通知用户并请求指导。因此,debug 的定位是异常恢复 / 定向修复通道

Implement、Check、Debug 三者的职责也已经比较明确:Implement 会先读 PRD / 技术设计 / 自动注入的 specs,再实现功能,并至少运行项目的 lint 和 typecheck 做验证;Check 先看 git diff,再按 specs 审查目录、命名、类型、潜在 bug,并且必须 self-fix (修复代码),不是只报问题;Debug 则是针对已知错误或 issue 描述做精确修复,避免顺手做无关重构

在 dispatch agent 的最后阶段,会创造草稿 pr :create-pr 阶段是真正的 GitHub PR 创建。Dispatch 和 create_pr.py 都明确写了这一步会:stage 并 commit 改动、push 到 origin、再用 gh pr create --draft 直接创建 Draft PR ,同时把 task.json 更新到 review 状态并写入 pr_url。而且这是整个并行流水线里唯一允许执行 git commit 的动作。由于要创造pr,我们需要将仓库绑定到一个Github远程仓库

PR 创建之后,人的职责就比较简单了:去 GitHub 上 review;没问题就 merge;然后回到本地主仓,把目标分支拉到最新,再清理这个任务的 worktree。多会话文档给出的口径是:cleanup.py 会删除 worktree,但保留分支,合并后的分支再按正常 Git 流程删除。

不过这里有一个实现细节差异 :文档页写的是"cleanup 删除 worktree 但保留分支",但当前仓库里的 cleanup.py 实现实际上是:清理时会归档 task、移除 registry、删除 worktree,并且默认还会删除本地 branch ;只有加 --keep-branch 时才保留分支。脚本的帮助信息和实现都写了这一点,所以如果你按"当前仓库实现"来跑,应以脚本行为为准。

注意

最后还有一步,什么task.py finishtask.py archive,可以区分一下之后

第一步trellis init的选项:

可以。先把范围说清楚:

如果严格按我上一条的"第一步"定义 ,也就是 init(options) 刚进入后、还没开始真正创建 .trellis/ 和平台目录之前,那一段里"已经会立刻影响执行路径"的选项,主要是下面这些。源码对应位置在 init.ts 开头:先设置写入模式,再处理开发者名,再解析 registry、template strategy,最后进入 monorepo 检测。(GitHub)

1. -f / --force

这个是第一步里最直接生效的。源码里 options.force 会把 writeMode 设成 "force",然后调用 setWriteMode(writeMode);含义就是后续 Trellis 写管理文件时,遇到已有文件按"强制覆盖"策略走。文档 FAQ 也把它说明成"强制覆盖已有文件"。(GitHub)

2. -s / --skipExisting

这也是第一步立即生效的。源码里如果没有 force、但有 skipExisting,就把 writeMode 设成 "skip",表示后续遇到已存在文件时跳过。文档 FAQ 对应写的是"跳过已有文件"。而且它和 --forceif / else if 关系,所以两者同时给时,源码优先走 force。(GitHub)

3. -u / --user

这是你例子里实际传入的那个。源码先执行 let developerName = options.user;也就是说只要你传了 -u user_name,它就直接采用这个名字,不再依赖 git config user.name。文档也明确说了:-u 用来设置开发者身份,并创建 .trellis/workspace/{username}/。(GitHub)

4. -y / --yes

这个在第一步的作用不是"立刻创建文件",而是抑制交互 。源码里当没有拿到 developerName 时,只有在 !options.yes 的情况下才会提示 Your name:;另外后面的 monorepo 决策里,options.yes 也会直接接受默认选择。FAQ 里对应说明是"跳过确认,使用默认配置"。(GitHub)

5. --registry

这个也属于前置生效选项。源码在项目类型探测后立即解析 options.registry;如果 registry 地址非法,会直接报错并 return,后面初始化不继续。也就是说它在很早阶段就能改变执行路径,甚至提前终止。文档里把它定义为"从自定义仓库拉取 spec 模板"。(GitHub)

6. --overwrite / --append

这两个不是全局写文件策略,而是模板写入策略 。源码会先把 templateStrategy 默认设成 "skip";如果给了 --overwrite 就改成 "overwrite",给了 --append 就改成 "append"。这个策略随后会用于远程 spec/template 下载和落盘。文档里也写得很直接:--overwrite 是"模板时覆盖已有 spec",--append 是"只补充缺失文件"。(GitHub)

7. --monorepo / --no-monorepo

这也是在前段就开始影响逻辑的。源码是 if (options.monorepo !== false) 才会进入自动检测;注释里明确写了:true = --monorepofalse = --no-monorepoundefined = auto。如果你强制指定 --monorepo,但当前仓库没检测到 monorepo 配置,程序会直接报错退出。最近更新日志也写到 trellis init 会自动检测 pnpm/npm/Cargo/Go/uv workspaces 和 git submodules。(GitHub)


有些选项不是我说的"第一步"就生效,而是稍后阶段才生效:

8. 平台 flags:--claude--cursor--iflow--opencode--codex--kilo--kiro--gemini--antigravity--windsurf--qoder--codebuddy--copilot

这些参数确实属于 init 的合法选项,InitOptions 接口里也列出来了;文档和 README 也都列了平台选项。但它们主要影响的是后面的"平台配置阶段" ,决定是否生成 .claude/.cursor/.agents/skills/.kilocode/.kiro/ 等平台目录,不是最前面那段写入模式/开发者识别逻辑。(GitHub)

9. -t / --template

这个也是 init 的正式参数,文档里示例是 trellis init -t electron-fullstack。但它的效果主要体现在后续模板选择/下载阶段:有没有预选模板、是否跳过某些交互,而不是我上一条里定义的"第一步"核心逻辑。源码接口里有 template?: string。(GitHub)


所以,如果只问"第一步里最关键、最早就会改执行路径的选项",可以压缩成这张表:

  • -f:全局强制覆盖
  • -s:全局跳过已有文件
  • -u:直接指定 developer name
  • -y:跳过交互确认
  • --registry:提前决定模板源,非法会直接中止
  • --overwrite / --append:提前决定模板落盘策略
  • --monorepo / --no-monorepo:提前决定 monorepo 检测路径 (GitHub)

而你给的这条:

bash 复制代码
trellis init -u user_name

在"第一步"里真正触发的核心效果只有两个:

一是把 developerName 直接设成 user_name;二是因为你没有给 -f/-s/-y ,所以写入模式保持默认 "ask",交互也保持开启。也就是说,后面如果遇到平台选择、模板选择、monorepo 确认,仍然可能会问你。(GitHub)

下一条我可以继续把这个拆成"只针对 trellis init -u user_name 这一条命令,按源码逐行走一遍最前 50~100 行执行流"。

Q:

  1. 已有文件指的是哪些文件?是否强制覆盖有什么区别吗?
  2. monirepo是什么?有什么用?
  3. -t 和 registry的关系是什么?
    A:
    可以,分开说。

1)"已有文件"具体指哪些?--force 和不强制覆盖有什么区别?

init 里,--force--skipExisting 先被转换成全局 writeMode;默认是 "ask"--force 变成 "force"--skipExisting 变成 "skip"。而真正写文件时,writeFile() 的规则是:文件不存在就直接写;文件已存在但内容完全相同就静默跳过;内容不同则按模式处理------force 覆盖,skip 跳过,append 追加,ask 则弹交互让你逐个选"跳过 / 覆盖 / 追加 / 对后续全部生效"。(GitHub)

所以这里的"已有文件",更准确地说,是所有通过 writeFile() 落盘且目标路径已经存在、并且内容不同的文件 。从 createWorkflowStructure() 可以确认,至少这些核心文件受这个机制控制:.trellis/scripts/.trellis/workflow.md.trellis/.gitignore.trellis/config.yaml.trellis/workspace/index.md.trellis/worktree.yaml,以及 .trellis/spec/ 里的 guides / frontend / backend 模板文件。(GitHub)

再往外一层看,Trellis 初始化本来就主要生成 Trellis 自己的结构文件平台接入文件 ,例如 .trellis/.claude/.cursor/AGENTS.md.agents/.kilocode/.kiro/ 等,而不是去改你的业务源码目录。也就是说,--force 的覆盖对象通常是这些 Trellis 管理的文件。(GitHub)

但有一个边界要注意:并不是 init 里的所有写入都走 writeFile() 。例如 bootstrap 任务 00-bootstrap-guidelines 是直接用 fs.writeFileSync()task.jsonprd.md.current-task;而且如果这个任务目录已经存在,源码会直接 return true,不会重写它。所以 --force 不是"无脑覆盖 init 过程中的一切文件",它首先保证的是 writeFile() 那一类写入。(GitHub)

把差异压缩成一句话:

  • 不加 --force :默认是 ask,遇到冲突会逐个问你怎么处理。(GitHub)
  • --force :遇到受 writeFile() 管理且内容不同的已有文件,直接覆盖,不再逐个确认。(GitHub)

还要再区分一个容易混的点:--force全局文件写入模式 ;而 --overwrite / --append远程 spec 模板下载时.trellis/spec 冲突目录的策略。源码里 templateStrategy 是单独算的;如果 .trellis/spec 已存在且你没给 --overwrite / --append,它会额外问你是 skip / overwrite / append。(GitHub)


7)monorepo 是什么?有什么用?

放在 Trellis 这个上下文里,monorepo 可以理解成:一个 Git 仓库里包含多个 package / app / service / workspace 。Trellis 当前会把 pnpm/npm/Cargo/Go/uv workspaces 和 git submodules 这类多包结构识别成 monorepo 候选。(GitHub)

它的用处,在 Trellis 里不是抽象概念,而是非常具体的三件事:

第一,按包拆 spec

开启 monorepo 模式后,Trellis 不再只生成单一的 .trellis/spec/frontend.trellis/spec/backend,而是给每个包生成自己的 spec/<package>/... 目录;源码里就是循环 packages,对每个包写 spec/<name>/。文档更新也明确写了:trellis init 会生成"按包划分的 spec 目录"。(GitHub)

第二,把包信息写进 .trellis/config.yamlwriteMonorepoConfig() 会把自动探测出的包写成 packages: 列表,并选一个 default_package:。这样后续 Trellis 的任务、上下文注入、脚本调用就知道"当前到底是在操作哪个包"。(GitHub)

第三,让后续脚本和上下文按包工作

Trellis 的 changelog 里已经把这层支持写出来了:session-start 支持 spec_scope 过滤;task.pyadd_session.py 支持 --package;动态 spec 发现也支持 monorepo 的路径结构,例如 spec/cli/backend/。这意味着 AI 注入上下文时,不必把整个大仓所有规范一股脑塞进去,而是可以更精确地只读当前包相关的规范。(Trellis Docs)

所以,monorepo 的核心价值 就是:

你仓库里有多个子系统时,Trellis 能把"规范、任务、上下文"从"整个仓库一锅端"变成"按 package 精确路由"。对于前后端同仓、多 app、多 service、带 submodule 的项目,这会明显减少上下文污染。这个判断是我根据 Trellis 生成 packages 配置、按包建 spec、以及按包过滤上下文这几处实现综合得出的。(GitHub)


9)-t--registry 的关系是什么?

这两个参数不是替代关系,而是**"源"和"目标"的关系**:

  • --registry:指定去哪里找模板 。文档写得很明确,它是"从任意 Git 仓库拉取 spec 模板";Trellis 会先判断这个仓库是 Marketplace 模式还是直接下载模式。(Trellis Docs)
  • -t / --template:指定在这个模板源里选哪一个模板 ID 。源码里单仓/多仓下载都把 options.template 传给 downloadTemplateById(...);在 monorepo 下,--template 还会作为"所有 package 的默认模板"。(GitHub)

具体关系是这样的:

情况 A:只写 -t,不写 --registry

-t 默认就是从 Trellis 的默认模板源里选一个模板 ID。源码里 selectedTemplate 最终会触发 "download specific template by ID" 这条路径。(GitHub)

情况 B:只写 --registry,不写 -t

这时 Trellis 先看这个 registry 是哪种模式:

  • 如果仓库里有 marketplace/index.json,就是 Marketplace 模式,会读取模板列表并让你交互选择;
  • 如果没有 index.json,就是 直接下载模式 ,把整个 marketplace/specs/ 当成一个模板直接下。(Trellis Docs)

情况 C:--registry-t 一起写

这是最明确、最稳定的组合:--registry 指定模板仓库,-t 指定这个仓库里的模板 ID。对于有多个模板的 marketplace registry,这基本就是"从某个模板仓里精确点名下载哪一个"。源码里 downloadTemplateById(..., options.template, ..., registry, ...) 正是这么传参的。(GitHub)

还有一个源码层面的细节很重要:

-y 模式下,如果你给了 --registry没给 -t ,而该 registry 又是一个包含多个模板的 marketplace,CLI 会直接报错,因为它无法自动猜该选哪个模板;报错文本就写着"Use --template to specify which one, or remove -y for interactive selection."。( GitHub)

所以你可以把它记成一句话:

--registry = 模板仓库地址;-t = 该仓库中的模板 ID。

没有 --registry 时,-t 用默认模板源;有 --registry 时,-t 就是在那个 registry 里选具体模板。(Trellis Docs)

如果你愿意,我下一条可以继续把这三个参数组装成几条最容易踩坑的命令示例,比如单仓、monorepo、自定义 registry、覆盖策略各怎么写。

所以如果识别到了monorepo,最好就是启用monorepo吗?通常是,但不是"只要识别到就一定要开"。

我的工程判断是:如果 Trellis 检测出的那些 package,确实是你会分别开发、分别维护、甚至规范都不同的子系统,那就应该启用 monorepo mode。 启用后,它会按包生成 spec/<name>/,并把 packagesdefault_package 写进 config.yaml;后续脚本也支持按 package 过滤上下文,所以 AI 读到的规范会更精确,不容易把 A 包的约定带到 B 包。(Trellis Docs)

不建议开的情况 也很明确:

如果它只是"形式上是 workspace / submodule",但你实际把整个仓库当成一个应用 来开发,目录规范、编码约定、任务边界都基本统一,那不开反而更省事。不开时 Trellis 会走单仓模式,只生成全局 .trellis/spec/,维护成本更低。(GitHub)

你可以用一个很实用的判断标准:

如果你平时会自然地说出这些话,就该开:

"web 和 api 的规范不一样。"

"这个包是 library,那个包是 service。"

"不同包需要不同上下文,不想让 AI 混读。"

如果你更常说的是:

"这其实就是一个项目,只是用 pnpm workspace 管依赖。"

那就没必要开。这个结论是根据 Trellis 的实现推出来的,因为它在检测到 monorepo 后会专门列出包并询问 Enable monorepo mode?,默认值还是 true,说明作者倾向于"真 monorepo 就启用",但仍然把最终决定留给你。(GitHub)

所以我给你的结论是:识别到 monorepo 时,默认应当先倾向于启用;但前提是这些 package 对你来说真的是"独立边界",而不是仅仅共享一个 workspace 外壳。

如果你愿意,我可以下一条直接给你一个 "哪些仓库应该开 / 不该开"的具体判断清单,按前端 monorepo、后端多服务、子模块仓库三类来讲。

那么--registry指定了远程模板的url之后,对这个远程模板有什么格式要求吗?

有。要分成两层看:

一层是 --registry 这个值本身的 URL/源格式要求;另一层是远程仓库内部模板的目录与索引格式要求。 当前 Trellis 文档把 --registry 定义为从自定义 Git 仓库拉取 spec 模板,官方支持 GitHub、GitLab、Bitbucket;源码里还进一步表明,它能解析 public HTTPS、public SSH、self-hosted HTTPS/SSH、带端口的地址,以及带分支/子目录的 browse URL。默认分支是 main,而且至少要能解析出 user/repo 这一级。(Trellis Docs)

1. --registry 的地址,支持什么格式

源码支持的输入形式比文档写得更宽一些,核心包括这些:

  • gh:org/repogitlab:org/repo#ref 这类 giget 风格
  • https://github.com/org/repo
  • git@github.com:org/repo
  • https://gitlab.example.com/org/repo
  • git@gitlab.example.com:org/repo
  • ssh://git@host:2222/org/repo
  • https://host/org/repo/-/tree/branch/path
  • GitHub/Bitbucket 的带 /tree/<branch>/<subdir> 浏览地址也会被规范化处理。(GitHub)

所以,你不一定只能传仓库根 URL 。从源码看,传到某个分支甚至子目录都是可以被解析的;RegistrySource 里就明确保存了 reposubdirrefrawBaseUrl。(GitHub)

2. 远程模板仓库,官方要求的结构是什么

官方文档给出的"标准发布方式"是:

  • 仓库里有一个 marketplace/ 目录
  • 如果要做多模板 marketplace,就再放一个 marketplace/index.json
  • index.json 里列出模板项,每项至少包含idtypenamepathdescriptiontags 是可选
  • 文档示例里 type 用的是 "spec"path 类似 marketplace/specs/my-stack。(Trellis Docs)

文档给的示例索引长这样:

json 复制代码
{
  "version": 1,
  "templates": [
    {
      "id": "my-stack",
      "type": "spec",
      "name": "My Stack Template",
      "description": "我们技术栈的编码规范",
      "path": "marketplace/specs/my-stack",
      "tags": ["react", "node", "typescript"]
    }
  ]
}

这不是我概括,是官方文档原样给出的结构。(Trellis Docs)

3. Marketplace 模式和 Direct Download 模式,各自要求什么

Marketplace 模式

这是marketplace/index.json 的情况。Trellis 会先去取这个索引;源码里 probeRegistryIndex() 的逻辑很直接:

  • 404 视为"没有 index.json"
  • 200 且能解析 JSON,就读取其中的 templates 数组。(GitHub)

所以在 marketplace 模式下,硬性要求基本就是:

  • registry 对应位置能拿到 index.json
  • index.json 是合法 JSON
  • 里面有 versiontemplates
  • 你通过 -t 选中的模板 ID 必须存在
  • 当前实现里模板 type 必须是 "spec",否则会报 "only spec is supported"。(GitHub)
Direct Download 模式

这是没有 index.json 的情况。文档写法是:如果仓库没有 marketplace/index.json,Trellis 会把整个 marketplace/specs/ 目录当成单个模板直接下载。源码对应的下载函数 downloadRegistryDirect() 也说明了:这种模式是"把 registry source 直接下载到 .trellis/spec"。(Trellis Docs)

这里有个很关键的约束:如果你同时传了 --registry-t,但这个 registry 没有 index.json,源码会直接报错:Registry has no index.json. Remove --template to use direct download mode. 也就是说:

  • -t → registry 必须提供 index.json
  • 没有 -t → 可以走 direct download。(GitHub)

4. 模板目录内部需要长什么样

文档把"每个模板"的目标结构描述为一个 spec 树,包含 frontend/backend/guides/README.md 这类内容。Trellis 下载后安装位置是 .trellis/spec;源码里 spec 类型模板的安装目标也确实是 .trellis/spec。(Trellis Docs)

这里有一个我基于源码做的实际建议

因为 downloadTemplateById() 会把模板路径 resolved.path 直接下载到目的目录 .trellis/spec,所以最稳妥的做法 是让 path 指向"其内容本身就应该成为 .trellis/spec/ 内容"的那个目录,也就是里面直接放 frontend/backend/guides/ 等,而不是再多套一层无关外壳目录。这个结论来自源码里 resolved.path 被直接拼成下载源,再落到 getInstallPath(..., "spec") 的行为。(GitHub)

5. 你实际搭自定义 registry 时,最稳的两种写法

写法 A:标准 marketplace 仓库

适合多个模板。

text 复制代码
repo/
└─ marketplace/
   ├─ index.json
   └─ specs/
      ├─ my-stack/
      │  ├─ frontend/
      │  ├─ backend/
      │  ├─ guides/
      │  └─ README.md
      └─ another-stack/
         └─ ...

调用方式:

bash 复制代码
trellis init --registry https://github.com/your-org/your-spec-templates -t my-stack

这完全符合文档示例和源码的 index.json + template id 流程。(Trellis Docs)

写法 B:单模板直链目录

适合你只有一个模板,不想维护 index.json

bash 复制代码
trellis init --registry https://github.com/your-org/your-spec-templates/tree/main/marketplace/specs/my-stack

这条之所以成立,是因为源码能解析 /tree/<branch>/<subdir> 形式,并且 direct mode 会把 registry source 直接下载到 .trellis/spec。这时不要再加 -t 。(GitHub)

6. 可以把要求压缩成一句话

最稳的约束可以记成:

  • 想配合 -t :你的 registry 必须提供 index.json,并且每个模板项至少有 id/type/name/path,其中 type 现在必须是 spec。(GitHub)
  • 不想配 index.json :就把 --registry 指到一个单模板目录,别加 -t,让 Trellis 直接下载它到 .trellis/spec/。(GitHub)

如果你愿意,我可以下一条直接给你写一个可用的最小自定义 registry 仓库示例 ,包括 index.json 和目录树。

在创建之后应该首先完成bootstrap任务,读取prd.md即可

workflow.md是最开始编辑的,编辑过了一般就不会修改了(除非手动)

worktree.yaml 管并行 agent 的运行环境怎么搭,这个可以仔细学一下如何修改