MoreRSS

site iconelmagnifico | 云浅雪修改

程序员,架构师,无人机集群表演设计师,嵌入式工程师,maya插件开发者,多智能体研究者,独立游戏爱好者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

elmagnifico | 云浅雪的 RSS 预览

Skills进阶

2026-06-05 00:00:00

Foreword

前一篇Skills算是简单的试用,日常用起来也没问题。但是如果要给一个软件写 Skills,把软件能力变成 AI 可以控制并且能完成你设定 pipeline 的 Skill,实践起来就有一些不一样了。

这里以 MenuReel(连锁餐厅数字菜单动效短片编排软件)为例,记录一下实际落地时和「Blog 润色 Skill」这类简单 Skill 的差异。

其实 MenuReel 的程序接口还没全部实现,但我已经提前通过 Skill 写一套「模拟调用协议」,让 Agent 按真接口的方式逐步执行完整 pipeline,而不是口头说「我已经帮你创建好了 10 个镜头段落」。反复试用的目的,是发现 Skill 没覆盖的地方,以及产品、接口上缺少的能力,从而把接口和产品补全,真接口一上线就能正常用。

等程序接口做完,Skill 里只需要把 [CALL] 替换成真实调用,流程约束可以不变。这样前期就能跑通核心用户体验,验证这个产品或方案是否可行,验证成本比先把整个程序全部做完要低得多。

Skills进阶

背景

背景依然非常重要。Skills 的模板中需要描述何时使用这个 Skill,但是如果要做这个列表其实很难,总有你忘记的情况或者是漏掉的。而背景存在的意义,就是让 Agent 充分理解你的软件功能和边界,Agent 可以凭借自己的理解来决定是不是该调用这个 Skill。

实际写的时候,背景最好分三层,不要只写一段产品介绍:

  1. 领域概念:软件是干什么的、核心对象有哪些(镜头段落、时间轴、配色动效模块等)
  2. 工作流:一条完整业务从输入到导出要经过哪些阶段
  3. 举例:用具体数字走一遍(比如 10 个主画面、8~12 个 scene、15 分钟以内)

「使用时机」和背景是互补关系。背景负责帮 Agent 理解边界,使用时机负责给 Agent 一个明确的触发词列表:

## 使用时机

在以下情况使用此技能
- 如果用户要生成一个镜头段落
- 如果用户要对 MR 内的镜头段落做修改
- 如果用户要对 MR 内的配色动效做修改
- 如果用户要对 MR 内的模块做修改
- 如果用户要对 MR 做短片导出
- 如果用户要对 MR 做转场演算
- 如果用户要知道当前 MR 的时间轴情况
- 如果用户要做一个菜单动效方案
- 如果用户要做一条门店菜单屏短片
- 如果用户要做一条菜单动效编排方案

每个子域 Skill 还可以再写自己的使用时机。比如时间轴相关的操作,单独在 时间轴.md 里再列一遍「转场演算 / 导出 / 查询时间轴 / 镜头段落编排」,Agent 读到子文件时更容易精准定位。

Description

正文里的「使用时机」很重要,但 Agent 加载 Skill 之前先看的是 YAML frontmatter 里的 description。它会被注入系统提示,用来判断「要不要读这个 Skill」。

description 建议用第三人称,同时写清 WHAT(能干什么)WHEN(什么场景),比正文里列触发词更早生效:

---
name: MenuReel
description: 用于 MenuReel 中进行连锁餐厅数字菜单动效短片编排相关的功能描述和调用
disable-model-invocation: false
---

disable-model-invocation 也要想清楚:false 表示 Agent 可以根据对话自动加载;true 表示只有用户点名时才加载。Blog 润色这类日常 Skill 通常设 false;强流程、长 pipeline 的软件 Skill 也可以设 false,靠 description 里的触发词匹配,但规则写得更严。

MenuReel 的子文件(素材.md时间轴.md 等)各自也有独立的 namedescription,相当于子 Skill。主 Skill 负责路由,子 Skill 负责专域细节,discovery 和加载都更精准。

多文件拆分

上一篇的 Blog 润色 Skill,一个 SKILL.md 就够了。软件类 Skill 很快会膨胀,全部堆在一个文件里,Agent 既难检索,也容易漏规则。

MenuReel 实际拆成了这样:

SKILL.md              → 总入口:背景、状态机、P0 规则、开工门禁
├── 素材.md
├── 镜头段落.md
├── 时间轴.md
├── 配色动效模块.md
└── 接口协议.md       → 统一协议层:命名、回包、错误码、全量接口定义

主 Skill 只保留全局规则和路由,具体接口细节放到子文件里,用「功能细节参考 xxx.md」跳转。这和 Cursor / Anthropic 推荐的 progressive disclosure 思路一致:先给 Agent 看目录和约束,需要时再读细节,避免一次把几千行规则全塞进 context。

状态机 / pipeline

之前设计的流程,都是人工写的 1.2.3.4,但是实际上 Agent 执行时,还是有概率出现跳过流程或者不按你写的走。这种情况下就需要明确的状态机来规范 Agent 的执行流程。

### 状态机(必须)

`draft -> initialized -> assets_ready -> shot_ready -> timeline_ready -> computed_pass1 -> palette_ready -> computed_pass2 -> exported`

光写一条状态链还不够,每个接口还要绑定 next_state 和前置条件。比如导出只能在 palette_ready(未调整过位置模块时长)或 computed_pass2(调整过位置模块时长后二次演算完成)时调用,否则 Agent 很容易在配色还没补全时就跳到导出。

状态流转,也需要明确定义。复杂接口不能只写输入输出,要把后续必须执行的子步骤写清楚:

#### `mr.timeline.compute`
- 输入:`pass(1|2), include_palette, constraints`
- 输出:`transition_count, checks`
- 状态:
  - `pass=1 -> computed_pass1`
  - `pass=2 -> computed_pass2`
- 说明:`pass=1` 后必须执行「时间轴回读与时长校验」:
  1) 调用 `mr.timeline.query` 获取过渡模块真实时长(默认 3s 仅作为预估);
  2) 演算成功后位置重排由程序自动完成,Agent 不再调用 `mr.timeline.move_module` 做常规重排;
  3) 再次调用 `mr.timeline.query` 校验总时长与模块连续性;
  4) 若总时长小于目标 `duration_sec`,按「静态段优先 + 按比例分配」计算增量,并调用 `mr.shot.update_duration` 扩充画面镜头段落时长(仅主画面 / 开场 / 收尾);
  5) 若步骤 4) 发生了任一位置镜头段落时长调整,则配色补全后必须执行 `mr.timeline.compute(pass=2, include_palette=true, constraints)`;
  6) 配色补全完成(`palette_ready`)或二次演算完成(`computed_pass2`)后,必须调用 `mr.timeline.query` 输出全量模块位置信息(按 `start_sec` 升序);
  7) 位置清单展示格式固定为 Markdown 表格(`顺序 | 模块ID | 类型 | 开始(s) | 时长(s) | 结束(s)`),并在表格后输出 `总时长`、`过渡总时长`、`主画面总时长`。

pipeline 约束,调用的流程或者是某些地方必须要执行些什么,都需要有约束的描述:

### 对话打印格式(必须)

```text
[PRINT-意图] <本次调用的中文意图>
[PRINT-调用] mr.<interface_name>(<key_params>)
```

### 最小调用模板(每一步强制复用)

```text
[PRINT-意图] <中文意图>
[PRINT-调用] mr.<interface_name>(<key_params>)
[CALL] mr.<interface_name>
Mock Response: {"code":0,"message":"success","data":{},"next_state":"<state>"}
```

### 全量执行要求(必须)

- 当 `scene_count=N` 时,必须完整输出 N 次创意调用、N 次线稿调用、N 次镜头段落调用、N 次时间轴编排调用。
- 配色动效模块同理,目标模块每一条配色创建都必须单独输出调用与回包。
- 任何一步若未输出,视为「流程未执行到位」,必须补齐后才能进入下一阶段。

规则分级 P0 / P1

规则一多,Agent 容易「全读一遍、全当建议」。MenuReel 在 接口协议.md 里给规则打了 P0 / P1 标签,让 Agent 知道哪些违反就必须停:

级别 含义 示例
P0 违反即停止,不得继续后续调用 状态机跳步、批量创建、省略中间流程、输出格式不符模板
P1 重要但次于 P0,多用于错误码分类 1001 参数缺失、2001 演算失败

写法上,全局规范和调用前置规则标 P0,错误码定义标 P1。Agent 看到 P0 就知道「这条不能商量」,比平铺十几条「必须」有效得多。

模拟调用协议

「模拟调用协议」,具体规则如下。除了上面的打印格式和全量执行要求,P0 级规则还包括:

  • 必须按状态机顺序调用,不得跳步
  • 缺参或状态不合法时,返回错误码并停止后续调用
  • 禁止批量创建:每次调用仅允许创建一个对象(1 个素材 / 1 个镜头段落 / 1 个模块)
  • 2D 镜头段落有两条合法路径:创意图 -> 线稿图 -> 镜头段落,或 线稿图 -> 镜头段落
  • 模拟调用时不得省略中间流程;禁止用「同样方式继续」「scene_01~12」「…」「等」来合并步骤
  • 不符合「最小调用模板」的输出视为无效调用,必须立即按模板重发

失败路径也要写清楚,不然 Agent 跳步了你拦不住:

### 错误码

- 1001 参数缺失
- 1002 状态不允许
- 1003 约束冲突(元素数量 / 画布安全区)
- 2001 转场演算失败
- 3001 导出失败
- 4001 禁止批量创建
- 4002 镜头段落前置步骤缺失
- 4003 必填用户输入未完成
- 4004 中间流程被省略
- 4005 缺少创意编排确认

Agent 合并步骤时应返回 4004,没做创意确认就调 init_project 应返回 4005,然后停止,不能继续往下走。

无效输出强制重发

P0 规则里有一条:不符合「最小调用模板」的输出视为无效调用,必须立即按模板重发。光写规则不够,最好给 Agent 一个固定的纠错输出格式:

[PRINT-意图] 检测到输出格式不符合模板,当前调用无效,立即按标准模板重发
[PRINT-调用] mr.<interface_name>(...) -> INVALID_FORMAT

然后紧接一条符合模板的标准调用。这样 Agent 自己漏了 [CALL] 或 Mock Response 时,有明确的自我纠正路径,而不是悄悄往下跳步。

开工门禁

软件类 Skill 和 Blog 润色 Skill 最大的区别之一:Agent 要先当产品经理收需求,再当工程师调接口。MenuReel 里设了两道门禁。

第一道:用户必填输入

参数未齐全时,不允许执行 mr.init_project 及后续接口:

  • project_name
  • element_count
  • theme

缺参时优先用 AskQuestion 做结构化选择框交互,禁止丢一段「请按模板填写」的文本让用户自己填。新方案必须视为新会话,不得默认复用上一轮的参数;只有用户明确说「沿用上次参数」时才允许复用。

AskQuestion 降级链

结构化交互也会失败,Skill 里要写降级策略,而不是假设 AskQuestion 永远可用:

  1. 默认单轮全量提问,一次收集全部缺失字段
  2. 调用失败或未返回有效选择 → 重试 1 次
  3. 全量提问连续失败 → 降级为分批提问(每批 3~4 个字段)
  4. 仍失败 → 降级为文本追问该字段,写回后直接继续,不再二次确认
  5. 全部字段收集完成后直接进入下一阶段,不做参数总览的第二轮确认

隐式上下文

project_namethemescene_countduration_sec、画布约束等,在开工时收集一次即可,之后作为当前会话的隐式上下文全程携带,不需要每个接口都重复传参,也不出现在接口的入参/出参定义里。

Agent 只需要维护两件事:当前隐式上下文(项目参数)和当前 state(状态机位置)。接口调用只传该步真正需要的参数(如 shot_idasset_id),Skill 写清楚这一点,Agent 才不会每个 mr.shot.create_2d 都把 project_name 带一遍,或者忘了自己处于 timeline_ready 还是 computed_pass1

第二道:创意编排确认

在执行 mr.init_project 前,必须先给出完整的「创意与画面编排说明」,至少包含:

  • 总体创意主题与叙事结构
  • 每个画面的名称与内容说明(与 scene_count 一致)
  • 每个画面的时长分配
  • 每个画面的配色动效设计(出现 / 展示 / 消失)
  • 开场与收尾的设计说明

输出后必须向用户请求确认;用户明确确认前,不允许进入接口调用阶段。这一步是为了防止 Agent 凭猜测直接开干,后面改起来成本很高。

接口定义

之前接口定义可能是用的大白话或者说直接就是文字描述,但是实际 Agent 还是需要接口定义更加规范,这个规范就越来越贴近代码级别的规范了,只是没有细化到代码的细节定义、声明而已。

实际维护了两套文档,用途不同:

  • 子域文件(如 镜头段落.md):给 Agent 看操作步骤,「列出参数 → 输出完成」
  • 协议层接口协议.md):给 Agent 看状态约束、前置条件、错误码、全量接口清单

verbose 写法示例:

#### 创建镜头段落-2D

镜头段落的数据来源是 2D 素材(svg、png),通过此接口完成素材到镜头段落的转化

输入素材 ID(asset_id),输出镜头段落 ID 和基本信息(时长,画布占位)

接口名:`mr.shot.create_2d`

输入参数:`asset_id(来自 mr.asset.create_2d_line), element_count, layout(width_px,height_px), duration_sec`

输出结构:`code, message, data(shot_id), next_state`

- 1.列出用户的输入参数
- 2.输出「用户调用创建镜头段落-2D,完成」

compact 写法示例:

### 3) 镜头段落

#### `mr.shot.create_intro`
- 输入:`rows, cols, spacing_px, intro_offset_px, duration_sec`
- 输出:`shot_id`

#### `mr.shot.create_outro`
- 输入:`rows, cols, spacing_px, duration_sec, same_as_intro`
- 输出:`shot_id`

#### `mr.shot.create_2d`
- 输入:`asset_id(必须来自 create_2d_line), element_count, layout, duration_sec`
- 输出:`shot_id`

#### `mr.shot.create_3d`
- 输入:`asset_id, element_count, layout, duration_sec`
- 输出:`shot_id`

#### `mr.shot.query`
- 输入:`shot_id`
- 输出:`shot_meta`

- 状态:存在开场 + 收尾 + 至少 1 个主画面镜头段落后可进入 `shot_ready`

完整协议还覆盖素材(5 个接口)、时间轴(6 个)、配色动效(2 个)、项目 init / export 等,镜头段落只是其中一个域。接口命名统一 mr.<domain>.<action>,回包统一 code / message / data / next_state

Mock 换真接口

真接口上线后只换 [CALL] 背后的实现。具体可以分三层,Skill 里的流程约束一层都不动:

Skill(状态机 + P0 规则 + 错误码 + 反模式)
  ↓ 约束 Agent 怎么一步步走
[CALL] mr.xxx
  ↓ 当前是 Mock Response;上线后替换为:
MCP tool / CLI 脚本 / HTTP 调用

这和前一篇里说的 MCP vs Skills 定位一致:MCP 管真接口,Skill 管流程和经验。Mock 阶段验证的是「Agent 会不会按你的 pipeline 走」;接上 MCP 或脚本后,验证的是「真程序能不能接住 Agent 的调用」。状态机、打印模板、全量执行要求、错误码都可以原样保留。

Skill 迭代试跑

Skill 迭代试跑具体可以当成一套固定动作:

  1. 准备 3~5 条代表性话术:完整做一条短片、只改配色、重新做一条、缺参开工、中途改需求等
  2. 跑对话,记录 Agent 在哪犯规:跳步、合并步骤、跳过创意确认、口头说「已创建 10 个」而不输出调用
  3. 对症补 Skill:缺 P0 规则补 P0,缺错误码补错误码,缺反模式补反模式
  4. 真接口上线后,用同一批话术回归:只换 [CALL],看流程约束是否仍然有效

MenuReel 实际跑下来,这套 Skill 一天内就能跑通主流程,真接口落地后几乎不用再改 Skill 正文,说明迭代试跑比先把程序全做完再对接 Agent 省得多。

反模式

写软件 Skill 时,下面几类 Agent 常见偷懒行为,建议在 Skill 里明确禁止:

  • 批量创建:一次调用创建多个素材或镜头段落
  • 合并步骤:用「其余画面同理」代替逐次调用
  • 跳过确认:没做创意编排说明就直接 init_project
  • 手动重排演算结果:演算完成后 Agent 自己调 move_module 改位置
  • 改过渡模块时长:过渡时长以 query 结果为准,不允许 Agent 自行调整
  • 复用旧参数:用户说「再做一条短片」时,直接沿用上一轮 project 参数

Summary

给软件写 Skills,和给 Blog 润色写 Skills,复杂度不在同一个量级。简单 Skill 一个文件、几段 Prompt 就够;软件 Skill 需要背景分层、frontmatter description、多文件拆分、P0/P1 规则分级、状态机、模拟调用协议、开工门禁、隐式上下文、错误码和迭代试跑,才能把 Agent 的行为约束在可预期的 pipeline 里。

核心思路可以概括成几句:

  • description 和背景补触发词的盲区
  • P0/P1 分级 + 状态机防跳步
  • 模板、错误码和 INVALID_FORMAT 防省略
  • 隐式上下文 + AskQuestion 降级保证交互可靠
  • 接口定义趋近代码规范
  • Mock 试跑验证流程,MCP/脚本替换 [CALL] 接上真程序

程序接口还没全部 ready 时,先用 Mock 协议把 Agent 的执行方式固定下来,等真接口上线后再替换 [CALL] 背后的实现,Skill 本身的流程约束可以不变。

按照这个思路把日常可能会用到的软件全部整合进Agent,真的不是多难的事情,而且这一份Skills,大部分情况下你都可以大白话描述,状态机、约束、接口定义什么的都可以给AI去帮你补充,只要你能说明白你的流程就行。

实际把这一套流程完整跑通,一天都用不了,而且后面实际落地以后 Skills 基本没怎么修改过,验证得非常充分了。不过有一点要注意,Agent 的模型能力会影响 Skills 的发挥,如果是小模型或者一些比较弱的模型,理解能力堪忧,就算有上面的重重保障、防呆,还是会被跳流程或者胡言乱语,所以需要目前市面上常用的大模型才行。

Quote

Cursor

Blog增加搜索功能

2026-06-04 00:00:00

Foreword

博客文章越来越多,靠标签和翻页找东西越来越费劲。站点是 Jekyll 静态部署在 VPS 上的,不想为了搜索再挂一个 Elasticsearch 或者 Meilisearch,所以目标是:构建时生成索引,线上纯静态文件,浏览器里完成检索

试了一圈以后,最终用的是 Pagefind + 自建的子串索引 双轨方案。这篇文章记录选型过程、当前实现,以及和其他方案的对比,方便以后自己维护或者换方案时有个参照。

Pagefind

https://pagefind.app/

Pagefind 是 MIT 协议的开源静态站搜索库,和 Algolia DocSearch 那种「云端 API」不同,它完全跑在访客浏览器里:

  1. 构建阶段jekyll build 产出 HTML 后,执行 npx pagefind,扫描带 data-pagefind-body 的正文区域,按语言规则分词,生成倒排索引(.pf_index.pf_meta.pf_fragment 等),和站点一起部署。
  2. 使用阶段:页面加载 pagefind.js + WASM,用户输入查询词后同样在客户端分词,在索引里匹配、排序,再拉摘要片段显示。

可以粗浅地理解成:离线建好「词 → 出现在哪些页面」的表,上线后只在浏览器里查这张表。它保证的是正文进了索引、能全站检索,但检索单位是 token(词),不是「任意连续汉字串」

  • 这点和后面中文踩坑直接相关,说白了就是所谓的分词对于中文的适配度低,很多中文词分词不正确导致没有被索引

本站 pagefind.yml 里配置了 force_language: zh-cn,npm 依赖目前是 pagefind ^1.5.2(1.5 起对 CJK 有加强,仍解决不了所有短语场景)。

Pagefind +自建索引

整体是 双轨:Pagefind 负责广搜和 UI;search-index 负责中文 连续子串 的精确匹配。导航栏「搜索」+ Ctrl+K 打开同一个 Pagefind 弹窗,精确匹配结果插在 Pagefind 结果列表上方。

flowchart TB
  subgraph build_step ["构建:VPS deploy 或本地 build.sh"]
    J["Jekyll build"]
    HTML["_site HTML"]
    PF["npx pagefind"]
    IDX["插件 search_index_generator"]
    PFD["输出 pagefind 目录"]
    SH["输出 search-index 分片"]
    J --> HTML
    HTML --> PF
    HTML --> IDX
    PF --> PFD
    IDX --> SH
  end
  subgraph browser_step ["浏览器"]
    UI["pagefind-modal 弹窗"]
    PFJS["Pagefind WASM 检索"]
    WK["search-cjk-worker"]
    EXACT["精确匹配列表"]
    PFRES["Pagefind 结果列表"]
    UI --> PFJS
    UI --> WK
    SH --> WK
    WK --> EXACT
    PFJS --> PFRES
  end

构建链路

VPS 上 deploy.shgit pull 后有更新时执行:

  1. jekyll build --destination /usr/share/nginx/html
  2. npx pagefind --site /usr/share/nginx/html
  3. search-index 由 Jekyll 插件 _plugins/search_index_generator.rbpost_write 钩子里生成(Node 脚本 scripts/build-search-index.mjs 仅作备用)

本地开发可用 ./build.sh,步骤相同。

Pagefind改动

  • 正文容器:_layouts/post.htmlpost-containerdata-pagefind-body;标题、副标题、标签用 data-pagefind-meta
  • 忽略区域:导航、页脚、data-pagefind-ignore(见 pagefind.ymlexclude_selectors)。
  • 前端:footer.html 引入 pagefind-component-ui.jspagefind-modalnav.html 搜索按钮打开弹窗。

Pagefind 适合:英文单词、长文全文、模糊相关内容;摘要高亮、子结果锚点也是它自带的。

自建索引改动

中文博文里大量 造词、地名、产品名(如「限宽墩」「奥美品牌定位」),读者往往是「记得这几个字连在一起」来搜。Pagefind 会把查询拆成更小的 token,容易出现:

  • 搜「限宽墩」命中别的文章里的「限制」「路宽」「墩子」

  • 真正写有「限宽墩」的《天津自驾游》反而直接搜不到了

因此在 Pagefind 之外增加 子串索引

项目 说明
路径 /search-index/manifest.json + /search-index/2015.json2026.json
单条格式 [url, title, searchableText],无重复字段
可搜内容 标题、副标题、正文内所有 h1–h6 标题文字、正文纯文本前 800 字
匹配方式 indexOf(查询词),必须 连续子串 命中
运行时 打开搜索框后加载 manifest,Worker 并行拉各年分片,在后台线程检索,不堵 UI
展示 弹窗内「精确匹配(N)」列表,样式对齐 Pagefind 卡片

根路径 /search-index.json 只剩几十字节的指针:{"v":2,"manifest":"/search-index/manifest.json"}。全部分片合计约 880KB(单文件 JSON 塞全文,体积到 9.7MB,首屏解析卡死,就放弃了)。

相关文件:

  • _plugins/search_index_generator.rb — 构建分片索引
  • js/search-cjk-fallback.js — 挂接弹窗、调度 Worker
  • js/search-cjk-worker.js — 拉分片、子串搜索

Service Worker

博客开了 PWA,js/sw.js/pagefind//search-index/search-cjk-*.jsnetwork-only,避免旧索引被 SW 缓存导致「新文章搜不到」。缓存命名空间已迭代到 main-v5-,改版后需要用户注销一次 SW 或硬刷新。

踩过的坑

现象 原因 处理
新文章搜不到 SW 缓存了 /pagefind/ SW 对 pagefind 路径不缓存
search-index.json 404 仅本地 build 未部署插件产物 Jekyll 插件随 build 生成;deploy 检查 manifest
搜「品牌定位」有数量无列表 过滤逻辑藏光 Pagefind 结果 + 注入被重绘清掉 独立 pf-cjk-results 容器,去掉误杀过滤
一直「正在搜索」 单文件 9.7MB 主线程 JSON.parse 按年分片 + 仅打开搜索时加载 + Worker
「限宽墩」精确匹配没有天津篇 词在文末 ####,800 字截断未覆盖 索引增加全文标题层级文字

典型验证词:品牌定位(标题命中)、限宽墩(小节标题 + 正文)、奥美品牌定位(标题子串)。

子串包含 vs 分词

  子串包含(search-index) 分词(Pagefind / jieba)
规则 连续字符序列出现即命中 先切词,再按词匹配
适合 限宽墩、品牌定位、Su7 Ultra 营销、自驾、STM32 等主题词
误匹配 少(要求连续) 中文易拆字沾边
新造词 不依赖词典 词典没有则易切错

改善 Pagefind 中文 不等于只改 jieba:还要改查询侧分词、匹配是否要求连续、标题权重等;对个人博客维护一个 Rust/WASM fork 不划算。更务实的做法是:Pagefind 继续广搜,子串索引补短语

方案对比

方案 类型 中文短语 集成成本 运维 备注
Pagefind + search-index(当前) 静态双轨 子串轨准确 中(已落地) 仅 nginx 静态 UI 现成,构建多一步
仅 Pagefind 静态 静态 英文体验好,中文词组不稳
FlexSearch / MiniSearch 纯前端 可配 CJK encoder 或构建期分词 中高(自写 UI) 静态 索引逻辑自控,无官方弹窗
Lunr + 中文扩展 构建期索引 依赖分词 trimmer 中高 静态 Hexo/Hugo 常见,Jekyll 需自己接
hexo-tokenize-search 思路 构建 search.json 构建期 tokenize 静态 和 search-index 类似,需移植
Meilisearch / Typesense 独立服务 需 Docker/进程 体验最好,违背「纯静态」初衷
Algolia DocSearch SaaS 低(若符合条件) 云端 开源文档站为主,个人博客未必合适
Fork Pagefind 改中文 改 Rust/WASM 可做成子串或更好分词 很高 静态 MIT 允许,长期合并上游成本高

没有找到一个「魔改 Pagefind 中文版」的成熟 Fork;官方在 Issue 里讨论 CJK 子串(如 #987),上游演进可跟,不必私有维护一整条搜索引擎分支。

Summary

博客搜索采用 Pagefind(分词全文 + 弹窗 UI)+ 按年分片的子串索引(中文短语精确匹配)。构建时 Jekyll 与 Pagefind CLI 各生成一套静态数据;使用时同一弹窗先展示精确匹配,再展示 Pagefind 结果。中文技术文里造词、固定词组多,子串包含比单纯优化 jieba 更贴需求;Pagefind 仍保留,负责英文、长文深处和模糊检索。若以后文章量或需求变化,可以考虑只强化子串轨(甚至去掉 Pagefind),或给上游贡献 CJK 子串模式,但现阶段双轨是性价比最高的平衡点。

由于有AI,所以Pagefind fork以后修改的方式也试过了,本地测试走通了,拿我整个Blog的词都做过测试,分词效果比官方好多了,也就不需要什么search-index了,但是上线的时候发现有问题。Blog都是在老VPS上了,CentOS,Pagefind编译是在另外一个VPS上,编译后的结果呢,CentOS跑不了,老VPS呢内存太小,跑不了Pagefind编译,好家伙给我死锁了。

要动老VPS的环境,相关要重新部署或者修改的内容有点太多了,于是就放弃了,AI修改Pagefind还是比较简单的,下次VPS换了再换成私有Pagefind也可以。

Quote

cursor

本文80%由AI生产

测试管理工具

2026-06-03 00:00:00

Foreword

测试用例的管理,测试用例一定是关联到某一个需求的,基于这个需求产生的测试用例

但是呢,需求是一期一期做的,可能一个需求里混了一些不相干或者零碎的其他功能内容在里面

如果每一期的用例混在一起,对应的测试用例集就混了一些不同方向或者归属的用例

测试人员的期望是测试用例后期可以按照功能或者什么标签之类的进行划分,需要的时候回归测试某些功能的用例,然后根据情况有一部分还要转化为自动化用例,作为保底

  • 实际测试用例随着功能变更,过往的测试用例也需要变更

测试用例同时还要满足评审的需求,有评审的流程和结果

自动化用例和普通的手工用例可能还会混在一起,这里就需要再进一步进行标签上的区分

测试用例测试完成以后,还需要对应的报告输出。这些是基础测试的管理工具的需求,市面上是否有工具可以满足或者其他某些工具可以稍微改造一下来满足。

Allure TestOps

https://qameta.io/

image-20260603170354995

TestOps,界面比较简单,价格比较美丽,39刀/月/人,稍微有点贵的离谱了,体验sandbox里只有guest身份,很多东西不能操作,局限性太大了,还是要试用一下。

  • 试用必须企业邮箱

左侧导航栏,中间是所有用例,右侧是测试用例的详细信息,具体怎么操作,预期结果是什么,如果点每个标签就可以自动进行同标签筛选

导航内的功能就有测试计划,测试每一轮的结果,

TestOps的试用稍微有点问题,激活以后无法进入激活的测试空间,DNS无法解析,这个是限制了中国?

  • 国内默认不给解析,需要额外翻墙处理

image-20260603172458228

看样子,TestOps是每个用户给一个独立的实例去跑,这个实例启动要一会,所以账号激活以后还进不去,要等一会

TestRail

image-20260603173420874

TestRail的注册激活就人性化一些,看起来也是要实例启动以后才能进入,但是他有一个界面给你展示,TestOps就缺少了这些

TestRail暂时没有对国内做限制,可以直接访问

image-20260603173622052

TestRail可以创建demo项目,类似样板项目,快速感知TestRail能做的事情

image-20260603173854256

一目了然,不过TestRail看起来页面还是比较老的技术,不是响应式的,每次全页面都重新加载,有点老气了。

  • 这种重新加载用起来感觉好累,卡卡的感觉

用例一多,加载起来确实很慢,转圈都转半天

整体管理逻辑和TestOps差不多,就是难用了一些

image-20260603175015777

基本标签,是否可以自动化,分配给谁做,这些都有,用例是基于Section和Case进行管理的

image-20260603175530475

TestRun里面是每个用例可以手动切换状态,每次切换都需要commit一下,说明,确实有点太重了,一天测几百个用例,这里来回点还是挺麻烦的

Xray

https://www.getxray.app/test-management

Xray是基于Jira家的组件,这里就不测了,要绑定Jira

MeterSphere

https://metersphere.io/

https://github.com/metersphere/metersphere

image-20260603183148680

不愧是国内的,符合中国体制的宝宝,demo演示直接给账号密码,本身有社区版,开源,功能看起来支持得也还行

image-20260603183302180

导入用例,有现成模板,用AI转化一下就行

image-20260603183358681

然后用例内可以关联需求,不过这个也是一个用例操作一下,有点傻了,缺少批量的那种关联,需求也只能关联一个链接,没有具体内容的展示

image-20260603183600782

用例执行,也是类似TestRail,每次执行都有一个commit

image-20260603184214543

MeterSphere也有一个评审的流程,可以大家过这个评审

但是关于怎么触发自动化测试,MeterSphere界面上至少是一点都没有,看不出来怎么触发。官方说法是可以通过jenkins插件触发

在MeterSphere中,可通过Jenkins插件实现特定条件或操作触发自动化测试,具体方式如下:

  1. Jenkins集成触发
    • 安装MeterSphere Jenkins插件后,在Jenkins任务中添加MeterSphere构建环节。
    • 配置MeterSphere平台认证信息,选择指定项目下的接口测试用例。
    • 通过Jenkins的定时任务、代码提交触发(如GitLab Webhook)或手动执行等方式启动测试。
  2. 关键组件协作
    • Task Runner:统一调度接口测试任务。
    • Result Hub:处理测试执行结果。
    • Kafka:接收测试结果数据供后续分析。
  3. 扩展触发方式
    • 通过Chrome插件录制Web请求生成JMeter脚本并导入MeterSphere,结合CI/CD流程触发。
    • 使用IDEA插件同步接口定义,基于代码变更自动触发测试。

更多细节可参考架构图:组件说明

用例上没有很明显的自动化标签,估计要实测看结果了

Qase

https://www.qase.io/

弱智东西,非企业邮箱输入会提示错误,但是就是不告诉你错误原因,企业邮箱立马就成功

image-20260603194609871

界面也还行,比较简单的,也算现代化的

image-20260603194812544

Qase的测试内容的描述更符合我预期一些,自动化的标签是有的,不过似乎没有和需求挂钩的地方

AgileTest

https://agiletest.app/

image-20260603201047001

AgileTest的这个价格稍微有点低,一个人1.5刀,这不是把其他人架起来烤嘛,不过Agile 也是ATLASSIAN公司的,需要配合Jira一起用,这个有点麻烦,我没有Jira,只能放弃了

Testiny

image-20260603201722835

https://app.testiny.io/

Testiny,没想到有国外的软件,中文适配的还行

image-20260603201927125

也有类似的问题,缺少自动化的标签,自定义标签没看出来哪里能设置,感觉他是靠树形结构去做归类的

Testiny的自动化也是通过接口来实现的,不过这里是把自动化的结果报告进行了同步收集,似乎并不能直接在Testiny这里直接触发自动化运行。

aqua

https://aqua-cloud.io/

aqua的价格有点逆天,产品89欧/人,测试管理89欧/人,测试19欧/人,注册有点麻烦要国外手机验证,虽然我有,但是我懒得注册了

testmo

image-20260603203806855

https://www.testmo.com/

testmo感觉还行,据说被testrail背后的公司收购了,然后一片唱衰,都说会被搞垮。

看了一下基础用例管理方式依然是通过文件夹的形式进行分类的

QAlity Plus

https://soldevelo.com/products/test-management-for-jira/

只适配Jira

Excel

传统测试工具,Excel其实是能满足上面所有内容的,无论是和需求关联、分组、打标签、评审、自动化,它都可以,只是有点没系统,所有都是人工约定的,没有很强的模板约束,其数据本身的历史追溯有点困难,文件存储本身也需要解决。

其他

其他类文本工具,比如notion、wolai、plane、各种智能表格等等,进行一定的改造也能满足测试的需求,就是有点不专业,很多流畅性的东西需要外部处理

其他一体的平台也包含一部分测试平台的内容,只不过这里我们已经有了其他内容的补充,暂时不需要那么重的一个平台,单纯能管好测试用例就够了,其他Devops有其他工具做了。

Summary

目前看下来MeterSphere可能是其中较为好用的,至于迁移成本,现在有AI干活的情况下,只需要你能把以前的数据导出来,那么这个成本就非常低,给好上下文背景,测试平台的接口或者模板格式给好,让AI把用例转移一下形式,其实非常容易,很简单就能迁移到新平台上

测试管理平台还看到了一些别人的需求,这里还包含了普通的小白测试人员,对应的任务分配,时间管理等等也会被集成到这个管理平台上,相当于可以量化考核每个测试人员的工作量

同时还有一个缺陷管理,测试出现的问题集中到缺陷中,但是大部分这个都做得比较弱,因为这个问题一般都需要挂到对应的任务管理系统中,比如Jira、Plane里面,这样好安排工作一些。

image-20260603204423812

这个兄弟说得挺好的,测试是否需要管理工具,是否需要这么多细节流程来确认测试工作人员是否工作到位了,这个东西和产品的质量,成本需要找到一个平衡点,大多数公司都没有找到。挺无奈的,研发、测试一环扣着一环,都是对上一步结果的再次验证和纠正,只要有人在这里,只要你不是三体人,那表达一定会有出入,最终的路径一定要经过二次甚至三次验证才能出结果,这就无形中抬高了很多成本了。

还有一种方式这个测试体系或者架构从一开始就是自己构建的,那么在这种情况下,结合一下AI,把excel表进行数据库化,然后加上一个前端作为展示和修改界面,其实就可以满足大多数测试的需求了,这个架构也不是很难,现在AI做其实很简单了。这种方式定制化程度很高,基本啥需求都可以自己搞定。

Quote

google

https://www.reddit.com/r/QualityAssurance/comments/197qvo7/thoughts_on_best_test_case_management_tool/?show=original

奥美品牌定位有感

2026-05-21 00:00:00

Foreword

之前参与了一下奥美的品牌定位会议,感觉还挺有意思的,至少在做事的方法论上还是可以的,记录一下

Ogilvy

https://www.ogilvy.com/cn/

奥美(Ogilvy)是全球知名的营销传播集团,1948 年由“广告教父”大卫·奥格威(David Ogilvy)在纽约创立。它以品牌战略、创意广告、公关与数字营销见长,长期服务跨国企业与本土头部品牌。如今奥美隶属于 WPP,属于典型的“全球方法论 + 本地执行”型咨询与传播公司。

负责给我们上课的同时也是为华为品牌战略服务的负责人

第一课

第一课,说明品牌是什么,干什么,弄清品牌的定义。本质上就是在做小培训,先教会非品牌出身的人,让他们明白这是什么,而不要乱带节奏,不要自说自话。

区分清楚老板、公司和公司品牌这几个概念,往往很多时候,都被混为一个,老板会觉得公司就是代表自己,很多品牌决策都过于主观,而没有遵守公司品牌、公司人格这个独立的东西。

说是进了这门不区分上下级,但是出门都要工作、生活的,有些东西被刻进骨子里了,不是说可以就行的。

第一课主要就是摸底,确认好公司内大家的诉求是什么,品牌的一些共性或者基础内容都有什么,然后说明要得到什么结论。

比如,品牌短句,长句,愿景,使命,性格,logo,这几个最最基础的东西,而这些内容奥美本身已经有一个初步的定义了,毕竟是做过很多项目的,大概理解还是有的,只是对于领域内的一些细节可能了解不是很多。

之后就是奥美,给出他们的一些想法,供我们参考。

做的几个活动,主要靠策划人引导,然后将公司的人分几个组,不同组进行发表,虽然说这不是辩论会,不是比赛,但是人嘛,总是希望发表以后得到认同或者说同化他人。这个过程中,其实也是各种人被洗脑,被忽悠的一个过程,简短的几个概念词被反复提起、拆解、强调,不懂的人会被快速同化。

无论是组内讨论还是组外讨论,每个组都安排了一个奥美的助手,并且全程录音,组内讨论后续也会被他们拿去分析,助手也会在组内引导大家的思考逻辑,纠正一些跑偏的讨论方向。

总体来说:

  • 求同存异,求最大公约数

  • 结果可能是少数服从多数
  • 活动过程中允许你发表意见,说服他人,说白了会闹的孩子有奶吃,表现力强的人往往会影响其周边的人,进而成为一个共同体,导致组与组之间稍微有点点对立

从培训形式来说挺好的,至少只要你主动,参与感是有的,但是如果你全程冷漠,那么这个事情就真的跟你没关系了

第二课

第一课的输出,奥美需要分析一段时间,然后第二课就是基于之前的结果进行总结,再给出几个选项,让所有人选。算是缩小了品牌定义的范围,在这里进行少数服从多数的投票了。

这里其实还有一个隐含标准:不是只看谁说得好,而是看哪个表达更容易被用户理解、更容易在市场传播中复用。

从结果来说,大家基本趋同了,总结出来的品牌短语、长句,基本一致了,只是略有偏差,这些后面定死就没啥问题了。

还有一些细节,可以看,奥美在做分组的时候,是有特意安排过的,来平衡某种角色或者背景的人过于集中的问题

第二课和第一课的分组又不相同,再次进行各人的意见融合

不过对于企业人格,品质等方面的用语,明显意见差异特别大,最终得到的结论和我自己的也有差距,这里过于文学性,咬文嚼字了,带来的区别有点太小了。

方法论反思

奥美这套流程在“统一认知、快速收敛”上非常有效,尤其适合内部观点分散的团队。但它也有天然边界:当目标是找一个更激进、更差异化的品牌方向时,多数共识机制往往会把表达磨平,最后得到一个“大家都不反对,但也不够锋利”的答案。

另外,活动结果会受到现场话语权影响。表达能力强的人更容易带节奏,谨慎或内向的人观点容易被淹没。为了避免这个问题,最好在讨论前加入匿名写卡、独立评分,再做集中讨论,这样结论会更接近真实共识,而不是现场情绪。

最后,品牌定位的完成不等于品牌建设的完成。真正难的是把定位落实到产品叙事、销售话术、市场传播和组织行为里,并且在 3-6 个月里用线索质量、转化效率、用户心智反馈去验证。

落地清单

如果只输出了一套品牌话术,而没有配套动作,最后大概率会变成 PPT 工程。至少要做下面几件事:

  • 产品侧:把品牌主张映射到产品功能命名、版本发布叙事、官网卖点排序
  • 销售侧:统一首句自我介绍(结合我自己碰到的销售,这个是有的)、行业方案开场、异议处理话术
  • 市场侧:统一公众号、官网、短视频、展会物料的表达口径
  • 组织侧:把品牌关键词写进招聘 JD、绩效沟通和新人培训材料

这几项如果没有同步,品牌定位再好,传到市场端也会失真。

常见失败信号

复盘下来,品牌定位项目最常见的失败,不是方向错,而是执行断层:

  • 内部会议里都认同,外部客户却听不懂
  • 对外说的是一套,对内做的是另一套
  • 每个部门都说“理解了”,但实际物料和话术各说各话
  • 三个月后没人再提这件事,品牌项目自然失效

所以品牌定位不是“一次性决策”,更像是“持续校准机制”。如果没有持续复盘和纠偏,前面的共识很快就会被日常业务冲散。

Summary

有些时候公司是技术主导的,导致很多东西过于偏向技术化了,而没有考虑到当前市场、普通人对于这些词汇或者概念的接受程度,这种情况就需要品牌引导,拉回来一些

有时候公司领导过于固执,或者这个体系过于僵化,没有人可以成为破局者,此时就需要外部的人介入,从他们第三方的视角来看这些事情,从而推动变革,这也是为什么一些顾问公司能存在的价值吧

Gitlab制品库实践记录

2026-05-20 00:00:00

Foreword

之前理解的制品库就是把CI的中间过程进行存储,虽然也包含了最终发布的部分,但是总觉得没啥大用,就没在意。后续发现其实从工程到生产环节有很多东西都需要进行交付,之前只关注了最核心的产品内容,周边一些零碎的东西,其实也需要被统一管理。而这个时候如果有很多个仓库需要一起进行打包、管理,这个就非常复杂了,而借助CI和制品库就可以通过规则把这部分内容统一管理起来,发布时进行大包发布即可,这些零碎的小工程也没有强耦合,可以随意发挥

背景

生产时需要管理的不只是单个 .bin或者.exe,而是一套 已验证可一起上线 的产品组合:

  • 硬件烧录固件(SOC、MCU、FPGA等)
  • 测试治具固件
  • 其它治具与工具(RFID、MAC、IP、参数等配置工具)

各组件由不同负责人维护、版本节奏不同,因此适合 GitLab 多仓库独立开发,再通过 manufacturing-kits + manifest 在发布时打成单一制造包,并自动生成与发布说明。

目标:组件独立迭代方式

sequenceDiagram
  participant Dev as 组件负责人
  participant Repo as 组件 GitLab 仓库
  participant REL as Release 页面

  Dev->>Repo: MR 合并 / 本地构建
  Dev->>Repo: 打 tag,创建 Release
  Repo->>REL: 上传附件 + 填写 Release Notes

理清思路:

Gitlab 社区版没有制品库或者制品这个概念,但是我们可以通过Gitlab仓库本身的功能,建立一个制品仓库,把制品的manifest文件和对应的CI动作都集成在这个仓库里,需要发版的时候只需要触发这个仓库进行CI即可。

而对于各个仓库来说,完全是不知道这个制品仓库的,从而实现解耦。

生产物料清单

首先需要确认实际生产需要的物料清单,并以此为准进行模板制作和规则约束

分类 组件 ID(建议) 负责人 是否必选
MCU烧录 fc-mcu 主控 A 必选
SOC烧录 fc-soc 核心 B 可选
测试治具 fixture-mcu MCU治具 C 必选
测试治具 fixture-soc SOC治具 A 必选
其它治具/配件 rfid-fixture RFID工具 B 可选
  mac-fixture MAC工具 C 可选
  ip-fixture IP工具 A 可选

命名规则:{产品线}-{YY.M.D},例如 P1-26.5.20 → 对应 Git tag / Release 名。

  • 一般来说生产是按批或者订单进行的,只涉及到生产改动时这个进行改动即可

仓库结构

往往一个仓库可能要应对多种产线的情况,此时仓库保持独立,而不是submodule就很关键

flowchart TB
  subgraph component_repos [各组件独立 GitLab 项目]
    R1["fc-MCU-firmware"]
    R2["fc-SOC-firmware"]
    Rn["... 每个组件一个或一类一个"]
  end

  subgraph meta [发布与制造]
    MR["manufacturing-kits<br/>产品线 P1 / P2 ..."]
    DOC["自动生成 P1-26.5.20.md<br/>即治具更新内容"]
  end

  subgraph registry [制品]
    PKG["各组件 Release 附件"]
  end

  subgraph line [产线]
    OFF["脱机固件升级设备 / 工位"]
    LOG["生产追溯 DB 或 MES"]
  end

  R1 -->|打 tag + Release 附件| PKG
  R2 --> PKG
  MR -->|读 manifest,从 Release API 拉附件| PKG
  MR -->|打 Kit Release| PKG
  PKG --> OFF
  OFF --> LOG

可以看到这种情况下组合打包,本质上需要一个manifest,这个里面规定了打包的内容

仓库划分

建议仓库划分方式,但不是完全说死的

类型 做法
产品固件 一类一仓,如 P1/fc-mcu-firmwareP1/fc-soc-firmware
治具固件 可合并为 P1/fixture-firmware(多 bin)或按治具拆分
工具 P1/ip-burn-tool 等独立仓(版本节奏与固件不同)
制造包 一个产品线一个 meta 仓:P1/manufacturing-kits无业务代码,只有 manifest + CI,也可以多产线一个仓库)
文档 治具更新 Markdown 不手写维护,由 CI 从 manifest + 各组件 Release description 生成

「如有」组件:manifest 里 required: false,未参与本次 Kit 则整段在生成文档里标 「本次未更新 / 沿用上一 Kit」

manifest模板

manufacturing-kits 仓库中,每个 Kit 一个 YAML,与模板一一对应:

# kits/P1-26.5.20.yaml
kit_id: P1-26.5.20
product_line: P1
release_date: 2026-05-20
previous_kit: P1-26.4.12        # 用于「相对上一正式版变更」
validated_by: [A, B]            # 集成测试通过签字(审批人)
offline_upgrader_min: "2.1.0"   # 升级设备最低版本(若有)

components:
  fc-mcu:
    release_tag: v123.123          # 对应组件仓库 Releases 的 tag
    artifact: fc-mcu-v123.123.bin  # Release 附件名(需含后缀)
    sha256: "..."
    required: true

  fc-soc:
    release_tag: v45.6
    artifact: fc-soc-v45.6.bin
    required: false                # 本次未改则可 omit 或 inherit_from: P1-26.4.12

  fixture-mcu:
    release_tag: v2.0.1
    artifact: fixture-mcu-v2.0.1.bin
    required: true
  # ... 其余字段与模板章节同名

规则:

  • 必选组件:Kit 发布前必须在 manifest 里 显式 release_tag + artifact,且组件仓库 Release 已存在
  • 可选组件:未列则 CI 从 previous_kit 继承上一版引用(文档里写清楚「沿用 P1-26.4.12 之 fc-soc v45.6」)。
  • 各组件仓库由 owner 在 Release 页发布附件并填写说明;制品库 CI 不编译,只从 Release 拉文件。

示例

模板字段 来源
文件名 P1-26.5.20 kit_id
版本:v123.123 各组件 release_tag
固件文件:a.bin artifact(Release 附件名,需与页面上名称一致)
更新内容 1/2/3 组件 Release description;或 manifest 手写 changelog
@负责人 manifest owner + GitLab CODEOWNERS
「如有」 required: false + 继承策略

发布物目录示例(CI 打 zip):

P1-26.5.20/
├── MANIFEST.yaml              # 机器读
├── 治具更新内容-P1-26.5.20.md  # 人类读,版式同现模板
├── CHECKSUMS.sha256
├── firmware/
│   ├── fc-mcu-v123.123.bin
│   ├── fc-soc-v45.6.bin
│   └── ...
├── fixture/
│   └── ...
└── tools/
    └── ip-burn-tool-...

产线 / 升级设备:只部署 kit_id 对应 zip(或从内网按manifest 拉取)。

各仓库的动作:

步骤 动作 角色
1 manufacturing-kits 新建 kits/P1-26.5.20.yaml,填写各组件 release_tagartifact 发布负责人(如A牵头)
2 开 MR → 触发 集成流水线:校验 Release 存在、拉取附件(若有自动化测试可在此跑) CI + 各 owner 审批
3 MR 合并 → tag P1-26.5.20 发布负责人
4 CI:生成 Markdown、打 zip、创建制品库自身 Release 自动
5 通知生产:仅允许 P1-26.5.20;脱机设备刷入该包 生产 / 工艺
6 每台(或每批)记录:SN ↔ kit_id ↔ 时间 ↔ 工位 生产 / MES

manufacturing-kits 仓库目录

manufacturing-kits/
├── README.md
├── catalog/
│   ├── components.yaml
├── kits/
│   ├── P1-26.4.12.yaml
│   └── P1-26.5.20.yaml
├── schema/
│   └── kit.schema.json          # 校验 manifest 字段齐全
├── templates/
│   └── 治具更新内容.md.j2       # Jinja2,版式与现模板一致
├── ci/
│   ├── collect-artifacts.sh
│   ├── generate-release-doc.py
│   └── validate-kit.sh
└── .gitlab-ci.yml

.gitlab-ci.yml 阶段示意:

  1. validate:schema + 检查各组件 Release 是否存在
  2. collect:按 manifest 从各组件 Release API 下载附件
  3. document:渲染 更新内容-P1-26.5.20.md(可合并 Release description)
  4. package:zip + sha256
  5. release:制品库打 Kit 的 GitLab Release

注意manufacturing-kits + manifest

  • 不是 GitLab 自带的产品功能,需要自建一个 GitLab 仓库 + 写一点 CI/脚本;GitLab 提供的是「存清单、跑流水线、存制品、发 Release、做审批」这些积木。

catalog

manufacturing-kits的仓库里不仅仅要有各个kit的配置文件,由于各种代码仓库本质上是公用的,所以需要有一个目录层作为更上级,描述各个仓库的路径或者包名什么的,作为公共使用。

# 组件 ID → GitLab 项目(仓库路径在 catalog,Kit 里只写 release_tag)
fc-mcu:
  project_path: code/Firmware/fc-mcu
  project_id: 101              # 可选,API 用 id 更稳
  artifact_type: bin
  default_artifact: fc-mcu.bin

fc-soc:
  project_path: code/Firmware/fc-soc
  project_id: 102
  default_artifact: fc-soc.bin

ip-fixture:
  project_path: code/Facility/onion_ip
  project_id: 205
  default_artifact: onion_ip.exe
  branch_is_product_line: true # Release tag 建议形如 X1/v1.0.0

fixture-mcu:
  project_path: code/Facility/fixture-mcu
  project_id: 210
  default_artifact: fixture-mcu.bin

对应的CI程序就需要这么执行

读 Kit → 拿 fc-mcu 的 release_tag
     → 查 catalog 得 project_path=code/Firmware/fc-mcu
     → GET /projects/.../releases/{tag} → 按 artifact 名下载 assets.links

制品模式

有了上面内容以后,就可以给Cursor自动去完成制品库的文件编码了,但是第一次制品的流程,我发现有点问题。

AI默认你的制品库的CI流程是从各个仓库拉代码,然后编译,产生目标文件,然后再打包。其实在生产这里稍微有点点不同,这里各个仓库本身可能不完善(涉及到n多个不同环境),甚至CI流程都不太行,所以这里优先直接拿release页面的文件,而不是把打包编译的流程全跑一遍。

  • 对应的release页面这里就需要对应的owner去手动发布,并且编辑更新的内容

基于这个问题,又让AI改了一下制品库的模式

实际测试

Runner

如果要用Gitlab的CI流水线,那么要先配置好 Runner。可以用 VPS,也可以在内网 Windows 机器上装(例如 shell executor + Git Bash),在 GitLab 项目里注册并授权即可

image-20260519170233610

runner可以通过官网下载,然后配置

https://docs.gitlab.com/runner/install/

.\gitlab-runner.exe register  --url https://gitlab.xxxx.com  --token glrt-xxxx3d32-6LxN9

image-20260519172801808

runner有一个小细节要注意一下,经常容易卡住流水线,无法正常跑下去

你的 CI 里三个 job 都 没有 tags:,属于 untagged job。

若 Runner 配置了标签(例如 dockerbuild),且 未勾选「运行未打标签的作业 / Run untagged jobs」,该 Runner 不会接这些 job,界面就会显示「没有可用 Runner」。

建议最好不要在windows上跑runner,很多问题,cmd在最新的runner上不能用,powershell也会有一堆问题(最好用pwsh),建议换成git bash来跑

image-20260519190616223

Job运行时需要访问到各种项目的页面,对应的这些项目就得授权过去,可以在CI/CD的Job token中进行设置

Release

调试了老半天,流程总是走不通,不是这有问题,就是那有问题。

  • 沙雕AI打zip包,他打个tar包改名叫zip,后续流程中验证zip包,怎么验都错,但是我自己用zip解压可以正常打开,查半天

后续仔细看了一下发现是Gitlab的一点小问题,AI自己根本搞不明白为什么

image-20260519212352512

这个Release中软件包是只有名称,没有后缀的

image-20260519212405659

而CI文件里写的是需要后缀来识别的,那么一旦子仓库的发布内容不包含这个后缀,那么对应的制品库就无法正常跑下去,这个破问题弄半天,AI自己把自己绕进去了。

除了这个问题,Gitlab还有一点不如Github,发布页面不能直接引用仓库内的文件,不能直接添加资源,不能直接上传,这就导致你要把发布页面包含仓库内的东西就必须走Gitlab CI或者是手写一个脚本利用API来上传文件,然后这里还有坑点。这个你后上传的文件,是有权限的,走CI的那个流水线似乎没权限,访问不到这个链接,而要解决这个问题就还得给CI的脚本配上 owner 的 token,一环扣一环。

  • 实际上如果走Gitlab内部的上传库,runner是可以正常访问的,生成物也是能正常访问的

  • Gitlab的Release只愿意做一个纯链接页面,而没有一个实体存储的对象

子库CI不编译

现在最好的解决办法就是子仓库用Gitlab的CI,但是不编译,仅仅做打包和发布,编译的内容在仓库内建立一个Release文件夹,把每次发布的文件存储到这个里面,由Gitlab CI进行打包和发布即可

制品库环节就和上面说的流程一样走即可

Summary

所以如果要走Gitlab,那么就得全流程最好都走,想中间偷个懒或者不走Gitlab要解决的问题就有点多了。

Quote

Cursor

天津自驾游

2026-05-19 00:00:00

Foreword

周末小米举办了天津V1国际赛车场的活动,五一前就已经报名了1节练习+1天竞速,然而天公不作美,很多安排也不如预期,就改做自驾了。

天津

V1国际赛车场

image-20260518201241106

V1赛车场这次开放的是最长路段,4.2km,接近街道的赛道也给开放了,但是本质上挺危险的,V1赛车场虽然是F2级别,但是这个缓冲区的级别和凯泽差不多,没有波波池的缓冲,都是类似水泥地或者马路的缓冲区,减速可能不理想

先在模拟器连了一周,不四轮出白只能开到2分01,更快可能就有点作弊了,之前以为只是模拟器没有建模波波池,实际去了真场地,真的没有波波池,可以走偷鸡线路。

去天津之前已经基本确定了,练习日可以玩,竞速日会下雨,打算把竞速日退掉,没想到说了半天,不给退。最蠢的是官方举办的活动,竟然赛道地图没上线,要玩家现场自己画,真尼玛离谱。你练习日不行就算了,怎么竞速日也不行呢,包场2天,一个地图画不出来,这还是提前了半个多月预告的活动,真不知道在玩啥。

练习日

只报了最后一节练习,没想到那么多人,先是教练带着走线走了2圈,然后上去以后画赛道,画3圈,然后才能正式跑,这会已经掉了一些电了,人又多,根本跑不开

image-20260518202718163

跑到2分10秒以内应该很轻松,刹车点都比较早,而且一圈遇到三四个车都是常态。练习一节,1小时,这个时长也很抽象,中间去充电,回来又满是人,我直接连续跑了,最后剩下百分之十几的电下场了,缺电以后后面怎么开都是2分12秒或者2分11秒,很难比刚开始快了

竞速日

image-20260518203344881

不出所料,竞速日全天下雨,我的水平和开法,根本扛不住一点spin,天津这个地方spin我大概率会上墙,所以果断选择放弃,自驾一下市区更有意思

自驾

天津的这几个景区基本都在一块,走路大概就是一两公里就能到,反而是开车堵得要死,都是落在老城区里,都是单行道,车又多,还下雨

静园

image-20260518203721993

静园是溥仪幻想复辟时期所住的地方,很洋气,也很符合当时他有点崇洋和后续希望借助外部力量进行复辟的思想境界。静园能看的内容比较少,现在基本都是后来仿制的,中间溥仪离开以后这个地方几经周转,曾经破落得不成样子,溥仪死后才被重视重新修缮。

张园

image-20260518204112695

张园可得好好夸夸,算是我逛这些小景点里做得最好的了。门票都是一样的30,但是体验大有不同。

image-20260518204032223

张园每天固定时间都有表演,每场间隔大概一个半小时,实际演出接近一个小时,纯免费。表演的内容也不是尬演,而是带入了当时的场景,同时也有点类似景点讲解的导游。

表演融入了天津的一些艺术形式,现场互动做得很好,再加上现场来的基本都是偏向大学生、高中生,年轻的一代,确实很有活力,对比我等和一些老年人过来,都有点感叹。这种形式的思想、历史教育,确实非常不错,不是空喊口号,而是带你亲临当时的环境。

image-20260518204021236

小屋子里人也不多,大概八九十人,口号喊得激昂。现场的人也能参与表演,情侣甚至都给你准备了结婚仪式,虽然很短,但是他有,最后还有大合照,加群,抽奖,奖品是另外一个景点的门票,说是这个景点是他们相同团队做的,实际很拉跨,后面会提到。

不仅仅是有表演,本身展馆里就设置了一个剧本杀的谜题,而各种线索就隐藏在环境中,各种历史讲解中,你想解开谜题就要去仔细看历史,了解历史,我在现场看基本只要是年轻一些的面孔,都是人手一份,每个人都在解谜

同时展馆的后面也专门布置了一个剧本杀的场所,至于何时开就不知道了。

原汇丰银行旧址

image-20260518204831985

原汇丰银行旧址,就是张园同团队的另一个历史馆。打车的时候司机就说不知道这个地方存在,距离张园没多远,2km不到,我就感觉有点不对劲,可能这里内容不行。票价也不贵,30,等到演出开始了才进去。然后就是一个女演员,一个人表演完所有内容,非常尴尬,特别是开场的舞蹈都是演员和这种衣架做的假人互相表演,后续的内容也基本就是舞女的独白,没有观众的互动,各个场景其实做得也很浮夸,很塑料质感。

虽然是同团队,只能说这里踩坑了,怪不得游客这么少,没人来,类似张园其他项目这里直接就是没有。

五号院相声

image-20260518205821426

去得比较晚,已经开场了好一会才进去,错过了一点。不过现场人很少,基本可以随便坐,也没本地人,都是游客过来听的。

一共三个节目,第一个单口快板、第二个男女对口相声、第三个也是个对口相声。现场互动还是蛮多的,既有传统相声的冒犯,也有一些脱口秀的内容。

看着演员都挺老成的,实际他们自己说才刚20岁,完全想不到,都是十来岁就出来练相声,到这里表演了的。现场有个女生笑得贼搞笑,导致演员台上都被她感染了,不过演员的现挂能力还是有点问题,稍微容易被尬住,而且有些要控制自己的地方没好好控制,相对的相声老梗、老段子还是有挺多的,新内容比较少吧,而且节奏或者包袱抖来抖去,老是那几种冒犯的形式,对比脱口秀确实有点老东西的感觉了。

总体上还是值得一去的,他们也顺便嘲讽了一下周边的几个景点,比如瓷房子、xx故居,让游客别上当。

image-20260518210122160

现场点了个瓷缸喝大碗茶,有点那个意思。

瓷房子

image-20260518210929448

没进去,只在外面拍了一下照片,大概就是各种瓷器来组成房子的各种部分,只能说博眼球,而且这个是现代后做的,本身不是真的老东西。

张爱玲故居

这个故居有点纯蹭的意思,是小时候的房子了,现在主要是咖啡馆,需要消费入场,不去也罢。

其他

天津之眼

image-20260518211326775

路过两遍,但是周六晚上竟然不亮灯,不知道为啥,反正没拍到

观澜角/东堤公园

image-20260518211517923

海边公园,看着建设得挺好的,人少,风景又好,可惜最后没时间去。

限宽墩

image-20260521163734902

返程时由于大雨,高德导航又给导到村路上去了,然后走了10公里的村路,每两公里就有一个限宽墩,之前只在网上看到过,没实际试过,车感还行,车身极限是197cm,算上后视镜大概是217-220cm左右,基本都是直接进去就过了,一点调整都不用,看了一下后视镜,极限1cm左右。

有个板车就搞笑了,进来了,但是出不去,出去的墩小了一点,过不去,卡门口了

image-20260518211741250

狗不理包子,津门张记包子铺(江都路店),看似包子很瓷实,其实皮很薄,味道说不上好吃难吃,就是正常包子,纯手工,不是预制的

天津中栗华蒸饼店,这店有点拉,虽然很多人排队,但是这个蒸饼吧,真的有点馒头夹馅的意思,味道和预制品差不多,没必要买

刨冰,王中王(荔枝刨冰/红豆芒果炼乳/三黑等),没机会,没吃到

普通吃菜的,看到了很多推荐这几家的

  • 砂锅李(李家大排/芙蓉鲜贝/虾茸草菇白菜)
  • 聚发号(辣子鸡丁/盐爆肚丝/素烧茄子/扒牛肉条)
  • 肥猫烤翅(经典甜辣烤翅/疙瘩汤/醋椒豆腐)

Summary

时间还是略短,还有很多东西没时间体验