最近参加了 GitLab AI Hackathon 2026,3天完成了一个作品,趁着记忆还新鲜,把整个过程写下来。
这篇文章不是教程,也不是"我做了一个很厉害的东西"的自我吹捧。它更像是一份实录——我遇到了什么问题,怎么想的,怎么做的,踩了哪些坑,以及最后我想清楚了什么。
一、起点:一个让我很疲惫的真实问题
故事从一个课程详情页开始。
这个页面需要面向多个国家、多种用户角色,在同一套代码里处理非常复杂的条件渲染逻辑。用户有4个状态维度:是否是有效用户、是否属于当前组织、有没有拿到认证、有没有订阅过当前课程。课程也有4个状态维度:属于哪个组织、有没有拿到价格、是否支持订阅、是否支持问价。
这8个维度的组合,决定了页面上的横幅展示什么、价格以什么形式出现、按钮是否可见、能不能点击、点了是弹提示框还是悬浮框。
最关键的是,这些逻辑还存在优先级判断顺序。
单看这些描述,好像也还好。但真正让我疲惫的不是逻辑本身,而是这套逻辑在团队里被描述了四遍:BA 写在需求文档里、UX 标注在 Figma 里、我(前端开发)把它翻译成 TypeScript 代码、QA 再把它重新描述成测试用例。
这四份描述没有任何一份是互相兼容的格式。每次需求变更,四个角色要各自更新自己那份,然后开会同步。真正的逻辑活在集体记忆里,而不是任何一份可以被版本控制的文件里。
更痛苦的是:每次我需要改动其中一个判断条件,都会担心改坏了之前已经验证过的逻辑。因为定位"这段代码影响哪些场景"本身就需要花大量时间,改一个地方可能需要一个完整的工作日,而且还没有信心说"我没有引入新的问题"。
这个问题有一个名字,我后来把它叫做 Specification Gap——同一份业务意图,在跨角色传递的过程中反复被翻译、反复产生信息损耗,最终没有任何一个地方能说"这里是完整的真相"。
二、类比:建筑图纸给了我一个答案
我的本科是建筑学,在国内读了五年,后来机缘巧合转行成为了前端开发。
建筑学给我留下最深的训练,不是画图,而是一种思维方式:从模糊的需求出发,一步步推演出一个具体的、可执行的方案。这个过程可能叫做一草、二草、终稿,每一步都在把抽象变得更具体。
当我在思考 Specification Gap 这个问题的时候,建筑里的 CAD 图纸突然给了我一个类比。
一套建筑施工图,建筑师从里面读空间关系,结构工程师从里面读荷载,施工队从里面读尺寸,甲方从里面看效果——每个角色读到的东西不同,但图纸本身只有一份。改一处,所有人看到的都是最新版本。
软件开发目前缺少等价物。BA 文档、Figma 标注、TypeScript 代码、测试用例——这是四份独立的"图纸",不是一份。
这个类比让我想清楚了解法的方向:我需要的不是又一个文档格式,而是一份所有角色都能从中读取自己需要的信息的"母版" ,然后让 AI 做翻译层。
三、方案:先把代码结构想清楚
在真正开始做 Hackathon 之前,我已经在自己的真实项目里尝试过一个方向:把原来散落在 hook 里的条件逻辑,重新组织成几层分明的结构。
我识别出来的层次是这样的:
types.ts— 只放类型定义rules.ts— 规则表:输入条件 → 场景名称,按优先级排列presentation.ts— 场景名称 → UI 输出的映射resolver.ts— 编排层,把 rules 和 presentation 串起来utils.ts— 纯函数工具
这个结构的核心原则只有一条:rules.ts 决定"我们在什么情况下",presentation.ts 决定"这种情况下展示什么",两者严格分离。
为什么这个分离很重要?因为它让每一层的职责变得极度清晰——当业务规则变了,只动 rules.ts;当 UI 表现变了,只动 presentation.ts;当两者都变了,分开改,互不干扰。
验证这个方案的方式很简单粗暴:我在原有测试文件的基础上 copy 了一份,把调用替换成 specDriven 生成的代码,跑测试,全部通过。然后用不同账户登录,在浏览器里逐一检查 UI 表现,也没有问题。
这个验证过程给了我很重要的信心。原本可能需要一个完整工作日才能定位和修改好的逻辑,现在用一段自然语言描述就可以 cover,并且有测试用例作为保证。
这是一场有质量保障的开发加速。
四、Hackathon:把这个思路变成一个 GitLab Flow
GitLab AI Hackathon 2026 的主题是"You Orchestrate. AI Accelerates."——要求用 GitLab Duo Agent Platform 构建一个能介入真实工作流的 agent 或 flow,不是 chatbot,是能响应事件、自动执行动作的东西。
我的想法很直接:把自然语言场景描述变成 specDriven/rules.ts 的增量更新,通过 GitLab Issue comment 触发,自动开 MR,不直接提交到 main。
Flow 的设计分四步:
read_existing_rules(DeterministicStepComponent)——读取 repo 里现有的specDriven/rules.ts,如果不存在就返回NO_RULES_FILE_FOUNDscenario_parser(AgentComponent)——解析用户的自然语言描述,输出结构化 JSON,判断这是 gate 条件、user state 条件还是 course state 条件,并确定正确的优先级位置spec_generator(AgentComponent)——生成完整的rules.ts文件内容,commit 到 feature branch,开 MRpost_comment(AgentComponent)——在触发的 Issue 里回复结构化摘要:业务说明、presentation.ts代码片段、Jest 测试用例、next steps checklist
为什么选 TypeScript 而不是 YAML 来写 spec 文件?因为这是一个前端 repo,我希望这份文件对前端开发者友好——有类型检查、有 IDE 自动补全、as const satisfies Rule[] 可以保留字面量类型精度。
为什么要开 MR 而不是直接 commit?这是一个安全设计决策。任何人都可以在 Issue 里触发 flow,但只有有 merge 权限的人才能把变更应用到 main 分支。AI 做翻译工作,人做最终判断。
五、踩坑实录:3天里遇到了什么
说实话,这3天里写业务逻辑的时间远少于调试 GitLab Flow schema 的时间。
坑1:schema 格式不在一个地方
GitLab Duo Agent Platform 是相当新的平台,文档分散在多个页面,有些字段的格式在官方博客示例、schema 文档、hackathon template 三个地方说法不一致。
最典型的是 toolset 的格式。我以为是字符串列表:
toolset:
- "create_issue_note"
结果一直报 schema validation 错误。后来拿到完整的 v1.md 文档才发现,这种写法是对的,但 inputs 的格式不对——不能用简写,必须是:
inputs:
- from: "context:goal"
as: "user_goal"
还有 hackathon 平台要求在 v1 schema 外面套一层 definition 包装,这个在主文档里没有提,在 hackathon 的 template 里才有。
坑2:create_file_with_contents 不等于 commit
这是让我花了最多时间的一个坑。Flow 跑完,Action log 显示 "Create file" 成功,但 repo 里根本找不到文件。
原因是:create_file_with_contents 只是准备了文件内容,还需要 create_commit 才能真正写入 branch。正确的顺序是三步:
create_file_with_contents— 准备内容create_commit— commit 到 feature branchcreate_merge_request— 从 feature branch 开 MR
坑3:priority 推理不稳定
第一版 prompt 对优先级的判断太模糊,导致"用户不属于当前组织"这种 gate 条件(应该永远是 priority 1)有时候被放到了 priority 2。
解法是在 prompt 里加入明确的四层优先级规则:
- GATE 条件(身份验证、组织归属)→ 永远 priority 1,插入时所有现有规则下移
- USER STATE 条件(订阅状态、认证状态)→ gate 之后
- COURSE STATE 条件(价格状态)→ user state 之后
- FALLBACK → 永远最后
坑4:context:issue_iid 拿不到
Flow 触发时的 context 里包含了 Issue IID,但变量名不是 context:issue_iid,直接用这个名字拿到的是空值,导致 post_comment 步骤静默失败。
解法是让 agent 从 goal 字符串里自己解析——触发时的 goal 里包含了 Context: {Issue IID: X} 这段文字,agent 可以直接提取出 IID。
六、结果:flow 跑通之后
验证过程分三轮,每轮触发一个不同的场景:
第一轮:用户不属于当前组织 → flow 创建 specDriven/rules.ts,第一条规则,priority 1,开 MR
第二轮:用户已经订阅过当前课程 → flow 读取现有文件,识别为 user state 条件,插入 priority 2,原规则顺移,开第二个 MR
第三轮:课程价格尚未确定 → flow 识别为 course state 条件,插入 priority 3,已有规则再次顺移,开第三个 MR
每次触发,Issue 里都会自动回复一份结构化摘要,包括业务说明(BA 可以读懂的那种)、presentation.ts 的代码片段、覆盖新场景和回归测试的 Jest 用例,以及一个 next steps checklist。
三个 MR,三个 diff,一份不断积累的 rules.ts。没有直接操作任何文件,没有开过任何对齐会议。
七、反思:我想清楚了什么
AI 的正确角色是消除翻译工作,不是替代判断。
整个 flow 的设计里,AI 做的事情是:把自然语言翻译成结构化规则、判断优先级、生成测试用例、写业务摘要。这些都是"翻译工作"——有固定的输入输出格式,有明确的正确答案,人做起来费时费力且容易出错。
但 MR 的 review 和 merge,是人的决定。presentation.ts 的变更,是人来应用。这些涉及主观判断和上下文理解的部分,不应该交给 AI 自动执行。
这个边界想清楚之后,我对"AI 会不会替代前端开发"这个问题有了不同的感受。AI 替代的是执行层——有明确输入输出的重复性翻译工作。而识别问题、设计结构、判断边界,这些才是值得花时间深入的部分。
当业务逻辑可以被配置化,我就可以去做更复杂的事。
原本定位一处复杂逻辑并安全修改,可能需要一个工作日。现在用一段自然语言描述就可以 cover,并且有测试保证。这部分工作被轻量化之后,我的注意力可以放到更值得深入思考的地方——系统设计、架构决策、跨角色的协作模式。
这不是被替代,这是被解放。
从想清楚到做出来,中间的距离比想象中大。
这个 spec-driven 的思路我想了很久,但真正把它变成一个可以演示的东西,是因为有了 Hackathon 这个强制交付的压力,我才一鼓作气让 flow 落地。很多好的想法就是因为没有交付压力而永远停在"想法"阶段。找一个截止日期,哪怕很紧,对自己来说是很有效的。
后记
这个 flow 还有很多可以改进的地方——presentation.ts 的变更还需要手动应用,多组件的跨文件一致性还没有处理,对不同代码库的适配性还没有验证。
但在3天内,从一个真实的工作痛点出发,做出一个可以实际运行、有真实 before/after 对比的东西,已经足够了。
GitLab 项目:gitlab.com/gitlab-ai-h…
如果你也遇到过类似的"Specification Gap",欢迎交流。