Typography

活版印字


  • 首页
  • 博客
  • 闪念
  • 归档
  • 标签
  • 友链
  • 关于
  •    

© 2026 vigourpine

Theme Typography by Makito

Proudly published with Gridea Pro

让基于证据的工程成为项目的标准

Posted at 2026-09-08   Comments   Technology  

Make evidence-based engineering the standard for projects.

本文讨论的不是如何写代码,而是如何下判断。它来自一次完整工程实践,剥离了具体项目的提交号、文件名和数字,只留下原则、决策逻辑、验证步骤和真实踩过的坑。请先记住它的定位:这是默认工作法,不是审批关卡。它的唯一目的,是让“我认为”尽快变成“实测显示”,而不是增加流程负担。

最贵的错误,是自信地错了

在 AI 帮你写代码已成常态的今天,真正让人付出代价的失败,往往不是「不会写」,而是下面这些:

  • 用「我逐条核对过了」来代替真正跑一遍;
  • 用「只要改几行」去论证一个本就不该做的改动;
  • 悄悄改掉自己说错的结论,不留任何痕迹;
  • 把软偏好当成硬约束,为了一件没有用户可见收益的事反复纠结——也就是钻牛角尖;
  • 决定「不做」,却没人把理由记下来,几周后又被另一个人重新提起。

它们有一个共同点:代价高昂,却本可避免。更要命的是,这类错误往往集中在你最自信的地方——因为你从不会去复查那些你觉得「理所当然没问题」的结论。

这套方法论想做的,就是把「下判断」这件事本身约束住。它把一次次教训固化成可执行的清单 + 判断的尺子 + 反模式对照表,并且用「规则分档」来防止它自己退化成新的教条。

这套方法从哪来:起点不是生成,是核查

一份可靠的方案,开头就该交代它的来历

一份真正靠谱的优化方案,通常在开头就声明自己是怎么来的,形如这样一句话:

基于对某外部评估文档的核查,过滤掉不准确的假设(例如断言「缺少某份本应存在的文档」,而它其实已存在)和过早扩张的建议(例如超出当前阶段范围的平台/功能扩展),聚焦于真实存在的健壮性问题、体验改进和覆盖缺口。

换句话说,方案不是从「一个项目通常应该有什么」倒推出来的,而是老老实实走了四步:

  1. 拿到一份外部评估——它可能来自他人、来自某个 AI 工具,或来自一次代码审计;
  2. 逐条到仓库、到代码里去核实;
  3. 过滤掉两类内容:事实性错误,以及价值判断上的过早扩张;
  4. 剩下的,才成为任务。

这里有个容易被忽略的要求:这条推导链应当是物理可核对的。源文档、核查产物(也就是方案)、以及由方案沉淀出的方法论,最好同处一地。方案里的每个任务,都应该能回溯到源文档的某条建议,以及核实那条建议时你究竟读了哪些材料。

方案的自我约束也应该来自核实,而不是凭空设定。举个例子:当方案在开头写明了技术栈,并立下「不新增运行时依赖、不破坏既有安装/集成流程」这类硬约束之后,这些约束会贯穿全部任务,并在评审中成为否决某些建议的直接依据——比如据此否决「把某个自研解析器替换为第三方库」。

三步过滤:把不该做的挡在门外

核实阶段被过滤掉的内容,通常落入三类。下面用通用示例说明:

过滤类型 示例条目 核实依据(示例)
事实性错误 「项目缺少某份贡献/说明文档」 仓库里其实已存在该文档,甚至有多语言版本
过早扩张 支持更多第三方平台、增加 shell 补全等非核心能力 项目仍处早期;现有简写/手动方式已可覆盖,社区可后续按需补
隐含约束 不引入新依赖 现有唯一运行时依赖已足够,方案全程未新增

关键点在于:被过滤掉的条目并没有被删除,而是进了「不做的事项」表,并附上理由(这张表后面会专门讲)。

那么,核实到底读了些什么?这也是「方案来自阅读项目本身」的具体含义。通常包括:各类说明文档(README、贡献指南、变更日志)、架构与设计说明、既有的验证/测试记录、CI 配置、议题模板、构建与依赖清单、代码规范配置,以及源码与测试的全部。

核实过程中有两个常见收获,会直接影响后续任务:

  • 既有验证文档是否存在,会决定新任务的验收形式(例如「实施后配套产出一份验证记录」);
  • 构建/测试脚本的组织方式,会成为改动面评估的关键约束。比如「测试脚本是一份硬编码的全量列表,新增测试文件必须登记,而往已登记文件里加用例则不必」——这一条会显著改变两个看似等价方案的代价。

同一把尺子,也要拿来量方案自己

这是整套流程里最关键的一步。方案定稿之后,评审并没有停止,而是把前面那套核查纪律反过来用在方案本身。一份初看合理的任务清单,逐任务核查之后,条目的最终去向往往分布在以下几种:

去向 含义 典型依据(示例)
✅ 保留 → 实施 问题真实、方案正确 通过实测确认收益与正确性
❌ 移除 问题不存在,或方案比问题更危险 误判会自纠正、结局恒正确;或属 YAGNI;或核查发现原实现反而引入数据丢失陷阱
⚠️ 收敛 问题真实但方案过大 收敛为「最小外科版」,只改一个叶子函数;用差分实测证明回归为零
🔧 打补丁 方案基本成立但有漏项 差分实测发现原方案遗漏了某类输入的处理,需补护栏
➕ 新增 评审中才暴露的项 核查发现某处逻辑零测试覆盖,且带着一个已确认可达的缺陷在跑

这笔账本身就说明了很多问题:一份看起来合理的计划,逐任务核查后往往有相当比例需要修正——被移除、被收敛、被打补丁,甚至会新增评审中才发现的任务;而「原样未被触动」的条目,可能只占一半左右。

这个数字(无论具体是多少)正是整套方法论的核心论据:核查不是对方案作者的不信任,而是方案应有的最后一道工序。

扩大观测面,问题自己会冒出来

「扩大观测面」这件事本身,会立刻暴露此前不可见的问题。一个典型的链条是这样的:某个任务把 CI 从单一平台扩展到多平台矩阵,并补上了跨平台集成测试;紧接着的下一个提交,往往就是修复一个只在新平台 + 特定运行时版本上才会出现的断言失败。

这样的失败,在旧的观测面下完全不可见。这不是谁写错了,而是观测面不够。所以,扩大观测面本身就是一项有回报的投资——它把「潜伏的平台差异」提前变成「可见的红灯」。

先给规则分档:别让方法论自己变成教条

在展开具体原则之前,必须先说清楚一件事:本文里所有规则都分三档,执行强度并不相同。

  • [护栏]:不做就会导致数据损坏、错误结论或不可逆后果——默认强制。
  • [默认]:有理由的默认选择,但可以被用户意图或项目既有约定覆盖。
  • [审美]:收益只是纯粹的一致性/洁癖、用户通常根本观测不到差异——只在成本近零时顺手做。

当它们发生冲突时,按这条优先级链从上往下裁决:

用户明确意图 > 项目既有约定 > 本方法论默认 > 个人偏好

为什么非要分档? 因为把经验固化成清单时,天然带着两个失败模式:一是把软偏好当成硬约束,二是脱离产生规则的语境——规则一旦离开它被总结出来的那个项目,就容易被当成放之四海而皆准的律法。分档 + 优先级链 + 反向清单(何时不必遵循),正是防止方法论自身退化成教条的三道护栏。

那么,如何判断某条规则此刻到底该不该遵守? 回到内核那句话——它能不能产生用户可见的收益,值不值这个成本。如果遵守它已经不再服务于「让结论可信、让改动可控、让协作可回溯」这个目的,那就让规则让路。特别地:

  • 一条 [审美] 规则(比如统一的换行符 / 编码风格),若与所处环境的默认行为相冲突、又没有用户可见收益,就应该顺其自然,而不是反复手动纠正——环境往往会自动把它改回去,你纠正的成本被反复支付,却买不到任何持久收益。
  • 一条 [默认] 规则遇到项目已有约定时,从项目约定。
  • 只有 [护栏] 规则(会造成不可逆损害的那些),才值得在任何环境下坚持。

最后,附上一张反向清单——什么时候你可以理直气壮地不遵循:

  1. 满足某规则不产生任何用户可见收益、且成本非零 → 放弃它,或降级为「顺带做」,按需记一句理由即可。
  2. 某规则与用户明确意图或项目既有约定冲突 → 遵从用户 / 项目,不要强行套用。
  3. 某操作在同一环境已连续失败两次 → 换方法或降级,别第三次硬磕;先分清是「我用错了」还是「这环境不可靠」。
  4. 你正在为没观测到的问题做预防性大动作 → 回到 YAGNI,先问这个问题是否真实存在。
  5. 规则的满足本身变成了目标、而你已说不清它服务于什么 → 停下来,回到目的。

第一层·事实:怎么知道一件事是真的

能跑的,就必须跑

这条是护栏。 任何关于代码行为的断言,只要能跑,就必须跑一遍。「零回归」「经核验」「应该不会有问题」这类表述,在写进文档之前,必须升级成带计数的实测结论。

为什么这么较真?因为推断的错误率从来不为零,而且错误往往集中在自信的地方;因为实测的成本,通常远低于推断出错之后的返工成本;更因为——实测结果可以写进文档成为证据,而推断只能写进文档成为主张。

「推断会说」和「实测显示」之间的落差,往往大得惊人:

场景 推断会说 实测显示
声称「零回归」 「现有用例逐条核验通过」 fixture 差分后发现约一半行为改变;既存测试的输入全部落在「未变」一侧,但改变项里藏着原方案没想到的输入
声称「已覆盖」 原方案未提及某类输入 形如 --flag=false 的布尔标志被误解析为真值,绕过了本应触发的确认
声称「预期失败」 「新用例应该会红」 旧实现大部分用例失败、新实现全绿;而那唯一「常绿」的旧用例其实是回归护栏,不是新功能
声称「更彻底」 「完整重写比最小改动更干净」 逐字节差分证明最小版回归为零,完整版反而触及共享链路、风险更大
声称「不可达」 「当前不会触发」 实跑探针证明可达:某入口标志会被置位、正文被改写

怎么读这张表?每一行左边的说法,听起来都无比合理、无比自信;而右边是跑一遍之后才知道的真相。落到操作上,你可以这么做:

  1. 准备一组 fixture,必须包含现有测试用到的全部输入——这是证明「零回归」的唯一办法;
  2. 把新旧实现并排 import,逐个 fixture 比较输出(异常也要捕获后一并比较,因为抛错与返回值是两种不同的结果);
  3. 报告两个计数:行为相同多少、行为改变多少;
  4. 把改变项逐条归类为「本次意图内的修复」或「意外变更」。意外变更必须处理,或者显式记录为残留并说明不做的理由;
  5. 把计数写回文档,替换掉那些形容词。

用真代码验证,别拿你的抄本比你的推断

验证一个函数的行为时,要import 真实实现,不要在探针脚本里把它的逻辑复制一遍。验证一份文档里的代码时,要从文档里逐字抽取,不要照着手抄。

道理很简单:复制出来的那个「等价实现」,会在你最需要它的时候变得不等价——而你根本察觉不到,因为你比较的,是你自己的抄本和你自己的推断。

几个具体的做法:

  • 探针直接 import 被测的真实源码来跑,而不是把那段逻辑抄进脚本。很多「可达性」结论,依赖的其实是调用方的行为,而不是被抄的那段代码本身——只有跑真实链路才能发现(比如「某入口标志是否会被置位」,取决于上游解析器,而不是目标正则)。
  • 验证「文档里给出的实现」时,用脚本按标题定位、按代码围栏切片,从方案文档里逐字抽出实现和配套测试,改个名字再编译运行——而不是照着文档手敲一遍。
  • 定稿之后,再从已更新的文档里重新抽取、重跑一次。这一步证明的是「文档本身可执行且正确」,而不只是「我脑子里那个版本正确」。

归纳成可复用的四条:

  1. 探针脚本一律 import 被测源码,禁止复制实现;
  2. 抽取文档代码块之后,先断言关键标识存在(如函数签名)、条数符合预期,再运行;
  3. 文档改完之后重跑一次抽取——文档和代码会各自漂移,只有重跑能证明它们仍然一致;
  4. 需要理解跨模块行为时,把调用链上的每一个环节都读一遍(某个标志「有多严重」,往往只有读到它在下游被怎样消费,才能确定)。

先问「问题真的存在吗」,再问「该怎么修」

评审一个任务时,先问「这个问题真的存在吗、有人观测到过吗」,再问「该怎么修」。这个顺序不能颠倒。

因为一旦开始讨论实现方案,你的注意力就会转移到「怎么做」上,从而不知不觉默认了「该做」。把「存在性」这个问题单独拎出来,是唯一能阻止这个滑坡的办法。

实践中有一句固定的三问,专门用来把事实、价值判断、实施方案强行分开:

分析某任务的问题存在性以及是否应当处理?如果确实需要,再分析方案是否需要调整。最后敲定该任务方案。

这三问会带来几类典型结论:

  • 重复代码类:重复属实,但如果它是纯展示逻辑、没有任何观测到的缺陷,那收益就是审美性的;当代价是一次提交要触达大量文件时 → 移除。
  • YAGNI 类:某校验针对的字段只有一个版本、也没有变更计划 → YAGNI。更关键的是,核查发现原方案的实现(「先备份、再返回空表」)会在版本降级时静默清空并覆盖数据 → 这不是修复,而是引入了一个数据丢失陷阱 → 移除,并在「不做」表里写明「若将来引入新版本,应改为 fail-safe(拒绝 + 提示升级、绝不自动覆盖)」。
  • 自纠正类:某处误判会自纠正、结局恒正确,而真正要紧的不变量已经被别的测试覆盖 → 移除。

可复用的四步:

  1. 对每个候选任务强制回答:「有没有人观测到这个缺陷?还是只是看起来不优雅?」
  2. 如果答案是后者,检查收益是功能性的还是审美性的;
  3. 审美性收益不是不能做,但必须与改动面成比例——用「一次提交触达多少文件」来量化代价;
  4. 核查可能反转结论:有的原实现,比它要修的那个问题还危险。这种情况必须记录下来,否则下一个人会重新提出同一个方案。

第二层·判断:怎么决定做不做

「只要几行」是最危险的论证

一个缺陷该不该修,取决于修好它是否改变用户可见的结果,而不是取决于修它有多便宜。

「只要几行」之所以危险,是因为它把成本当成了理由。便宜的事情如果不产生收益,做了也只是白白增加改动面和验证负担。

看一个便宜但决定不修的例子:某处正文污染缺陷,修法已知、只需几行,但决定不修,决定性理由是严重性——

  • 在它的触发场景里,即使修好了污染,用户可见结果也不变:触发前提本身就使后续处理失败,产物照样被跳过;
  • 被改写的那一行,位于每轮都会被丢弃重来的一次性托管产物里,下次运行即自愈;
  • 所以修它,只买到了「不再对一个可丢弃的文件做外观损伤」。

但决定不做,要附带重新立项条件:若将来那个「根因缺陷」被立项修复,本项应与它同一批处理——因为只有到那时,改动才带来用户可见的收益。

再看一个便宜且严重、必须做的对照例子:为 CLI 增加 --flag=value 语法时,差分实测发现原方案漏了布尔标志的处理——--flag=false 会被读成真值,从而同时绕过两道本应触发的确认护栏,而它的终点,恰好与本任务要修的「静默批量操作」相同。这类补丁又便宜又严重,必须随任务一起做。

可复用的四步:

  1. 先独立回答两个问题:「修好它,用户能观测到什么变化?」/「修它要动多少东西?」
  2. 如果第一个答案是「没有变化」,那么第二个答案再便宜,也不构成理由;
  3. 判断「用户可见」时,要追到数据的生命周期:被改的文件,是用户资产,还是一次性托管产物?后者会自愈;
  4. 决定不做时,必须写下重新立项条件。

大改之前,先写个探针止损

实施一个较大改动之前,先写一个最省事的版本跑一遍。它的作用不是交付,而是暴露你没想到的失败模式。

举个真实的收敛过程:某次要收敛一处文本改写逻辑。

  • 先写一个「更省事的版本」:只做最小修正,不加范围限定;
  • 实测发现它引入了一个当前代码里并不存在的新 bug:在整篇文件上匹配某个模式,会改坏正文代码块里的示例内容;
  • 于是「范围限定」就从一个看起来多余的复杂度,变成了必需品;
  • 最终方案收敛为只改一个叶子函数,共享的解析器/正则一律不动——因为它们被用在整条发现/处理链路上(覆盖多个命令),为一个只在局部运行的重写,去改动全链路解析器的结构,风险面与收益完全不成比例。

由此得到一条清晰的判据:改动能否被限制在叶子函数内。能,就做;不能,就要重新论证收益是否配得上这个风险面。

可复用的四步:

  1. 大改动前先写一个简化版,并明确写着「这是探针,不是交付物」;
  2. 探针跑出新缺陷时,别把它当成「简化版的固有问题」忽略掉——它往往指出了完整版的必要组成;
  3. 用「这次改动触达几个文件 / 几条调用链」来量化风险面;
  4. 共享的基础设施(被多条链路使用的解析器、工具函数),默认不动。

别给走不到的路径写测试

可达性判定的依据,必须是可追踪的代码不变量,而不是「至今没触发过」。

给不可达的路径写测试,等于把投机行为固化成规格。将来有人读到这条测试,会以为这个输入是个合法场景。

一个确实不可达的例子:判断某注入路径是否可达时,结论是不可达,但理由必须精确——目标函数只在某个分支被调用,传入的值恒为上游某个「净化函数」的输出,而该净化函数会把非白名单字符整体替换掉,输出的字符集不可能含目标字符。因此不为它写测试,并写明「若将来净化函数被放宽,应在其所在模块补不变量测试」。同时还砍掉了另外两类没有信息量的用例:与其他用例重复覆盖的情形,以及任何「修正验收」式的断言(那是修复的验收标准,写进特征测试会立刻变红)。

再看一个可达性误判的反面教材:曾经判定某路径「当前不可达」,理由是「某入口标志需要顶层键存在才会被设置」。这个判定是错的——上游解析取自结构化解析结果,所以当输入写成另一种形态时,该标志会被置位;而目标正则的锚点匹配不到那种形态,扫描于是越过边界、改写了正文。

教训是:可达性必须按两条独立链路分别判断——解析视图决定是否被调用,文本视图决定改到哪一行。 只核实其中一条,就会误判。

可复用的四步:

  1. 主张「不可达」时,必须指出是哪个不变量保证了它,并给出该不变量在代码里的位置;
  2. 如果理由只是「没见到过」,那就不是不可达,只是低频;
  3. 一个入口标志可能由多种输入形态产生(解析后的值 vs 原始文本),逐一核实每一种;
  4. 不变量被放宽时,测试应该加在不变量所在的模块,而不是加在下游消费者那里。

第三层·自我校正:错了怎么办

自己提的方案,要更狠地怀疑

当一项工作是你自己提议的,你的论证义务更重,而不是更轻。

比如,在完成一处收敛后,你主动提出一个开放项(例如「给某个此前零覆盖的函数补一组特征测试」)。合理的回应不是马上动手写测试,而是要求先论证:

把它单独立项、写进方案。一定要捋一下,确认你想增加的任务是合理的——我们不想把任务搞得过于复杂,但该做的还是得做。

于是先做论证,而不是先动手:

  • 为什么值得做(注意,每一条都不是审美性的):① 它带着一个已实测确认可达的缺陷在跑,而相邻的姊妹逻辑刚刚为同一触发条件修掉了同类问题,这个不对称不写成测试,后来者根本看不见;② 方案已经明确推迟了某些修正,而推迟的正当前提必须被记录在案——计划文档会被归档,测试不会;③ 它是任何未来修正的前置条件。
  • 为什么它能保持简单:不引入红/绿两阶段 → 无实现改动 → 无回归面 → 验收退化为两条(全部用例首跑即绿;除测试与变更日志外,不得出现任何其他文件改动)。
  • 砍掉了什么:主动列出从一批探针 fixture 收敛到少数几条测试的裁剪过程。

可复用的三步:

  1. 自己提议的任务,先写「为什么值得做」和「为什么它能保持简单」两段,再写实施步骤;
  2. 主动列出砍掉了什么——裁剪记录比范围声明更能证明你想清楚了;
  3. 纯增量任务(只加测试、只加文档)的验收标准,要与修复任务不同,不要机械套用红/绿两阶段。

被实测推翻时,先告诉依赖你的人

当证据推翻了你先前给出的判断,你的第一动作是告知那些依赖该判断做决定的人,第二动作才是修正。

以一处「不可达」判定被实测推翻为例,正确的动作顺序是:

  1. 主动披露:明确告知「我之前给你的一个判断是错的,而你在做范围决定时依赖了它」——因为对方先前的决定部分建立在那个错误判断上,他有权重新决定;
  2. 把决定权交还:给出几个选项(修 / 不修 / 扩大范围),而不是自行替他选择;
  3. 更正留痕:把旧判定原文引用后再驳斥,而不是悄悄改掉。于是文档里会同时存在「原判定」(作为被引用的错误)和「这条判定是错的」(作为结论);
  4. 连带更正那些理由错误的正确结论:若某个结论(如「不可达」)是对的、但理由是错的(如「同上」),也要一并更正为真正的不变量依据;
  5. 更正记忆:若同一句错误判定已经写进了长期记忆/文档/注释,检索并更新它,必要时新增一个「已更正的错误判定」专段。

可复用的四条:

  1. 判断被证据推翻时,先检查有谁依赖了这个判断做决定,主动告知;
  2. 更正要留痕:引用原文再驳斥,而不是替换。悄悄修改会让后来者失去「这里曾经错过」的信息,从而可能重犯;
  3. 一个由错误理由支撑的正确结论也是问题——理由错了,下次场景一变,结论就会跟着错;
  4. 错误如果已经写进长期记忆 / 文档 / 注释,必须一并检索并更正,否则它会继续误导。

让文档编辑「可以失败」:计数式自校验

批量编辑文档时,写脚本来执行,并在写盘之前跑一组计数断言。一旦失败就退出,原文件毫发无损。

比如对方案文档做多轮批量替换,每一轮的脚本里都含着数十项断言:

  • 每处替换的目标串必须恰好出现 1 次(否则说明锚点不唯一,或已经被改过了);
  • 旧表述的归零计数、新表述的出现次数;
  • 行数护栏(期望值 = 原行数 + 各编辑预测的增量 ± 容差);
  • 代码围栏总数为偶数、无 CR、无 BOM、标题数量不变。

而在这么做的时候,踩过一批很有代表性的坑——它们几乎人人都会遇到:

坑 症状 根因 修法
字符串替换 API 的反向引用 所有计数断言同时以整数倍(如 2×)超标,文档头部被复制多遍 新文本里含 $`(美元符号紧跟反引号)之类的特殊序列,被当成「插入匹配位置之前的全部内容」 替换串一律用回调 () => newStr
断言作用域过宽 「旧表述已归零」却报出剩余 新内容为了驳斥而原文引用了旧表述 给待查串加上下文限定,并新增正向断言,要求「驳斥引用」各存在 1 次
golden 串跨章节误报 期望 1 次、实际多次 同一字符串在别的章节里,作为输入字面量合法出现 按章节切片后再计数
自写行对齐算法 报出假的增删行数,还说「某段被改动」 简易双指针在改写区附近丢失了对齐 以版本控制工具的 diff --numstat 与 hunk 头为权威,核对 hunk 数与替换次数一一对应(相邻编辑会被合并成一个 hunk)

这里有个耐人寻味的地方:$` 这个坑,恰恰可能就是同一份方案里刚刚描述过的某个缺陷。知道它、写过它、然后踩了它。 这说明「知道一个陷阱」和「手上有防住它的机制」是两件完全不同的事——而真正防住它的机制,是那条行数护栏断言,它让损害停在了写盘之前。

可复用的六步:

  1. 编辑工作区外的文件时(编辑工具可能会拒绝),改用工作区内的临时脚本 + 提权执行;
  2. 校验必须置于写盘语句之前——这一条能让所有失败都变成无损失败;
  3. 替换串一律用回调,无论当前文本里有没有 $;
  4. 断言要限定作用域;写「某表述已归零」之前,先想想新内容会不会为了驳斥而引用它;
  5. 行级差分用版本控制工具,别用自己写的算法;
  6. 临时脚本用完即删,结束时确认版本控制状态干净。

第四层·闭环:让每个决定都有下落

「可选延伸」不是终态

讨论产生的每一个选项,最终都必须变成「做」或「不做」并写进文档。「可选延伸(需另行决定)」不是一个终态。

当某缺陷的处理方式经决策者拍板之后,方案里应该做这样几处编辑:

  • 新增一条排在首位的理由——把严重性论证放在成本论证之前,防止将来有人只看到「便宜、几行」就重新立项;
  • 把「可选延伸(需另行决定)」改写为「已决定:不做——本任务不包含,也不另立任务」;
  • 附上重新立项条件:「若将来某根因缺陷被立项修复,本项应与它同一批处理——只有那时改动才带来用户可见收益,且两者共享同一触发条件、宜一并验收」;
  • 在所有提及该结论的地方同步,避免只在一处留痕。

一个任务的闭环陈述,应该包含三要素:发现了什么 / 已经处理 / 现在的可实施判断。比如:

该任务现在的结论是:方案原本会带着一个自己新造的静默错误路径上线,现已补齐;补完之后,它可以实施了。

可复用的五条:

  1. 「不做」的决定要写明判据(如「修正能否限制在叶子函数内」+「修好是否改变用户可见结果」);
  2. 要写明失效条件 / 重新立项条件——决定是在特定认知下做的,认知变了,决定就该重开;
  3. 理由的排列顺序有意义:把决定性的理由放在第一位,因为它最可能被后来者只读到;
  4. 多处提及同一结论时全部同步,否则文档内部自相矛盾;
  5. 每轮结束给出一句明确的可实施判断,不要让状态停在「讨论过了」。

「不做」也要登记在案

被否决的方案不要删除,而是放进「不做的事项」表,并附上理由。

道理很实在:一个被否决的方案,如果没有留下理由,那么下一个人(或者下一次会话里的你自己)会重新提出它,然后重新走一遍否决流程。

一张「不做的事项」表,通常汇集了来自几个源头的条目:

  • 来自最初过滤的:过早扩张的平台/功能支持、非核心的补全能力、第三方库替换;
  • 来自评审移除的:若干个被判定为 YAGNI / 自纠正 / 比问题更危险的任务(各附完整理由);
  • 来自方案收敛的:几条被否决的替代实现——每一条都注明「经实测」(例如某结构化改写方案会丢失注释、重排结构、其错误处理形同虚设;某「更省事版本」会改坏正文;某字符收窄会产出非法格式);
  • 来自纯测试任务的:明确「只记录、不修」的已知缺陷;
  • 来自最小改动原则的:明确「不顺带修」的相邻既有问题。

可复用的四条:

  1. 表格两列就够了:建议项 / 不做理由;
  2. 理由里要包含实测证据(「经实测,X 会导致 Y」),而不只是价值判断;
  3. 如果否决是基于当前认知,注明失效条件;
  4. 这张表的长度,是评审质量的一个指标——一份只增不减的方案,说明没人真的在质疑它。

钉住缺陷的测试,必须自己说清楚

特征测试里,凡是锁定已知缺陷行为的用例,测试名必须以 KNOWN DEFECT (编号): 开头,并在测试体内注明「本条钉住的是当前缺陷行为、不是期望行为,将来修复时应反转断言,而非删除」。

因为一条常绿的测试,会被后来者读成对该行为的设计背书。不标注,就等于把「我们决定暂时不修」悄悄变成了「这是正确行为」。

具体来说,一组特征测试里若有钉住已知缺陷的用例,它的命名与注释应做到:

  • 名称带缺陷编号,如 ... KNOWN DEFECT (编号): <描述当前被钉住的缺陷行为>;
  • 注释写明同源关系与修法方向,如「与某缺陷同源;将来按某种方式修复本缺陷时,应反转本条断言」。

可复用的三条:

  1. 编号要与缺陷清单对得上,让测试和文档可以互相索引;
  2. 注释里要写清修复时该怎么做(反转断言),而不只是「这是 bug」;
  3. 纯测试任务的验收标准是:所有用例首跑即全绿;若首跑出现红,说明期望值抄错了,应回到实测输出核对,而不是改源码让它变绿。

第五层·工程纪律:把约束变成可执行的检查

说好的边界,每一轮都要验

当你和别人约定「先别动当前项目」时,全程就只改约定范围内的文件;而且每一轮操作结束,都要验证边界没有被突破。

比如,整个评审阶段(可能跨多轮会话)要做到项目源码零修改:

  • 每轮结束执行 git status --short,必须为空;
  • 临时文件计数必须为 0(用统一前缀,如 .tmp-*,便于一键统计与清理);
  • 所有验证都通过 import 真实源码 + 临时文件完成,不把任何改动落到源码、测试、构建产物、依赖清单、变更日志上;
  • 构建产物零漂移,是一条独立的验收项。

可复用的三条:

  1. 约束要写成可执行的检查(一条命令 + 期望输出),不要写成「注意不要改」;
  2. 每轮结束跑一次,而不是最后才跑一次;
  3. 临时文件用统一前缀,便于一键计数与清理。

提交按逻辑拆,不按行数拆

提交要按逻辑原子性来拆分,而不是按文件数或代码行数。判据是:把这个提交单独 checkout 出来时,代码库是否处于一个说得通的状态。

场景 决定 理由
同一处解析循环的等号支持 + 相关 bug 修复 + 护栏 合为一个 feat 提交 三者是同一次重写;拆开需要构造一个「已支持新语法、但某分支仍丢值」的人为中间态,而那个状态本身是坏的
功能改动 + 配套纯文档 拆成两个(feat/ci 与 docs) 功能改动与纯文档分开
实现 + 验证记录文档 拆成两个 同上
纯测试任务 一个 test 提交 只触达测试与变更日志,源码与构建产物零漂移
扩容后暴露的平台修复 独立 fix(test) 它是扩容暴露出来的问题,不是扩容本身的一部分

配套的 commit message 规范(通用约定):

  • 采用带 scope 的 Conventional Commits:feat(scope): / fix(scope): / ci: / docs: / test(scope):;
  • 详细描述里写:变更内容 + 影响范围 + 验证结果(实测数字、门禁退出码、构建产物是否零漂移);
  • 提交前先更新变更日志,相关改动合并为一条记录(不按提交拆分);
  • 变更日志条目的结构:**日期 · 类型 · 标题** + 背景 / 变更 / 测试(必要时加「顺带修复」「不做」);
  • 默认仅提交、不推送;禁止自动提交,等待明确指示。

至于「最小原子改动」到底意味着什么:

  • 最小版只改一个叶子函数,不碰被全链路使用的共享解析器;
  • 在纯测试任务里,版本状态中只要出现任何源码或构建产物改动,都意味着越界;
  • 明确不顺带修某个相邻的既有行为——那是既有语义,扩大它就会突破最小原子改动的边界;
  • 明确不顺带收紧某处判据——记进「不做」表,另行评估。

可复用的四条:

  1. 拆分前先问:「这个中间态是不是一个坏状态?」如果是,就别拆;
  2. commit body 里写实测数字,不写「应该没问题」;
  3. 类型选择看主要意图:主体是新功能就选 feat,顺带修的 bug 在 body 里说明;
  4. 「顺带能做」不等于「应该顺带做」;每一个顺带项,都要单独过一遍前面的「问题存在性」和「严重性 > 成本」。

工具报错,先分清是「我用错了」还是「它不靠谱」

工具报错时,先判断是「我的用法错了」,还是「这个工具在这个环境里不可靠」,然后切换工具,而不是反复重试。

下面这些坑,以 Windows + PowerShell 5.1 为例,但很有普遍性:

工具坑 症状 切换方案
读取原始内容的字符/行计数不可靠 对 UTF-8-no-BOM 文件报出的计数与实际不符 改用运行时精确核验(字符数 / CRLF / 孤立 LF / 行数 / BOM)
内联 node -e "..." 引号 / 反引号被 shell 转义吃掉 → 语法错误 改写成临时脚本文件来执行
脚本文件被 shell 以错误编码读取 脚本里的非 ASCII 字面量(如中文路径)变乱码 → 「路径含非法字符」 不要在脚本里硬写非 ASCII 字面量;改用文件系统枚举(如按扩展名 Get-ChildItem)定位目标,或以当前工作目录 + 相对路径规避
编辑工具无法写工作区外的文件 报权限/范围错误 工作区内临时脚本 + 提权执行
管道过滤丢内容 过滤工具输出时漏掉部分行(编码破坏) 让工具用 --output= 写文件,再用运行时解析
版本控制命令被沙箱拒绝 「拒绝访问」 单独提权重试
控制台输出非 ASCII 乱码 计数结果被乱码淹没 只依赖退出码,以及写文件的结构化输出

可复用的四条:

  1. 同一个命令失败两次就换方法,不要试第三次;
  2. 在编码敏感的环境里,涉及非 ASCII / 精确字节计数的验证,一律走可靠的运行时,而非 shell 内建;
  3. 需要解析工具输出时,让工具写文件,而不是走管道;
  4. 把踩到的坑记进长期记忆——这类条目会在后续会话里,多次替你省下重复排查。

落到手上:四张操作清单

前面讲的都是「为什么」。这一节把它们压成「怎么做」,可以直接照着走。

① 评审一个候选任务时,按顺序回答这六问:

  1. 问题真的存在吗? 有没有人观测到?还是只是看起来不优雅?
  2. 如果存在,严重吗? 修好它,用户能观测到什么变化?(追到数据生命周期:是用户资产,还是一次性产物?)
  3. 改动面多大? 能否限制在叶子函数内?触达几个文件、几条调用链?
  4. 收益与代价成比例吗? 审美性收益,配得上这个改动面吗?
  5. 原方案的实现正确吗? 有没有比它要修的问题更危险的地方?
  6. 决定不做的话,理由和重新立项条件写在哪?

② 采纳一份「重写某函数」的方案前:

  1. 从文档逐字抽取新旧实现(不手抄),抽取后断言关键标识与条数;
  2. 构造 fixture 集,必须包含现有测试的全部输入;
  3. 跑差分,报告「相同 / 改变」两个计数;
  4. 改变项逐条归类:意图内修复 / 意外变更 / 既有缺陷被顺带修好(这类要补护栏测试,否则是一次未登记的静默行为变更);
  5. 新测试分别对旧实现和新实现各跑一次,取得精确的红绿条数写回文档;
  6. 文档改完后重新抽取再跑一次,证明文档本身可执行且正确;
  7. 用版本控制工具的 diff 核对 hunk 数与替换次数一一对应。

③ 批量编辑文档时:

  1. 先备份;
  2. 每处替换的目标串,断言「恰好出现 1 次」;
  3. 替换串一律用回调 () => newStr;
  4. 计数断言 + 行数护栏 + 结构完整性(围栏偶数、无 CR/BOM、标题数不变);
  5. 校验置于写盘之前;
  6. 写盘后做行级差分,确认没有意外改动;
  7. 回读关键区段,确认渲染后的形态与意图一致;
  8. 删除临时文件,确认版本控制状态干净。

④ 提交前:

  1. 变更日志先更新(相关改动合并为一条);
  2. 四步门禁:类型检查 / 静态检查(lint)/ 测试 / 构建,退出码均为 0;
  3. git status --short 检查改动面是否符合任务声明(构建产物零漂移);
  4. Conventional Commits + scope;body 写变更 / 影响范围 / 实测验证结果;
  5. 等待明确指示再提交;默认不推送。

这些坑,我们真的踩过:反模式对照表

下面每一条,都是实际发生过、或被明确避开的。左看反模式,中看它长什么样,右看它会带来什么后果:

反模式 说明性实例 后果
用「逐条核验过了」代替实跑 声称「现有用例全部继续通过(逐条已核验)」 声称本身可能是对的,但没有发现遗漏的输入类别——核验只覆盖了它想到的地方
只核实一条链路就断言不可达 只看文本视图、没看解析视图,就判「当前不可达」 判断错误,且可能被用来支撑范围决定
用成本论证该不该做 「这个缺陷只要几行就能修」 差点做了不产生用户可见收益的改动
悄悄修改错误表述 ——(没做,选择引用后驳斥) 若做了,后来者会失去「这里曾经错过」的信息
机械套用红/绿两阶段 ——(纯测试任务明确不套用) 纯测试任务会凭空造出一个「失败」阶段
替换串直接传入字符串替换 API 编辑文档时 $` 被展开 文档头部被复制多遍,多项断言以整数倍超标
自己写行对齐算法做差分 报出假的增删行数 差点误判某段内容被改动
内联脚本里放引号/反引号 shell 吃掉转义 语法错误,反复踩
脚本里硬写非 ASCII 字面量 被 shell 以错误编码读取 中文路径变乱码 →「路径含非法字符」
给不可达路径写测试 ——(确认不可达后砍掉) 若写了,会把投机行为固化成规格
缺陷测试不自我标注 ——(改用 KNOWN DEFECT 前缀) 若不做,常绿测试会被读成设计背书
顺带修无关问题 ——(不顺带修相邻既有行为、不顺带收紧判据) 若做了,会突破最小原子改动边界、模糊验收标准
让决定停在「可选延伸」 ——(改写为「已决定:不做」+ 重新立项条件) 若不做,悬而未决会被下一个人重新打开

附录 A:一个项目该留下哪些账目

方法论落地之后,建议为每个项目维护一份「最终账目」,用可核对的事实替代形容词。它通常包含三张表:

① 方案文档演进:记录每轮编辑后的规模与编辑处数,以及一致性保证。

阶段 规模(行数/字节) 本轮编辑处数
(示例)某轮收敛 + 某任务移除后 —— ——
(示例)写入某新增任务 —— 若干处
(示例)记录某决定 —— 若干处

全程保持统一的换行/编码约定(如纯 LF、无 BOM);每轮都以版本控制工具的 diff 核对 hunk 数与替换次数一一对应;每轮结束时,项目仓库的 git status --short 为空。

② 任务实施状态:逐任务记录「保留/移除/收敛/新增」的去向、是否已实施,以及对应的提交标识(在内部版本中保留,通用文档中以占位符表示)。

③ 剩余工时与依赖约束:记录尚未实施任务的预估工时,以及任务之间的顺序依赖(例如「某纯测试任务复用了另一任务引入的 helper,顺序不可颠倒」;而与它无依赖的任务,则可独立实施)。

说明:本附录在通用版中不填具体数字与提交号——它们属于单个项目的一次性事实,应记录在该项目自己的方案文档里,并可回溯核对。

附录 B:如果只记得一句话

能跑的就必须跑,跑出来的就照实说,说错了就当场改、并且告诉依赖它的人,决定不做就写下理由和重开条件,改完就验证边界没被突破。

让内容型 Skill 的发布和安装变得像写 Markdown 一样简单

Posted at 2026-09-03   Comments   Technology   AI  

dsh-skills-nexus:让 DSH 生态「零门槛」接入任意 Skill 仓库

如果你厌倦了为每一个 DSH Skill 写 Cordis 插件包装层,这个工具可能是你一直在等的「万能适配器」。


一、项目定位:它解决了一个什么痛点?

DSH(DeepSeek Harness)的 Skill 生态正在快速扩张,但官方 dsh plugin add 命令有一个隐形门槛:只有包含 dsh.bundle.patch 的 Cordis 插件包才能被注册为 profile 层。这意味着,一个纯内容仓库——里面只有 SKILL.md 和一些参考文件——根本无法通过官方渠道接入 DSH。

dsh-skills-nexus 正是为了填平这个鸿沟而生。它的设计哲学非常简洁:

Skill 仓库保持纯粹,适配工作由 Nexus 一次搞定。

思路是:与其让每个 Skill 作者都去学习 Cordis 插件开发、维护 package.json 和 cordis.patch.yml,不如做一个「通用适配层」——安装一次 Nexus,之后任何含 SKILL.md 的 GitHub 仓库都能一键注册为 DSH Skill。


二、核心机制:Symlink 桥接,而非重新发明轮子

Nexus 的实现方式相当优雅,没有搞复杂的运行时注入或自定义 Provider:

GitHub 仓库 ──clone──► ~/.dsh/skills-nexus/repos/<name>/
                              │
                              │ symlink
                              ▼
                    ~/.dsh/skills/<name>  ←── DSH 官方文件系统 Provider 自动发现

也就是说,Nexus 本身只负责克隆和创建符号链接,真正的 Skill 发现、热重载、文件监控全部交给 DSH 原生的文件系统 Provider。这种设计有几个显著好处:

  • 零运行时开销:Nexus 不参与 Skill 的加载和解析,DSH 直接通过 symlink 读取原始文件。
  • 与官方机制完全兼容:不需要自定义 Provider,也不会破坏 DSH 的更新链路。
  • 卸载干净:remove 命令同时删除克隆目录和 symlink,不留残余。

三、功能逐项评测

3.1 安装与注册:一条命令,极简体验

# 安装 Nexus(只需一次)
dsh plugin --profile web add "github:xiaxi626/dsh-skills-nexus"

# 注册任意 Skill 仓库
dsh-skills-nexus add github:owner/repo
dsh-skills-nexus add github:owner/repo#dev      # 指定分支
dsh-skills-nexus add owner/repo --subdir skills/foo  # 子目录安装

支持的 URL 格式非常全面,从 github:owner/repo 到完整 HTTPS URL、SSH、甚至 bare owner/repo 简写都兼容。对「集合仓库」(如 trae-community/trae-skills 这种多个 Skill 嵌套在子目录下的结构)也有 --subdir 参数支持,每个子目录独立克隆、独立管理,互不影响。

3.2 智能仓库检测:不会误操作

Nexus 在 add 时会自动检测仓库类型,并给出明确反馈:

仓库类型 Nexus 行为
纯 SKILL.md 内容仓库 直接注册
SKILL.md + DSH 插件包装层 询问是否忽略包装层,或建议用 dsh plugin add
纯 DSH 插件(无 SKILL.md) 拒绝注册,提示使用官方安装方式
无 SKILL.md 也无插件标记 报错退出
集合仓库(未指定 --subdir) 拒绝并提示使用 --subdir

检测逻辑很贴心,尤其是「混合仓库」的交互设计——既给了用户选择权,又防止了误用。超过 20 个 Skill 的集合仓库还会触发确认提示,避免误装整个巨型仓库。

3.3 版本锁定:轻量级但可靠

Nexus 在注册时会记录解析后的精确 commit SHA,作为轻量锁:

dsh-skills-nexus list
# 输出包含 commit、subdir、启用状态
  • 分支锁定:update 会执行 git pull fast-forward
  • 标签/提交锁定:update 只验证 checkout 是否匹配,不自动漂移

没有引入复杂的 lockfile 格式,仅靠 manifest 里的 commit 字段就能实现「可复现安装」。对于追求稳定的用户,可以用 #tag 或 #commit-sha 固定版本;想要跟随上游更新的,用 #branch 即可。

3.4 生命周期管理:enable / disable / remove

dsh-skills-nexus enable <name>    # 创建 symlink(默认已启用)
dsh-skills-nexus disable <name>   # 移除 symlink,保留克隆
dsh-skills-nexus remove <name>    # 彻底删除

disable 的存在很实用——临时下线某个 Skill 测试效果,不需要重新克隆。remove 会清理克隆目录 + symlink + 注册记录,卸载路径很干净。

3.5 名称规范化:防御性设计

DSH 要求 Skill 名称为小写 kebab-case([a-z0-9]+ 段,单横线分隔)。Nexus 在 add 时会自动将不规范的 frontmatter 名称转换为 kebab-case,并给出 ⚠ 警告。

这种「自动修正 + 警告」的策略比直接报错更友好,减少了用户因命名规范问题而卡住的概率。


四、与官方方案的对比:什么时候用 Nexus,什么时候用 dsh plugin add?

维度 dsh-skills-nexus dsh plugin add
适用仓库 纯内容仓库(SKILL.md + 参考文件) 纯 Cordis 插件(含 dsh.bundle.patch)
仓库要求 无需 package.json、无需 Cordis 代码 需要完整的 Cordis 插件结构
安装命令 dsh-skills-nexus add github:owner/repo dsh plugin add github:owner/repo
版本管理 内置 commit 锁定 + update 依赖 pnpm 版本解析
构建脚本 绕过 pnpm allowBuilds 拦截 受 pnpm 构建策略约束
运行时 无额外开销(纯 symlink) Cordis 插件层加载

两者是互补关系,不是替代关系。如果仓库有 SKILL.md → 用 Nexus;如果仓库是纯插件代码 → 用 dsh plugin add;两者兼具 → 任选。


五、适用场景与推荐人群

强烈推荐使用 Nexus 的场景:

  1. Skill 作者:你写了一个 Prompt 技巧或工作流指南,只想用 SKILL.md 描述它,不想为了发布而学习 Cordis 插件开发。
  2. 企业内部 Skill 库:公司内网 Git 仓库里有一堆业务相关的 Skill 文档,用 Nexus 可以统一管理,无需逐个包装。
  3. 社区 Skill 聚合:维护一个「awesome-dsh-skills」集合仓库,社区成员用 --subdir 按需安装其中单个 Skill。
  4. 快速试用:看到 GitHub 上某个 Skill 仓库想立刻体验,Nexus 是最快的路径。

不太适合的场景:

  • 仓库本身已经是 Cordis 插件(Nexus 会建议你走官方渠道)。
  • 需要复杂的运行时注入或自定义 Provider(Nexus 的定位是内容适配,不是插件框架)。

六、优缺点总结

✅ 优点

  • 零门槛接入:Skill 作者完全不需要懂 Cordis 或 Node.js,一个 Markdown 文件就能发布。
  • 设计优雅:利用 symlink + 官方文件系统 Provider,不重复造轮子。
  • 版本可控:轻量 commit 锁定,分支可更新、标签可固定。
  • 跨平台友好:Windows 自动用 junction(无需管理员权限),其他平台用标准 symlink。
  • 防御性强:仓库类型自动检测、名称规范化、集合仓库安全提示。
  • 卸载干净:remove 不留残余,不会污染 DSH 环境。

⚠️ 需要注意的点

  • 需要重启 DSH 才能识别新 Skill:添加后必须重启 profile(或等待文件系统 Provider 重扫),不能热插拔。这是 DSH 文件系统 Provider 的固有限制,不是 Nexus 本身的问题。
  • 集合仓库每个子目录独立克隆:P1 设计选择(独立克隆)意味着如果集合仓库有 10 个 Skill,会克隆 10 份完整仓库。虽然保证了隔离性,但对磁盘空间敏感的用户需要注意。
  • 无 SKILL.md 的仓库无法注册:这是设计上的边界,不是缺陷,但需要用户理解 Nexus 的定位。

dsh-skills-nexus 是 DSH 生态中一个「小而美」的基础设施工具。它没有试图做太多事,而是精准地解决了一个真实痛点:让内容型 Skill 的发布和安装变得像写 Markdown 一样简单。对于 Skill 作者来说,这意味着发布成本从「学习 Cordis 插件开发」降到了「写一份 README」;对于用户来说,这意味着可以一键接入社区中大量纯文档型的 Skill 仓库。

如果你正在使用 DSH,并且希望探索社区中那些只有 SKILL.md 的宝藏仓库,安装 Nexus 几乎是必选项。它不会替代官方的插件系统,但它极大地扩展了 DSH 的可用 Skill 边界——而这正是生态繁荣所需要的。


安装命令

dsh plugin --profile web add "github:xiaxi626/dsh-skills-nexus"

重启 profile 后,即可用 dsh-skills-nexus add 探索任意 Skill 仓库。

Git标准工作流程

Posted at 2026-08-15   Comments   Git  

梳理一下标准流程:


一、正常的工作流程(避免冲突)

1. 初次获取仓库(只需一次)

git clone <仓库地址>
cd <仓库目录>

之后就不需要再 clone 了,直接进入已有的本地仓库目录工作。

2. 每次开始工作前,先同步远程最新代码

git pull --rebase origin master

或者普通的合并方式:

git pull origin master

推荐使用 --rebase 保持线性历史,减少不必要的合并提交。

如果远程没有更新,git pull 会直接告诉你已经是最新的。

3. 修改文件,然后提交

git add .
git commit -m "描述你的修改"

4. 推送前(可选)检查状态

git log --oneline --graph --decorate --all -20

或者更简单地:

git status

确认本地和远程是否分叉。如果 git pull 之后没有再出现新的远程提交,通常不会分叉。

5. 推送到远程

git push -u origin master
  • -u 参数只在第一次推送时需要,用来设置上游分支,之后可以直接 git push。
  • 如果远程没有新提交,你的推送一定是快进(fast-forward),不会报错。

二、如果推送时遇到“non-fast-forward”错误

这说明远程有本地没有的提交,也就是远程在你上次拉取后又有了新提交(别人推的,或者你在 GitHub 网页上操作产生的)。此时需要先整合远程的提交。

解决方法(推荐 rebase)

  1. 拉取远程提交并变基

    git pull --rebase origin master
    
    • 这个命令会自动把远程的提交拉下来,然后把你的本地提交“嫁接”到远程最新提交之后。
    • 如果没有冲突,命令会顺利结束,此时你的本地历史已经包含了远程提交,且你的提交在最新位置。
    • 注意:如果 git pull --rebase 已经成功完成,就不需要再执行 git rebase --continue。
  2. 如果出现冲突,Git 会暂停并提示冲突文件。你需要:

    • 手动编辑冲突文件,删除冲突标记(<<<<<<<、=======、>>>>>>>),保留正确内容。
    • 执行 git add <冲突文件> 或 git add . 标记为已解决。
    • 继续变基:
      git rebase --continue
      
    • 重复直到变基完成(提示 “Successfully rebased and updated refs/heads/master.”)。
  3. 推送

    git push origin master
    

    此时因为本地已经包含了远程的所有提交,是快进推送,不会报错。


三、澄清你的表述

你说:“如果遇到有其他人基于某分支的提交,导致我们拉取的版本在提交时会弹出冲突,即两个提交都基于同一分支提交,就先变基git pull –rebase origin master,然后git rebase –continue,最后git push -u origin master?”

这里有一个小误区:

  • git pull --rebase origin master 本身就会执行变基操作。如果变基过程中出现冲突,它不会自动完成,而是停下来让你解决冲突。解决冲突后你需要执行 git rebase --continue 来继续变基。
  • 所以顺序是:
    1. git pull --rebase origin master (可能因冲突而暂停)
    2. 解决冲突 → git add .
    3. git rebase --continue
    4. git push origin master (不需要再加 -u,除非是第一次推送该分支)

如果 git pull --rebase 没有冲突,它就直接完成了,不需要 git rebase --continue。


四、总结推荐的标准工作流

# 1. 进入仓库目录(不是每次clone)
cd your-repo

# 2. 同步远程最新代码
git pull --rebase origin master

# 3. 修改文件...

# 4. 提交
git add .
git commit -m "你的提交信息"

# 5. 推送
git push origin master   # 第一次可用 git push -u origin master

如果推送时发现被拒绝(因为远程有新提交),回到第2步执行 git pull --rebase 再推送。

这样就能避免绝大多数“非快进”错误,也能保持提交历史整洁。

五、强制推送回退

git commit –amend:只适用于 “刚写完代码,还没 push” 的情况。作用是 “替换” 上一次提交。
它不是用来提交的。如果你add一次新的改动,然后输入这个命令,会看到的上一次commit信息,如果你修修改改提交了,会出现阻止信息。
如果应对这次阻止你直接输入了——

git push --force-with-lease origin master

强制推送以恢复远程,会覆盖掉上一次提交信息。
遇到这个情况你需要在本地输入命令以查看操作日志,找到被 amend 之前的那个提交哈希值(比如叫 a1b2c3d)

# 1. 查看操作日志,找到被 amend 之前的那个提交哈希值(比如叫 a1b2c3d)
git reflog

## 这条命令对比旧提交(708cd3f)和新提交(0f50a4c),把差异存成补丁
git diff 708cd3f 0f50a4c > my_changes.patch

# 2. 把本地分支强行指回那个哈希值(恢复第4次)
git reset --hard a1b2c3d

# 3. 把被覆盖掉的提交重新推回远程,恢复远程的历史
git push --force-with-lease origin master

# 4.把你刚才备份的补丁,应用到当前代码上
git apply my_changes.patch

(如果这一步提示冲突,别怕,那是因为补丁里的内容和你现在的代码略有偏差,手动把补丁里的修改重新加进去就行,通常不会)
我的建议是最好清除当前本地文件夹除了.git外的其他所有文件,把本地的备份拿过来放进去。

# 5. 正常提交成为第5次
git add .
git commit -m "这是新增的第5次提交"
git push origin master

六、提交

通常我们见到的git commit -m是跳过编辑器,直接把这句引号里的内容作为提交信息。
如果我们想编辑提交内容应该git commit,它会弹出一个文本编辑器(黑框里的 Vim 或 Nano),让你在里面写长篇大论的提交说明。
此时编辑器会出现一堆以#号开头的文字,这些都是注释说明不会出现在提交信息里。我们输入命令后会默认进入INSERT插入模式,光标来到第一行这个空行。你将提交信息粘贴进编辑器即可。
若想清空编辑器信息,Windows系统可以按Esc键,输入vim快速删除全部内容的命令ggdG然后回车,在输入i回到插入模式。
输入完成后按Esc键,输入!wq回车,提交就完成了。

如何在Gridea Pro主题中实现giscus 评论

Posted at 2026-08-13   Comments   Gridea Pro   Technology  

1、仓库准备

随便建一个仓库,或者直接用博客仓库也行,但更建议单开一个仓库,这不仅有利于评论系统测试,也利用数据分开存放。

开启 Discussions

进入评论存储仓库,Settings → General → Features 勾选 Discussions。

安装 giscus App

打开 github.com/apps/giscus → Install → 选择当前仓库。若是组织仓库,需要拥有者授予权限。

创建分类

在仓库 Discussions 页左侧点击 Categories 的铅笔图标,创建一个分类(例如 Blog Comments)。后续每篇文章的讨论都会落在这个分类里。

2、giscus.app 生成配置

访问 https://giscus.app:
按照页面从上到下的顺序依次修改配置。
语言选择中文;
在仓库输入框填入你的评论仓库,若第一步设置正确,输入仓库地址后会提示“成功!该仓库满足所有条件。”;
页面 discussion 映射关系建议选择默认的“Discussion 的标题包含页面的 pathname”,你可根据需要选择其他选项,但一定要先查阅它们的区别和你的主题模板设置;
Discussion 分类选择你上面创建的分类,底下的复选框“只搜索该分类中的 discussion”是默认开启的;
特性-选择是否启用某些特性,默认开启的是“启用主帖子上的反应(reaction)”;
主题我就不懂了大家可以自己选;
最后就是在你想让评论出现的位置添加以下 <script> 标签。但如果已经存在带有 giscus 类的元素,则评论会被放在那里。重要的部分我就删掉了,大家自己操作时会生成。

<script src="https://giscus.app/client.js"
        data-repo=""
        data-repo-id=""
        data-category=""
        data-category-id=""
        data-mapping="pathname"
        data-strict="0"
        data-reactions-enabled="1"
        data-emit-metadata="0"
        data-input-position="bottom"
        data-theme="preferred_color_scheme"
        data-lang="zh-CN"
        data-loading="lazy"
        crossorigin="anonymous"
        async>
</script>

大家要注意data-theme和data-loading,如果你的主题模板支持明暗甚至更多颜色方案,还想设置评论懒加载,你要格外注意它们的引用和切换。

3、Gridea Pro设定

评论选项卡中giscus评论系统的几个信息就在上面提到的script中。

对主题迁移的高效后处理分析

Posted at 2026-08-13   Comments   Gridea Pro   AI  

最近几个月就博客主题模板从一个平台迁移到另一个平台做了深入研究,并在以往的经验基础上使用AI大模型辅助分析、创建和修改主题。
一开始,我并不熟悉很多技术栈,只了解过node.js、html、js、css、JavaScript的一点皮毛。得益于之前为了学习epub电子书编写方法,自己制作与他人合作翻译的轻小说的epub书籍,以及搜集资料创建GitHub上的epub-study仓库,我能够看懂别人写的CSS代码所表述的前端特征,能够看懂html的代码结构,并针对别人已经写好的主题模板和网上的案例进行修改和小幅度的创造。
在几个月前,我有一次在网页端问Kimi一个问题——如果将一个博客主题模板从一个平台移植到另一个平台,我该如何写提示词让AI能够很好的完成任务。在大量的对话中,我最终锁定了这样一段话,在agent中告诉AI,“读取模板文件,列出所有需要迁移的组件和功能,目标迁移系统Gridea Pro 主题系统,描述主题概述”“参考该Gridea pro主题的文件夹命名和层级,将分析得到的Hugo Anubis2的HTML 结构、CSS 样式转换为目标框架语法”。就是这两句简单的prompt,为移植主题模板打开了一扇窗,告诉AI要先找规则。
自此,我开始尝试将Hexo主题移植到Gridea Pro上。在尝试了两次hugo-theme-anubis2后,很偶然地,AI开始按照现在看来相对正确的路子去理解上面这两句prompt,然后非常惊人地第一次把整个移植的大框架搭建对了,并且直到现在都是我目前方法的基石。
开始尝试第二个移植项目时,我遇到了问题:好像上面的prompt不对了,但我不知道有什么方法能把路子摆回之前的输出上。当时我只觉得应该复用prompt,因此包括上面这两句开场白在内的一些用词,在新建移植项目对话时我一定会再输入一遍,以保证路线不偏离。
那个时候流行的只有 prompt 这个概念,skill 还没有火到被我刷到的程度。
过了几个星期,当我发现 skill 这个概念时,就想着让 AI 帮我总结一下之前移植主题的方法。它提出了“映射表”的概念,于是我基于此开始开发映射表,以及读取映射表的外围代码,再往里面填入具体内容。但很快我发现,使用这种方法时,大模型的思维容易变得僵化。
后来,我在 Gridea Pro 组织的官方仓库里找到了 theme-builder-skill。这是一个相当棒的项目,它采用三层架构:知识层提供结构化领域知识(参考文档),工具层提供可执行自动化脚本,模板层提供即用型起始主题代码。各层之间通过定义良好的接口实现松耦合。
具体的分析我让AI写了一篇小论文Theme‐Builder Skill:面向 AI 辅助编程的多引擎静态博客主题开发框架。
但很快我又发现了下一个问题:它是一台好的主题生成器,能够快速将某个主题预览以语法正确的状态移植过来,却只管移植得对不对,不管是不是真的100%像原版。测试时,我甚至遇到过两个不同的主题移植过来后预览相似、但都不像原版的情况。
然后我问开发者,开发者补充了一个相对不错的prompt,我把原文贴在这里——

<!--
  Author: https://github.com/Tespera
-->

# 角色设定
你是一位资深的前端开发工程师和 Gridea Pro 主题开发专家,精通 Jinja2 模板语法以及各类前端页面重构。

# 任务目标
我需要你分析指定的主题源码或目标网站,将其 100% 视觉复刻,并完全转译为符合 Gridea Pro 规范的 Jinja2 语法主题。

# 基础信息(变量)
- **主题名称**:[Kehua] (主题文件夹为小写)
- **演示站链接**:[https://time.20002009.xyz]
- **源码/参考链接**:[ `https://github.com/kehuame/whitespace`]
- **原主题类型**:[Typecho 主题]

# 核心开发要求

## 1. 语法与组件规范
- 使用 Jinja2 语法进行完全转译。
- 必须严格遵循 Gridea Pro 的主题开发规范,使用我们内置的 Gridea Pro theme build skill 进行开发并使用里面的脚本进行数据渲染和语法校准。

## 2. 前端设计优化
- 在进行前端页面设计时可使用frontend design skill 进行设计优化

## 3. 页面完整性
转译后的主题必须包含以下所有基础页面及对应的页面组件(如果原主题缺少某页面,请保持原设计风格进行合理补充)

### 页面
- 首页 (Index)
- 博客列表页 (Post List)
- 文章详情页 (Post Detail)
- 归档页 (Archives)
- 闪念页 (Memos)
- 标签列表页 (Tags)
- 标签详情落地页 (Tag Detail)
- 分类列表页 (Categories)
- 分类详情落地页 (Category Detail)
- 友情链接 (Links)
- 关于页面 (About)

### 组件
- 搜索
- 分页(上一页,下一页)
- 文章详情页底部的上一篇,下一篇
- memos 热力图
- 评论组件

## 3. 样式与布局 (100% 像素级复刻)
- 样式和页面布局在第一阶段必须 **100% 忠实复刻** 原主题的设计。
- **响应式适配**:必须全面适配 PC 端和移动端。请主动检查原主题的响应式情况,如果原主题仅有移动端或 PC 端,请基于原设计风格自行补全另一端的自适应布局代码。

## 4. 核心功能支持
- **深浅模式**:必须支持 Dark / Light Mode 切换及样式适配。
- **全局搜索**:需完整接入搜索组件及逻辑。
- **评论系统**:保留原有的评论组件 UI 样式,但底层逻辑必须修改为支持直接接入 Gridea Pro 的标准评论服务。

# 输出交付要求
1. 请先简要分析原主题的结构和你即将采取的开发步骤。
2. 给出完整的文件目录树结构。
3. 按照组件、布局(Layout)、具体页面的顺序,依次输出完整的 Jinja2 代码和关联的 CSS/JS 代码。
4. 所有代码需放入该主题的文件夹层级中。
5. 请认真思考每个细节,确保开箱即用。

它解决了一个问题,就是让AI扮演一个角色,并让AI真的按照角色设定去做事。这也是早期大家通用的调教方法“你是一个/一位……”。这段话很有指向性,但我觉得它只是解决了移植的专业性要求。
于是,我放弃了纯映射表的路子,改用对theme-builder-skill的补充方案——
将迁移流程分解为七个有序阶段:逆向分析、变量映射推导、模板重写、CSS移植、自动化验证、真机验证和映射积累。该方法论建立在Theme-Builder Skill框架的验证基础设施之上,并在此基础上贡献了四项迁移特有的创新:
(1)通过"先理解后翻译"的逆向分析策略,确保迁移不丢失源主题的设计意图;
(2)基于交叉比对的动态变量映射推导机制,不依赖硬编码映射表,通过源主题变量清单与目标平台变量目录的逐项匹配自动推导对应关系;
(3)Pug Mixin到Pongo2 Include的范式转换策略,解决"函数式组件"到"声明式片段"的语义鸿沟;
(4)通过映射积累实现迁移知识的复利效应,将每次迁移的变量对应关系持久化为可复用的结构化数据。
详细的第一版小论文见Hexo‐to‐Gridea‐Migration:面向 AI 辅助编程的跨平台静态博客主题迁移方法论(以Pug为例)
于是,theme-port-skill 诞生了。它天然有一个短板:机制不够完善。作为一个能进化的 skill 配套设施,它做不到 harness 那样的能力。因此它经常犯一类奇怪的错误:对原版主题理解不够深入,遗漏各种功能和设置;对 CSS/JS 等细节的把握,要么死板地直接拿过来,以为改个语法表达就行,要么虽然理解到位,执行上却出问题。这其实和人很像——不能怪大模型太笨,我们自己也不聪明;但我们总希望足够聪明的它,不要重复犯同样的错误。
harness,又是一个新词。它不负责生成下一个 token,但负责决定模型什么时候调用工具、访问哪些文件、任务拆成几步、出错后怎么回退、执行到哪一步可以结束。
llm、prompt、skill、harness、agent,再加上应用、会话、传输、网络、数据链路,这些层级都有了。你是不是想到了 OSI 七层模型?大模型训练正在搭建一个操作系统,并朝着 AGI 迈进。
回到刚才的话题,总结一下。第一,当前的 theme-port-skill 能较好地移植静态资源,但对原版功能的理解可能不到位。此外,theme-port-skill 与 theme-builder-skill 的交互可能存在问题:为了开发需求,我们在后续更新中把 theme-port-skill 变成与 theme-builder-skill 并行的高级别 skill,导致它静态资源的引用问题被交互放大了(未升级前也有同样的问题)。第二,两个 skill 的组合技依然会遗漏原版主题的功能,我到现在都没找到原因。我确实让大模型逆向读取了原版的所有逻辑,但它似乎只挑熟悉的部分处理;其他部分,如果你不专门问,或者不通过前端预览测试发现遗漏,它极有可能直接说“移植得挺好”。
现在,我附上一个能查出大量问题的方法。当然,核查它最终输出的内容后,我发现有一部分变化其实来自原版主题自身的更新:一些数值和逻辑做了调整。为了匹配原版主题的预览效果,最好还是参照当时移植所依据的版本,一个一个去核对,把输出结果中对应那个版本、但尚未处理好的部分修正补上;确认没问题后,再拿最新版本和当前移植版本做对比,看看剩下的差异有哪些、又该如何处理。
我想,这也算是对版本管理的一种严谨态度吧。
prompt如下——

加载 gridea-theme-builder skill 和 theme-port-skill。 分析原版主题与当前主题,当前主题未复刻的功能以及逻辑有差异的功能,看看CSS/JS排版样式的差异。

地阶功法分享完毕。

Hexo‐to‐Gridea‐Migration:面向 AI 辅助编程的跨平台静态博客主题迁移方法论(以Pug为例)

Posted at 2026-07-14   Comments   Gridea Pro   AI  

摘要

静态网站生成器(SSG)主题迁移是一项需要同时掌握源平台模板语法、目标平台模板引擎差异、数据模型映射和CSS设计规范的复杂任务。其中,Hexo的Pug模板引擎与Gridea Pro的Pongo2(Jinja2的Go实现)之间存在根本性的语法范式差异,使得跨平台迁移成为一项手动语法翻译的繁重工作。本文提出Hexo-to-Gridea-Migration,一个面向AI编程助手的结构化迁移方法论,将该迁移流程分解为七个有序阶段:逆向分析、变量映射推导、模板重写、CSS移植、自动化验证、真机验证和映射积累。该方法论建立在Theme-Builder Skill框架的验证基础设施之上,并在此基础上贡献了四项迁移特有的创新:(1)通过"先理解后翻译"的逆向分析策略,确保迁移不丢失源主题的设计意图;(2)基于交叉比对的动态变量映射推导机制,不依赖硬编码映射表,通过源主题变量清单与目标平台变量目录的逐项匹配自动推导对应关系;(3)Pug Mixin到Pongo2 Include的范式转换策略,解决"函数式组件"到"声明式片段"的语义鸿沟;(4)通过映射积累实现迁移知识的复利效应,将每次迁移的变量对应关系持久化为可复用的结构化数据。该方法论原生支持 Pug、Swig、Nunjucks、EJS 四种 Hexo 源模板引擎的迁移,本文以 Pug 为例展开阐述。

引言

随着SSG生态的不断发展,用户在Hexo、Hugo、Jekyll、Gridea等平台之间迁移的需求日益增长。Theme-Builder Skill框架为Gridea Pro主题开发提供了完整的脚手架、校验和测试工具链,但该框架聚焦于从零创建主题,而非从现有主题迁移。跨平台迁移面临独特的挑战:源主题的模板语法(如Pug的缩进式语法)与目标平台的模板引擎(如Pongo2)存在根本性的范式差异,不能通过简单的语法替换完成。

Gridea Pro的渲染后端通过Pongo2(Go实现)提供Jinja2支持。Pongo2与标准Python Jinja2存在约14个已知不兼容项,这些差异在AI辅助开发场景中尤为关键——AI模型通常默认生成标准Python Jinja2语法,这会在Pongo2环境下产生难以调试的运行时错误。Theme-Builder Skill已文档化了这些不兼容项并提供了语法校验工具,但未覆盖从Pug等外部模板语言迁移的具体策略。

Hexo生态中,Pug(原Jade)是一种广泛使用的模板语言,以其简洁的缩进语法和强大的mixin机制著称。将Hexo Pug主题迁移到Gridea Pro Pongo2需要同时处理三重转换:Pug缩进语法到HTML标签语法、Hexo变量系统到Gridea变量系统、以及Pug mixin到Pongo2 include组件的范式转换。这些转换中,mixin到include的映射是最具挑战性的——Pug的mixin是带参数的函数式组件,而Pongo2不支持macro,需要以完全不同的范式重新组织代码。

本文提出Hexo-to-Gridea-Migration,一个面向AI编程助手的结构化迁移方法论。该方法论将完整迁移流程分解为七个有序阶段,每个阶段具有明确的输入、输出和验证标准。核心设计原则是"先理解后翻译"——不是机械地转换Pug语法,而是先逆向分析源主题的渲染意图和设计语言,再用Pongo2重新生成语义等价的HTML结构。

虽然本文以 Pug 为例展开阐述,但该方法论的 Prompt 文档原生支持通过文件扩展名自动检测四种 Hexo 源模板引擎:Pug(.pug)、Swig(.swig)、Nunjucks(.njk)和 EJS(.ejs)。不同引擎的语法转换对照表及组件转换策略在 Prompt 文档中有完整覆盖,确保了方法论的跨引擎通用性。

本文的核心贡献如下:

  1. 七阶段结构化迁移流程,覆盖从逆向分析到映射积累的完整生命周期,每阶段具有独立的验证标准。

  2. 基于交叉比对的动态变量映射推导机制,不依赖硬编码映射表,通过源主题变量清单与目标平台变量目录的逐项匹配自动推导对应关系。

  3. 多引擎组件范式转换策略,系统化解决不同源引擎(Pug Mixin、EJS 函数、Swig/Nunjucks Macro)到Pongo2 Include的语义鸿沟。

  4. 映射积累机制,通过将每次迁移的变量对应关系持久化,使后续迁移能够复用先验知识,实现迁移效率的复利增长。

相关工作

SSG主题迁移现状

现有SSG主题迁移工具主要依赖手动重写。Hugo社区提供了从Jekyll迁移的指南,但本质上仍是手动对照翻译。Hexo社区有少量从WordPress迁移的工具,但聚焦于内容迁移而非主题迁移。目前尚无系统化的Hexo Pug到Gridea Pro Pongo2的主题迁移方案。

Theme-Builder Skill框架

Theme-Builder Skill是一个面向AI编程助手的结构化技能包,为Gridea Pro主题开发提供端到端支持。该框架采用三层架构(知识层、工具层、模板层),提供了脚手架脚本、语法校验脚本和渲染测试脚本,并管理了Pongo2的全部14个不兼容项。本工作建立在Theme-Builder的验证基础设施之上,复用其validate_syntax.py和render_test.py作为自动化验证层的核心组件,在此基础上扩展了迁移特有的流程和工具。

AI辅助代码迁移

随着大语言模型(LLM)在代码生成领域的进步,AI辅助编程已从代码补全发展到完整的软件工程任务。在代码迁移领域,现有工作主要集中于同语言版本升级(如Python 2到3)或框架迁移(如AngularJS到React),这些场景的源和目标语法高度相似。跨模板引擎迁移(Pug → Pongo2)面临根本性的范式差异,需要更深层的语义理解而非语法翻译。结构化Prompt是将领域专业知识编码为可执行指令的有效方式——不同于检索增强生成(RAG)的被动检索模式,结构化Prompt将领域知识直接嵌入到AI助手的上下文窗口中,使其在任务执行过程中持续参考。本方法采用此策略,将迁移特有的领域知识编码为结构化迁移Prompt。

方法论:七阶段迁移流程

Hexo-Pug-to-Gridea-Migration将完整迁移流程分解为七个有序阶段。每个阶段具有明确的输入、输出和验证标准,阶段之间通过定义良好的接口衔接。

七阶段迁移流程:逆向分析 → 变量映射推导 → 模板重写 → CSS移植 → 自动化验证 → 真机验证 → 映射积累。阶段间顺序执行,每阶段完成后需用户确认方可进入下一阶段。

在执行阶段一之前,Prompt 文档定义了一个前置步骤——源引擎自动检测。AI 通过扫描源主题目录中的文件扩展名自动识别模板引擎:.pug 对应 Pug、.swig 对应 Swig、.njk 对应 Nunjucks、.ejs 对应 EJS。检测结果决定后续所有阶段使用的语法转换对照表和组件转换策略。本文后续展示以 Pug 引擎为例。

阶段一:逆向分析源主题

传统迁移方法通常直接对比源模板和目标模板的语法差异,逐行翻译。这种方法忽略了源主题的渲染意图——Pug模板最终生成的HTML结构才是迁移的真正目标。阶段一采用"先理解后翻译"的策略,包含四个子步骤:

**目录结构扫描。**遍历源Hexo Pug主题的完整目录结构,按布局、页面、局部、Mixin、脚本、样式、图片、配置等分类输出组件清单,并标注每个组件在Gridea主题中的对应目标文件。(注:不同引擎的组件复用模式叫法不同——Pug 称 Mixin、EJS 称 Function/Include、Swig/Nunjucks 称 Macro——阶段一分析时保留原始叫法,阶段三重写时统一转换为 Pongo2 的 include 模式。)

**页面-组件依赖图。**对每个页面模板,绘制其依赖的组件树,明确extends、include和block的层级关系。这确保模板重写时不会遗漏任何组件依赖。

**关键逻辑提取。**提取每个Pug模板中的条件分支、循环逻辑、变量使用清单和Mixin调用。变量使用清单是阶段二推导映射表的输入——列出每个模板中出现的所有Hexo变量(page.xxx、config.xxx、theme.xxx、site.xxx)和Helper函数调用(url_for()、date_xml()、truncate()等),标注出现位置和语义。

**设计语言提取。**从源主题的CSS/SCSS中提取色板、字体栈、间距系统、布局参数、断点、圆角、阴影和过渡等设计参数。这些参数不依赖AI猜测,而是直接从CSS源码中读取:root变量或SCSS变量。

阶段二:推导变量映射表

变量映射是跨平台迁移的核心挑战。阶段二的核心原则是不在Prompt中硬编码任何Hexo → Gridea变量映射,所有映射关系由AI动态推导。

推导过程包括四个步骤:(1)从阶段一的变量使用清单中获取源主题的所有变量引用;(2)从template-variables.md参考文档中获取Gridea侧的所有可用变量及字段名;(3)如果存在历史映射文件hexo-to-gridea-mappings.md,将其作为先验知识,但需与当前源主题的实际变量使用情况交叉验证;(4)逐项匹配:对每个Hexo变量,在Gridea变量目录中寻找语义等价的对应物。

推导输出包含五个维度:Hexo变量名、Gridea变量名、匹配依据(语义等价/功能等价/字段名不同)、是否需要特殊处理(如禁止|date filter),以及发现位置。

方法论文档中列出了10条硬性规则,覆盖了template-variables.md未涉及的跨系统陷阱。以下列出部分关键陷阱。这些陷阱是迁移场景特有的——它们来源于Hexo变量系统与Gridea变量系统之间的语义差异,而非模板引擎本身的语法问题。

跨系统变量映射中的关键陷阱(部分)

陷阱 错误做法 正确做法
post.date是RFC3339字符串 使用|date filter 使用post.dateFormat
prev/next方向语义相反 直接使用原顺序 交换prev/next位置或标签文案
post.content需|safe 直接输出 post.content|safe
archives分组键大写 使用group.year 使用group.Year
友链字段名被重命名 使用link.name 使用link.siteName
theme_config数字比较 直接与数字比较 使用theme_config.count|default:8|to_int
Hexo的config.subtitle等 直接访问config.xxx 通过customConfig声明,模板中用theme_config.xxx
site.categories无对应 期待全局分类列表 从posts手动聚合post.categories
__('key')多语言 期待多语言机制 硬编码中文文案
partial('path', {data})传参 期待传参语法 改用{% set %}设置上下文 + {% include %}

阶段三:脚手架生成与模板重写

阶段三首先使用scaffold_theme.py脚本生成完整的Gridea主题骨架,包含11个页面模板、4个局部模板、CSS变量体系和config.json配置。此步骤直接复用Theme-Builder Skill的脚手架工具,确保生成的主题结构与Gridea Pro规范完全一致。

然后按依赖关系顺序重写每个模板。核心原则:不是翻译Pug语法,而是理解Pug渲染出的HTML结构,用Pongo2重新生成同样的HTML。

模板重写涉及三重语法转换:Pug缩进语法到HTML标签语法、Hexo变量到Gridea变量的对照替换、以及Pug mixin到Pongo2 include的范式转换。对于非 Pug 源引擎,Prompt 文档同样提供了完整的语法转换对照:EJS → Pongo2(14行对照表)、Swig → Pongo2(11行对照表)和 Nunjucks → Pongo2(11行对照表),其中 Swig/Nunjucks 与 Pongo2 共享约90%的语法,迁移成本远低于 Pug 和 EJS。其中mixin转换是最具挑战性的环节——Pug的mixin是带参数的函数式组件,而Pongo2不支持macro。我们为此设计了三种转换策略:

  1. **Include组件策略。**将mixin转为独立的include片段,通过{% set %}变量传递上下文参数。例如,Pug的+tag(tagName)转为{% set tag = tagName %}{% include "partials/tag.html" %}。

  2. **内联策略。**对于逻辑简单、调用次数少的mixin,直接内联展开,避免过度拆分导致的文件碎片化。

  3. **条件分支替代。**Pug的case语句在Pongo2中无直接对应物,转为{% if %}{% elif %}链。

完整的Pug到Pongo2语法转换对照表见附录“Pug → Pongo2语法转换对照表”。每完成一个模板,立即执行validate_syntax.py进行语法校验,确保零新增错误。

阶段四:CSS移植策略

CSS移植采用"重构而非复制"的策略:保留Gridea脚手架的CSS变量体系,将源主题的色板、字体栈和布局参数映射到CSS变量,然后逐组件对照迁移。具体策略包括:保留:root中的--color-*变量体系,将源主题的色值填入对应变量;将源主题的字体栈合并到--font-sans,确保中文字体在正确位置;将源主题的布局参数映射到--content-width、--header-height等变量。如果源主题有暗色模式,将暗色变量填入[data-theme="dark"]块。

CSS移植检查清单覆盖12个维度,涵盖颜色、字体、布局、响应式、Markdown样式、代码块、导航栏、分页器、标签云和页脚等核心样式区域。

阶段五:自动化验证与内容核查

自动化验证建立在Theme-Builder Skill的验证流水线之上,针对迁移场景增加了内容核查层。

**语法验证层。**直接复用Theme-Builder的validate_syntax.py,检查Pongo2模板中的常见错误,目标为零ERROR零WARN。迁移特有的额外检查包括Pug残留语法检测(如缩进式逻辑、mixin调用语法)和Hexo变量残留检测。

**渲染测试层。**直接复用Theme-Builder的render_test.py,使用模拟数据渲染所有模板,目标为所有页面渲染成功且无残留模板标签。

**内容核查层(迁移特有)。**渲染测试通过后,逐页抽查输出HTML。这是迁移特有的验证步骤,因为变量映射错误和mixin转换错误往往在语法层面不可见,仅体现在渲染结果中。检查项包括:文章列表非空、分页链接可点击、导航菜单完整、文章标题/日期/内容完整、标签列表显示、上下篇导航、封面图显示、OG meta标签正确、归档年份正确显示(特别注意group.Year大写)、友链信息完整、空状态场景(0篇文章、无封面图、无标签)不崩溃。方法论文档提供了详尽的内容级问题速查表,覆盖9种常见症状及其诊断路径。

迁移特有的内容级问题速查表

症状 可能原因 诊断路径
整页空白 extends不是第一个标签 每个页面模板第一行
if块完全不渲染 not x == y静默失效 全局搜索改为!=
归档年份不显示 用了group.year小写 改为group.Year
HTML标签显示为文本 缺少|safe 所有post.content输出
日期显示为空 对字符串用了|date 改为post.dateFormat
循环体空 变量名写错 对照阶段二映射表
友链名称不显示 用了link.name 改为link.siteName
分页不显示 判断条件错误 改用pagination.hasPrev
暗色模式切换无效 CSS变量未在暗色块中定义 检查CSS暗色变量块

阶段六:真机验证

将主题目录复制到Gridea Pro的themes/目录,在Gridea Pro桌面应用中切换为新主题,执行渲染并检查output/目录中无fallback-banner(黄色降级视图表示渲染报错)。逐页抽查首页、文章页、归档页、标签页、友链页和404页,验证暗色模式和移动端(375px)响应式。

阶段七:映射积累

**7.0 前置预检。**在开始交叉比对之前,Prompt 文档定义了强制的前置校验步骤:对迁移后的 Gridea 主题依次执行(1)语法校验(validate_syntax.py,要求零 ERROR);(2)渲染测试(render_test.py,要求零 FAIL);(3)结构完整性检查(参照 quality-checklist.md 的 P0 级别,确认 0 篇文章、无封面图、特殊字符标题等边界情况不崩溃)。只有通过前置预检后,才允许进入交叉比对。若存在已知问题但用户确认可接受,则映射来源置信度降级。

**7.1 交叉比对与映射写入。**映射积累是让每次迁移产生复利效应的关键步骤。迁移完成后,将本次的变量映射关系提取并追加到hexo-to-gridea-mappings.md中,供后续迁移直接复用。该方法论支持两种使用模式:模式A(完整迁移流程中自动触发)和模式B(对已有迁移主题进行事后交叉比对)。

映射来源分为两个可信度等级:L1(高置信度,人工确认过的迁移主题)和L2(自动生成,AI自动推导的映射)。冲突解决遵循严格规则:L1永远覆盖L2;L1之间冲突以最新日期为准;L2写入时若发现已有L1记录则不覆盖。

前置知识加载

在执行任何迁移操作前,AI必须完成8项前置知识加载:Skill总入口文档、Gridea模板变量参考、Pongo2差异指南(由Theme-Builder Skill维护)、主题架构文档、配置规范、CSS模式、质量检查清单,以及历史映射文件(如存在)。这确保AI在执行迁移时具备完整的领域知识上下文。

讨论

方法论优势

Hexo-Pug-to-Gridea-Migration相较于传统手动迁移方法具有以下优势:

**结构化可复现。**七阶段流程将迁移过程从"凭经验的一次性手工操作"转变为"有明确步骤、输入输出和验证标准的工程流程"。每阶段的结果可审查、可复现。

**知识持久化。**通过阶段七的映射积累,每次迁移的经验被编码为结构化数据,后续迁移可直接复用。这与传统方法中"每次迁移从零开始"形成鲜明对比。

**适合AI辅助。**该方法论专为AI编程助手设计:明确的阶段划分使AI可以逐步执行并汇报进度;详细的验证标准使AI可以自动检测错误;结构化的前置知识加载确保AI在执行任务时具备完整的领域上下文。

**容错性强。**每阶段的独立验证确保错误在早期被发现。语法校验捕获模板语法错误,渲染测试捕获变量映射错误,内容核查捕获语义错误,真机验证捕获平台兼容性问题。

局限性

当前方法论存在若干局限性。首先,模板重写仍依赖AI对源Pug模板的语义理解,如果AI误解了某个Pug结构的渲染意图,可能导致HTML结构偏差。其次,CSS移植中的设计参数提取依赖于源主题CSS的规范和可读性——如果源主题使用大量魔法数字或非标准写法,提取准确度会下降。第三,渲染测试脚本使用Python Jinja2模拟Pongo2,无法检测运算符优先级差异(如not x == y在Pongo2中会被静默解释为(not x) == y)。第四,该方法论目前仅覆盖Hexo Pug到Gridea Pro Pongo2的迁移路径,对其他SSG平台组合(如Hugo到Gridea、Jekyll到Gridea)的支持需要扩展变量映射规则和陷阱清单。

可推广性

Hexo-to-Gridea-Migration的核心架构——结构化阶段分解、动态变量映射推导、显式陷阱管理、渐进式验证和知识积累——不限于当前的迁移场景。该框架可推广到任意需要跨模板引擎迁移的SSG平台组合,只需替换对应的变量目录、陷阱清单和语法转换对照表。该方法论原生支持的四种引擎(Pug、Swig、Nunjucks、EJS)之外,未来可扩展到 Hugo(Go Templates)、Jekyll 等平台,届时需要补充新的变量映射表和陷阱清单(如Go Templates的range-else结构在Jinja2中无对应物)。更广泛地,这种"将特定迁移路径的领域知识编码为结构化Prompt"的范式,可扩展到其他跨平台迁移场景,如API网关配置迁移、ORM模型到数据库DDL的转换等。

结论

本文提出了Hexo-to-Gridea-Migration,一个面向AI辅助编程的结构化跨平台主题迁移方法论。该方法论建立在Theme-Builder Skill框架的验证基础设施之上,将Hexo Pug主题到Gridea Pro Pongo2主题的迁移分解为七个有序阶段,通过逆向分析、动态变量映射、模板重写、CSS移植、自动化验证、真机验证和映射积累,实现了从"凭经验手工操作"到"结构化工程流程"的转变。

该方法论的核心创新在于:(1)“先理解后翻译"的逆向分析策略,确保迁移不丢失源主题的设计意图;(2)不依赖硬编码映射表的动态变量推导机制;(3)Pug Mixin到Pongo2 Include的范式转换策略;(4)通过映射积累实现迁移知识的复利增长。

未来工作包括:扩展对 Hugo(Go Templates)、Jekyll 等平台到 Gridea Pro 的迁移支持,开发面向Hugo、Jekyll等平台到Gridea Pro的迁移Prompt,引入AI驱动的自动CSS变量提取工具,以及构建面向社区的开源迁移知识库,积累更多迁移来源的映射数据。

伦理声明

本工作呈现的是一款软件工具及配套方法论,旨在辅助开发者完成跨平台主题迁移。本工作不涉及人类受试者实验、个人数据收集或任何可能造成伤害的应用场景。所有引用的第三方代码和文档均为开源项目,其许可证信息可在对应仓库中查询。

可复现性声明

Hexo-Pug-to-Gridea-Migration方法论以结构化Prompt文档的形式提供,其核心迁移Prompt文档(hexo-pug-to-gridea-migration-prompt.md)覆盖了七个阶段的所有操作指令、验证标准和参考文档。该方法论依赖Theme-Builder Skill框架提供的脚手架脚本(scaffold_theme.py)、语法校验脚本(validate_syntax.py)和渲染测试脚本(render_test.py),以上工具均已在Theme-Builder Skill仓库中开源。要复现迁移流程,需要:将迁移Prompt文档与AI编程助手配合使用,在具有相应Skill的AI环境中加载前置知识文档,并按照七阶段流程执行。模拟数据覆盖12篇具有多样化边界条件的文章。

参考文献

Gridea Pro. “Theme-Builder Skill:面向AI辅助编程的多引擎静态博客主题开发框架.” 2026.

Campos, U., et al. “Static Site Generators: A Systematic Literature Review.” Journal of Web Engineering, 2022.

Lewis, Patrick, et al. “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.” Advances in Neural Information Processing Systems, 2020.

Gridea Dev Team. “Gridea Pro——静态博客写作客户端.” https://github.com/getgridea/gridea, 2024.

flosch. “Pongo2——Go语言的Django风格模板引擎.” https://github.com/flosch/pongo2, 2024.

Hexo Dev Team. “Hexo——快速、简洁且高效的博客框架.” https://hexo.io, 2024.

Pug Dev Team. “Pug——健壮、优雅、功能丰富的Node.js模板引擎.” https://pugjs.org, 2024.

附录 A:Pongo2不兼容项速查

本工作所依赖的Pongo2与标准Jinja2之间的14个不兼容项(包括过滤器参数冒号语法、三元表达式不支持、not x == y静默失效等)已在Theme-Builder Skill框架的附录A中完整文档化。迁移过程中,AI通过前置知识加载机制引用该清单,确保生成的Pongo2代码符合运行时要求。此处不再重复列述,但补充以下在迁移场景中最高频触发的不兼容项:

  1. **过滤器参数语法。**标准Jinja2使用括号传递过滤器参数(如{{ content|truncate(100) }}),而Pongo2使用冒号(如{{ content|truncate:100 }})。这会影响所有带参数的过滤器调用,在迁移大量模板时是最常见的错误来源。

  2. **日期格式化陷阱。**Pongo2的date过滤器仅接受Go原生time.Time类型。在Gridea Pro的渲染上下文中,post.date被序列化为RFC3339字符串而非time.Time对象。直接使用post.date|date:"2006-01-02"会导致整个页面降级为回退横幅。正确的做法是使用预格式化的post.dateFormat字段。

  3. **逻辑运算符。**Pongo2 使用英文单词 and、or、not,不支持 &&、||、! 符号。

  4. **长度获取。**使用 |length 过滤器而非 .length 属性(如 {% if posts|length > 0 %})。

  5. **三元表达式。**Pongo2 不支持 a ? b : c 三元表达式,需用 {% if %}...{% else %}...{% endif %} 替代。

  6. **字符串拼接。**不支持 ~ 拼接运算符,在 {{ }} 中直接相邻输出。

  7. **否定包含。**使用 not "a" in b 而非 "a" not in b。

  8. **include 路径。**路径相对于 templates/ 根目录,必须添加 .html 后缀。

  9. **标签内不可换行。**所有 {% %} 和 {{ }} 必须保持单行。

  10. **不支持 macro。**Swig/Nunjucks 的 macro 和 Pug 的 mixin 均需转换为 {% include %} 模式。

附录 B:Pug → Pongo2语法转换对照表

Pug语法到Pongo2语法的完整转换对照

Pug语法 Pongo2等价写法 说明
extends layout.pug {% extends "base.html" %} 模板继承
block content {% block content %} 块定义
include partials/head.pug {% include "partials/head.html" %} 子模板引入
if condition (缩进) {% if condition %}...{% endif %} 条件判断
else if condition {% elif condition %} 多分支条件
each item in items {% for item in items %}...{% endfor %} 循环迭代
+mixinName(arg1, arg2) {% include "partials/xxx.html" %} Mixin→Include
= variable (输出) {{ variable }} 或 {{ variable|safe }} 变量输出
!= variable (不转义) {{ variable|safe }} 原始输出
// 注释 {# 注释 #} 模板注释
case page.type {% if %}{% elif %} 链 分支选择
a(href=url) Text <a href="{{ url }}">Text</a> 链接生成
div.class#id <div class="class" id="id"> 元素生成

注:以上为 Pug → Pongo2 的转换对照表。该 Prompt 文档同样包含 EJS → Pongo2(14行)、Swig → Pongo2(11行)和 Nunjucks → Pongo2(11行)的完整转换对照,其中 Swig/Nunjucks 与 Pongo2 共享约90%的语法(主要差异仅为文件后缀和 macro 转 include)。

附录 C:LLM使用披露

在本方法论的开发过程中,大语言模型被用作开发和文档编写辅助工具。LLM辅助了迁移Prompt文档的起草、多引擎语法转换对照表的编制,以及验证脚本迁移特有检查逻辑的代码生成。所有LLM输出的内容均经过人工开发者审查、在真实Hexo Pug主题上测试验证,并根据实际迁移过程中发现的问题进行了多轮迭代修正。本工作的核心构思——七阶段迁移流程的设计、动态变量映射推导机制、以及mixin到include的范式转换策略——由人类开发者独立完成。作者对所有内容承担全部责任。

Theme‐Builder Skill:面向 AI 辅助编程的多引擎静态博客主题开发框架

Posted at 2026-07-13   Comments   Gridea Pro   AI  

摘要

静态网站生成器(SSG)主题开发是一项需要同时掌握模板引擎语法、数据模型、CSS布局和SEO规范的复杂任务。不同SSG平台之间模板引擎的异构性进一步加剧了这一困难。本文提出Theme-Builder Skill,一个面向AI编程助手的结构化技能包,为Gridea Pro主题开发提供端到端支持。该框架采用三层架构——知识层、工具层和模板层——并显式管理三种模板引擎(Jinja2/Pongo2、Go Templates和EJS)之间的语法差异。本文的主要贡献包括:(1)通过显式的“陷阱清单”和引擎专用起始模板,系统化管理跨引擎不兼容性;(2)自动化验证流水线,包括配置校验、语法检查和模拟数据渲染,将反馈周期从分钟级压缩至秒级;(3)声明式主题配置模式,约束GUI控件类型以防止静默失败;(4)CSS变量驱动的设计系统,内置深色模式支持。本文论证了将领域专业知识编码为结构化技能包是提升AI辅助开发质量的有效范式,并讨论了该方法在静态博客主题开发之外其他需要深度领域知识的软件工程领域中的可推广性。

引言

静态网站生成器(SSG)如Hugo、Jekyll、Hexo和Gridea已成为个人博客、文档站点和轻量内容发布的主流方式。这些工具将内容(以Markdown编写)与呈现(由主题模板定义)分离,生成快速、安全且易于部署的静态HTML页面。

然而,主题开发仍然是SSG生态中最具挑战性的环节之一。开发者必须同时掌握模板引擎语法、理解SSG运行时暴露的数据模型、设计响应式CSS布局,并实现SEO最佳实践。不同SSG采用不同模板引擎的事实进一步加剧了这一问题——Hugo使用Go的html/template,Jekyll使用Liquid,Hexo默认使用EJS——使得跨平台主题迁移成为一项手动语法翻译的繁重工作。

Gridea Pro是一款基于Go、Wails和Vue.js构建的桌面端静态博客写作客户端。其渲染后端同时支持三种模板引擎:Jinja2(通过Pongo2的Go实现)、Go原生html/template和EJS。虽然这种多引擎设计为开发者提供了最大的灵活性,但也引入了显著的复杂性:Jinja2/Pongo2实现与标准Python Jinja2存在约14个已知不兼容项,Go Templates使用完全不同的变量访问模式(PascalCase点号表示法),而EJS没有模板继承机制。

本文提出Theme-Builder Skill,一个面向AI编程助手(如Claude、Trae)的结构化技能包,为Gridea Pro主题开发提供端到端支持。该框架遵循“显式优于隐式”的设计原则:所有引擎差异、配置约束和验证规则均以结构化文档和可执行脚本的形式表达,而非隐式编码在AI模型的训练参数中。

本文的核心贡献如下:

  1. 通过显式陷阱清单和引擎专用起始模板系统化管理跨引擎不兼容性,覆盖14个已文档化的Pongo2与标准Jinja2差异项。

  2. 自动化验证流水线,包括配置校验、多引擎语法检查和模拟数据渲染,将主题开发反馈周期从分钟级压缩至秒级。

  3. 声明式主题配置模式,采用受限GUI控件类型系统(5种允许类型),防止静默GUI渲染失败。

  4. CSS变量驱动的设计系统,内置深色模式支持和响应式布局模式,仅通过配置变更即可实现主题定制。

相关工作

SSG主题系统

主流SSG平台共享一个通用的三层主题架构:模板文件、配置文件和静态资源。Hugo的主题系统使用Go的html/template配合define/template组件模式,Jekyll采用Liquid语言配合include和layout实现模板复用,Hexo默认使用EJS配合partial()进行子模板引入。Gridea Pro的独特之处在于同时支持三种引擎,这提供了最大的灵活性,但也引入了现有工具无法解决的跨引擎迁移复杂性。

现有SSG主题开发工具主要依赖基于CLI的脚手架(如hugo new theme),这些工具设计用于交互式人工使用,而非AI辅助开发。它们缺乏对模板语法错误的静态分析能力——错误仅在渲染时才能发现——且不提供GUI配置类型约束的文档说明。

AI辅助软件开发

随着大语言模型(LLM)在代码生成领域不断进步,AI辅助编程已从简单的代码补全发展到完整的软件工程任务。当前范式包括结构化提示(系统提示引导模型行为)、检索增强生成(RAG)用于注入领域知识,以及Skills/Plugins机制用于扩展模型工具调用能力。本文采用Skill机制,将领域知识(模板引擎差异、配置模式、质量检查清单)打包为可加载的技能包,使AI助手转变为主题开发任务的领域专家。

系统架构

三层架构

Theme-Builder Skill采用三层架构:知识层提供结构化领域知识(参考文档),工具层提供可执行自动化脚本,模板层提供即用型起始主题代码。各层之间通过定义良好的接口实现松耦合。

sequenceDiagram
    participant KL as Knowledge Layer
    participant TL as Tool Layer
    participant TPL as Template Layer
    participant DL as Deliverables

    KL ->> TL : guides
    TL ->> TPL : generates
    TL ->> DL : validates
    TPL ->> DL : customizes

图:Theme-Builder Skill三层架构。 知识层(SKILL.md + 10份参考文档)指导工具层(脚手架、校验、渲染测试脚本),工具层生成起始模板并校验自定义主题。

标准化六步工作流

该框架定义了一个标准化六步开发工作流,将主题开发分解为有序阶段,每个阶段具有明确的输入、输出和验证标准:

Theme-Builder Skill六步开发工作流

步骤 名称 核心操作 交付物
1 引擎选择 选择模板引擎(默认:Jinja2) 引擎类型决策
2 脚手架生成 运行scaffold_theme.py 完整目录结构 + 11个模板 + 配置
3 模板开发 在骨架基础上定制模板和CSS 定制化主题文件
4 语法校验 运行validate_syntax.py ERROR/WARN/PASS报告
5 渲染测试 使用模拟数据运行render_test.py 渲染HTML + 完整性报告
6 实机验证 将主题加载到Gridea Pro themes/目录 实际渲染效果

核心设计理念是快速失败:语法校验和渲染测试在本地执行,无需启动Gridea Pro桌面应用,将反馈周期从分钟级压缩至秒级。

标准目录结构

框架生成的每个主题遵循统一的目录结构:config.json(主题配置),assets/styles/main.css(主题样式),assets/media/images/(静态图片),templates/(11个HTML模板,包括首页、文章、归档、标签、标签页、关于、友情链接、博客、速记和404页面),以及templates/partials/(4个可复用局部模板:head、header、footer、post-card)。

多引擎模板系统

引擎选择与差异管理

Theme-Builder Skill支持三种模板引擎,分别面向不同的开发者群体:Jinja2/Pongo2作为默认推荐(Python生态),Go Templates(Hugo迁移和Go开发者),以及EJS作为兼容模式(旧版Gridea主题)。下表总结了各引擎之间的关键语法差异。

三种模板引擎特性对比

特性 Jinja2/Pongo2 Go Templates EJS
模板继承 extends + block define + template(包裹模式) include(组装模式)
变量访问 config.siteName .Config.SiteName(PascalCase) config.siteName
循环结构 for post in posts range .Posts(含range-else) for (var i=0; ...)(JavaScript)
自定义配置访问 theme_config.key index .Site.CustomConfig "key" theme_config.key
HTML转义 自动转义,safe解除转义 自动转义,safeHTML解除转义 <%= %>转义,<%- %>原始
过滤器/函数 管道语法|filter 函数调用func arg JavaScript函数

Pongo2兼容性挑战

Pongo2是Jinja2语法的Go实现,但约10%的语法不兼容。框架在其jinja2-guide.md参考文档中记录了14个关键不兼容项。这些差异在AI辅助开发场景中尤为关键,因为AI模型通常默认生成标准Python Jinja2语法,这在Pongo2环境下会产生难以调试的运行时错误。

两个代表性不兼容项说明了这一挑战:

**过滤器参数语法。**标准Jinja2使用括号传递过滤器参数(如{{ content|truncate(100) }}),而Pongo2使用冒号(如{{ content|truncate:100 }})。这会影响所有带参数的过滤器调用。

**日期格式化。**Pongo2的date过滤器仅接受Go原生time.Time类型。在Gridea Pro的Jinja2渲染上下文中,post.date被序列化为RFC3339字符串而非time.Time对象。直接使用post.date|date:"2006-01-02"会导致运行时错误,使整个页面降级为回退横幅。正确的做法是使用预格式化的post.dateFormat字段。

模板变量系统

模板变量系统覆盖了Gridea Pro中所有可用的数据上下文,包括全局变量(config、theme_config、menus、tags、links)、文章对象(约30个字段,涵盖内容、元数据、状态、统计和导航维度)、标签对象、分页对象、速记对象和链接对象。每个变量都记录了其类型、语义和跨引擎访问模式。系统还提供了引擎专用的过滤器实现,包括reading_time(中日韩文字感知字符计数)、excerpt(智能摘要提取)和word_count。

校验与测试框架

脚手架脚本

scaffold_theme.py是入口工具,接收主题名称、引擎类型和可选参数,生成完整的主题骨架。脚本内嵌了三种引擎的模板内容,确保每种引擎的语法正确性。生成的config.json预配置了8个常用自定义配置项,包括主题色、特色图片开关、每页文章数、深色模式、社交链接和自定义代码注入。

语法校验脚本

validate_syntax.py实现了三层校验逻辑:

**配置校验层。**检查JSON有效性、引擎字段合法性(必须为jinja2、go或ejs之一),以及customConfig类型字段白名单(仅支持5种GUI控件类型:input、textarea、select、toggle、picture-upload)。使用color、switch或number等不受支持的类型会导致Gridea Pro GUI面板显示空白控件,因此这一预检查至关重要。

**模板完整性校验层。**检查所有必需模板文件是否存在,确保结构完整性。

**引擎专用语法校验层。**对于Jinja2,检查14个Pongo2不兼容模式(过滤器括号、macro/call检测、~拼接、is defined、not in、三元表达式、not x == y静默失败、date过滤器误用、&&/||运算符),以及for/if/block标签配对和include/extends文件存在性。对于Go Templates,检查{{ }}括号配对、range/if/with/define/end配对和==使用警告。对于EJS,检查<% %>标签配对、非法的require()和import语句。结果以三个级别报告:ERROR(阻塞)、WARN(潜在风险)和PASS。

渲染测试脚本

render_test.py在语法校验通过后执行,使用模拟数据渲染所有模板,验证输出的完整性和正确性。对于Jinja2,脚本自动将Pongo2语法(冒号参数)转换为Jinja2语法(括号),使用Python jinja2库渲染,并包含18个自定义过滤器桩函数。对于Go Templates和EJS,检测运行时可用性,若环境未安装则回退为结构检查。

渲染后输出检查包括HTML完整性验证(<html>、<head>、<body>标签)、残留模板标签检测和错误字符串扫描。模拟数据集覆盖12篇测试文章(含/不含特色图片、含/不含标签、长标题、HTML特殊字符、隐藏文章)、7个标签、3条速记和2个链接,确保全面的边界情况覆盖。

主题配置模式

声明式配置

框架通过config.json中的customConfig数组提出声明式主题配置模式。每个配置项定义:name(驼峰式变量名,模板中通过theme_config.xxx访问)、label(GUI面板显示文本)、group(逻辑分组)、type(GUI控件类型,约束为5种允许值)、value(默认值)和可选的note(提示文本)。

GUI控件类型约束

Gridea Pro的GUI面板对customConfig条目实施严格的类型约束。仅五种控件类型有效:input、textarea、select、toggle和picture-upload。使用不受支持的类型会导致空白GUI控件。validate_syntax.py的配置校验层显式检查这一点,防止主题在GUI面板损坏的情况下发布。此外,Gridea Pro对config.json维护进程级缓存;修改customConfig声明需要重启应用才能生效。

CSS设计模式与响应式布局

CSS变量驱动的设计系统

脚手架生成的main.css采用CSS自定义属性构建设计系统,在:root中定义9个核心变量:颜色变量(--color-primary、--color-text、--color-text-secondary、--color-bg、--color-bg-secondary、--color-border)、排版变量(--font-sans使用系统字体栈、--font-mono)和布局变量(--max-width为720px、--header-height为64px)。该系统使主题定制仅通过配置变更即可实现,无需修改CSS规则。

深色模式实现

深色模式通过[data-theme="dark"]属性选择器实现,覆盖CSS变量值。无需额外的CSS文件或JavaScript样式注入。切换逻辑仅修改<html>元素上的data-theme属性;所有使用CSS变量的元素自动响应变更。该方法具有零运行时开销,并与Gridea Pro的enableDarkMode配置无缝集成。

响应式布局策略

响应式布局采用640px断点配合移动优先策略。桌面端使用粘性顶栏(position: sticky; top: 0)和720px内容区域。移动端调整字号、导航间距和卡片内边距。所有布局组件使用原生CSS(Flexbox),不依赖外部框架,最小化主题体积。

SEO与结构化数据

元数据标签系统

框架为每个页面模板定义了完整的元数据标签系统,包括基础meta标签(charset、viewport、description、favicon)、Open Graph标签(og:title、og:description、og:image、og:url、og:type)和Twitter Card标签。文章详情页的og:image和twitter:image自动使用文章特色图片,回退到站点默认头像或Logo。

JSON-LD结构化数据

模板支持嵌入面向搜索引擎的JSON-LD结构化数据:文章详情页使用Article模式,面包屑导航使用BreadcrumbList模式,站点搜索功能使用WebSite模式。同时提供RSS 2.0/Atom订阅模板和canonical URL链接。

讨论

质量保障

框架的质量检查清单(quality-checklist.md)作为主题发布前的最终审查标准,覆盖八个维度:模板完整性、配置校验、引擎语法正确性、渲染正确性、空值处理、HTML语义、响应式设计以及性能与可访问性。渐进式校验策略——语法校验(零依赖,纯文本分析)之后是渲染测试(需要引擎运行时)最后是实机验证——确保了效率:语法错误在数秒内即可发现,无需启动重量级渲染环境。

可推广性

Theme-Builder Skill的架构不限于静态博客主题开发。将领域专家知识编码为结构化技能包的范式——由参考文档、可执行脚本和起始模板组成——可推广到其他需要深度领域知识的软件工程领域,如数据库模式迁移、API客户端生成和框架专用样板代码生成。

局限性

当前框架存在若干局限性。首先,渲染测试脚本使用Python Jinja2模拟Pongo2,无法检测运算符优先级差异(如not x == y在Pongo2中会被静默解释为(not x) == y)。其次,从其他SSG平台(如Hexo Pug、Hugo)迁移主题需要完全手动重写而非自动翻译,原因在于模板引擎之间存在根本的范式差异。第三,框架尚未支持主题市场集成或可视化预览功能。

结论

本文提出了Theme-Builder Skill,一个面向Gridea Pro主题开发的结构化AI技能包,通过显式差异管理、自动化校验和标准化工作流解决了多引擎模板开发的挑战。该框架证明,将领域专业知识编码为结构化技能包是提升AI辅助开发质量的有效方法。未来工作包括扩展对更多模板引擎的支持、引入可视化主题预览功能、构建主题市场集成,以及开发面向跨平台转换的AI驱动主题迁移工具。

伦理声明

本工作呈现的是一款软件开发工具,不涉及人类受试者、个人数据或潜在有害应用。该框架旨在辅助开发者进行主题创作,不涉及歧视、偏见、公平性、隐私或安全问题。所有引用的代码和文档均为开源且公开可用。

可复现性声明

Theme-Builder Skill框架完全开源,以独立仓库形式提供。完整源代码,包括脚手架脚本(scaffold_theme.py)、语法校验脚本(validate_syntax.py)、渲染测试脚本(render_test.py)、模拟数据(mock-data.json)以及所有参考文档,均包含在仓库中。三套起始主题模板(Jinja2、Go Templates、EJS)作为资源目录的一部分提供。要复现主题生成工作流,依次运行python scripts/scaffold_theme.py <name> --engine jinja2、python scripts/validate_syntax.py <theme-dir>和python scripts/render_test.py <theme-dir>。模拟数据覆盖12篇具有多样化边界条件的文章(含/不含特色图片、含/不含标签、长标题、HTML特殊字符、隐藏文章)、7个标签、3条速记和2个链接。本文所有实验均基于框架的公开API,除Python 3.7+外无需额外依赖即可复现。

参考文献

Campos, U., et al. “Static Site Generators: A Systematic Literature Review.” Journal of Web Engineering, 2022.

Lewis, Patrick, et al. “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.” Advances in Neural Information Processing Systems, 2020.

Gridea Dev Team. “Gridea Pro——静态博客写作客户端.” https://github.com/getgridea/gridea, 2024.

flosch. “Pongo2——Go语言的Django风格模板引擎.” https://github.com/flosch/pongo2, 2024.

附录 A:Pongo2不兼容项清单

本附录列出框架管理的全部14个Pongo2/标准Jinja2不兼容项。

  1. 过滤器参数使用冒号而非括号

  2. 不支持三元表达式

  3. 不支持逻辑运算符&&、||、!

  4. 不支持~字符串拼接

  5. 数组长度使用|length过滤器而非.length属性

  6. is defined测试不可用

  7. not in语法存在差异

  8. date过滤器仅接受time.Time(不接受字符串)

  9. 不支持macro和call

  10. 标签内不允许换行

  11. include路径解析规则不同

  12. not x == y被静默解释为(not x) == y

  13. loop.length在for循环中不可用

  14. extends必须是模板中的第一个标签

附录 B:LLM使用披露

在Theme-Builder Skill框架的开发过程中,大语言模型被用作通用辅助工具。具体而言,LLM辅助了脚手架、校验和渲染测试脚本的代码生成,参考文档的起草,以及三套引擎专用起始模板的生成。所有LLM生成的内容均经过人工开发者审查、测试和验证。LLM在研究构思中未发挥重要作用。作者对所有内容承担全部责任。

CST中螺旋线圈电感的建模

Posted at 2026-07-05   Comments   Technology  

建模方法

在CST中按照参考中的建模方法建好螺旋线线圈以后,沿螺旋的中轴建一根与螺旋线一样长的圆柱,再创建一根“一端在轴上,另一端在螺旋线上”的圆柱,使其长度保持线圈中径的一半。最后两端加上引脚即可
操作讲起来比较简单。螺旋线可通过 CST 的方程式建模,然后在起始处创建一个圆片,再 sweep curve,也可以只创建一个圆片直接旋转它生成螺旋体。
我们可以通过CST内置的Macro vba editor记录这一过程。因为通常情况下,想在同一个 CST 版本中复制操作历史,需要打开有相关操作的 CST 文件,选择 history list 中的操作点击 copy,在新的文件 paste。但是对于同一个 CST 版本,完全可以将操作用 VB 记录下来,保存为全局脚本,在新的文件中直接运行它。这就是 CST 的 VBA Macro 内置脚本功能的意义。
当然如果你只是直接点击 history list 中的相关操作然后点击 More 中的 Macro,使其自动生成代码,这个方法是有缺陷的。它跟 CST 的内置脚本一样,会记录为一次操作,但是同样的也完全没有模型操作记录。其实秘密藏在文档里,在 CST 的 VB 脚本预设中有一个叫“AddToHistory”的命令会记录模型操作。
现在我们拆解一下 history list,以其中一步操作为例讲解一下如何修改代码。
现在有一步操作叫“Define curve circle: curve1:circle1”,在 history list 中点击打开详情,可以看到如下这段代码。

With Circle
     .Reset
     .Name "circle1"
     .Curve "curve1"
     .Radius "wire_d/2"
     .Xcenter "r"
     .Ycenter "0"
     .Segments "0"
     .Create
End With

当我们在 history list 中点击copy复制这一个 history 项时,打开系统的剪贴板你会发现,它其实是一个JSON格式文本;但在 CST 软件内部执行时,软件会提取其中的 code 字段,将其当作 VBA 脚本 交由解释器运行,从而在 3D 界面中重绘出模型。这是 CST 进行无界面自动化建模的标准数据交换方式。复制 history list 中的该操作得到的代码如下所示。

CST History Data Exchange Format V2

{
    "history": [
        {
            "caption": "Define curve circle: curve1:circle1",
            "version": "2025.1|34.0.1|20241028",
            "hidden": false,
            "type": "vba",
            "code": [
                "With Circle\r",
                "     .Reset\r",
                "     .Name \"circle1\"\r",
                "     .Curve \"curve1\"\r",
                "     .Radius \"wire_d/2\"\r",
                "     .Xcenter \"r\"\r",
                "     .Ycenter \"0\"\r",
                "     .Segments \"0\"\r",
                "     .Create\r",
                "End With"
            ]
        }
    ]
}

现在我们在 history list 界面点击 Macro 将其转化为 VB 代码,生成的代码如下所示。

' Macro

Sub Main ()


'## Merged Block - Define curve circle: curve1:circle1
StartVersionStringOverrideMode "2025.1|34.0.1|20241028" 
With Circle

     .Reset

     .Name "circle1"

     .Curve "curve1"

     .Radius "wire_d/2"

     .Xcenter "r"

     .Ycenter "0"

     .Segments "0"

     .Create

End With
StopVersionStringOverrideMode 
End Sub

阅读这段代码你会发现:操作内容包裹在 With 到 End With 之间;主程序内容从 StartVersionStringOverrideMode 开始,到 StopVersionStringOverrideMode 结束,在 StartVersionStringOverrideMode 这行的后面是这段代码兼容的 CST 版本;这段代码的 history 名称在“Merged Block”这一行,而“Merged Block”的意思是“合并块”,因此生成的代码是无法编辑的块命令。
想让它在 history list 和 history list (fast model update) 中可编辑,必须将“Merged Block””改为“AddToHistory”,然后在后面再加上“, (String)”,设置一个变量名作为 String,然后将上面的操作赋给 String,这样方能处理多个操作历史。
以下是 CST 中创建螺旋线圈电感的 VBA 代码。

'#Language "WWB-COM"

Option Explicit

Sub Main ()
    StartVersionStringOverrideMode "2025.2|34.0.1|20241216"

    Dim cmd As String
    
    '===========================================================
    ' Step 1: 定义材料 Copper (annealed)
    '===========================================================
    cmd = "With Material" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""Copper (annealed)""" & vbCrLf & _
          "     .Folder """"" & vbCrLf & _
          "     .FrqType ""static""" & vbCrLf & _
          "     .Type ""Normal""" & vbCrLf & _
          "     .SetMaterialUnit ""Hz"", ""mm""" & vbCrLf & _
          "     .Epsilon ""1""" & vbCrLf & _
          "     .Mu ""1.0""" & vbCrLf & _
          "     .Kappa ""5.8e+007""" & vbCrLf & _
          "     .TanD ""0.0""" & vbCrLf & _
          "     .TanDFreq ""0.0""" & vbCrLf & _
          "     .TanDGiven ""False""" & vbCrLf & _
          "     .TanDModel ""ConstTanD""" & vbCrLf & _
          "     .KappaM ""0""" & vbCrLf & _
          "     .TanDM ""0.0""" & vbCrLf & _
          "     .TanDMFreq ""0.0""" & vbCrLf & _
          "     .TanDMGiven ""False""" & vbCrLf & _
          "     .TanDMModel ""ConstTanD""" & vbCrLf & _
          "     .DispModelEps ""None""" & vbCrLf & _
          "     .DispModelMu ""None""" & vbCrLf & _
          "     .DispersiveFittingSchemeEps ""Nth Order""" & vbCrLf & _
          "     .DispersiveFittingSchemeMu ""Nth Order""" & vbCrLf & _
          "     .UseGeneralDispersionEps ""False""" & vbCrLf & _
          "     .UseGeneralDispersionMu ""False""" & vbCrLf & _
          "     .FrqType ""all""" & vbCrLf & _
          "     .Type ""Lossy metal""" & vbCrLf & _
          "     .SetMaterialUnit ""GHz"", ""mm""" & vbCrLf & _
          "     .Mu ""1.0""" & vbCrLf & _
          "     .Kappa ""5.8e+007""" & vbCrLf & _
          "     .Rho ""8930.0""" & vbCrLf & _
          "     .ThermalType ""Normal""" & vbCrLf & _
          "     .ThermalConductivity ""401.0""" & vbCrLf & _
          "     .SpecificHeat ""390"", ""J/K/kg""" & vbCrLf & _
          "     .MetabolicRate ""0""" & vbCrLf & _
          "     .BloodFlow ""0""" & vbCrLf & _
          "     .VoxelConvection ""0""" & vbCrLf & _
          "     .MechanicsType ""Isotropic""" & vbCrLf & _
          "     .YoungsModulus ""120""" & vbCrLf & _
          "     .PoissonsRatio ""0.33""" & vbCrLf & _
          "     .ThermalExpansionRate ""17""" & vbCrLf & _
          "     .Colour ""1"", ""1"", ""0""" & vbCrLf & _
          "     .Wireframe ""False""" & vbCrLf & _
          "     .Reflection ""False""" & vbCrLf & _
          "     .Allowoutline ""True""" & vbCrLf & _
          "     .Transparentoutline ""False""" & vbCrLf & _
          "     .Transparency ""0""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define material: Copper (annealed)", cmd
    
    '===========================================================
    ' Step 2: 定义曲线 circle1
    '===========================================================
    cmd = "With Circle" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""circle1""" & vbCrLf & _
          "     .Curve ""curve1""" & vbCrLf & _
          "     .Radius ""wire_d/2""" & vbCrLf & _
          "     .Xcenter ""r""" & vbCrLf & _
          "     .Ycenter ""0""" & vbCrLf & _
          "     .Segments ""0""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define curve circle: curve1:circle1", cmd
    
    '===========================================================
    ' Step 3: 新建组件 component1
    '===========================================================
    cmd = "Component.New ""component1"""
    AddToHistory "New component: component1", cmd
    
    '===========================================================
    ' Step 4: 定义 CoverProfile solid1
    '===========================================================
    cmd = "With CoverCurve" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid1""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .Curve ""curve1:circle1""" & vbCrLf & _
          "     .DeleteCurve ""True""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define coverprofile: component1:solid1", cmd
    
    '===========================================================
    ' Step 5: 拾取面
    '===========================================================
    cmd = "Pick.PickFaceFromId ""component1:solid1"", ""1"""
    AddToHistory "Pick face", cmd
    
    '===========================================================
    ' Step 6: 设置边
    '===========================================================
    cmd = "Pick.AddEdge ""0.0"", ""0.0"", ""0.0"", ""0.0"", ""10"", ""0.0"""
    AddToHistory "Set edge", cmd
    
    '===========================================================
    ' Step 7: 定义旋转体 solid2
    '===========================================================
    cmd = "With Rotate" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid2""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .NumberOfPickedFaces ""1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .Mode ""Picks""" & vbCrLf & _
          "     .Angle ""n*360""" & vbCrLf & _
          "     .Height ""H""" & vbCrLf & _
          "     .RadiusRatio ""1.0""" & vbCrLf & _
          "     .TaperAngle ""0.0""" & vbCrLf & _
          "     .NSteps ""0""" & vbCrLf & _
          "     .SplitClosedEdges ""True""" & vbCrLf & _
          "     .SegmentedProfile ""False""" & vbCrLf & _
          "     .DeleteBaseFaceSolid ""False""" & vbCrLf & _
          "     .ClearPickedFace ""True""" & vbCrLf & _
          "     .SimplifySolid ""True""" & vbCrLf & _
          "     .UseAdvancedSegmentedRotation ""True""" & vbCrLf & _
          "     .CutEndOff ""False""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define rotate: component1:solid2", cmd
    
    '===========================================================
    ' Step 8: 定义圆柱体 solid3
    '===========================================================
    cmd = "With Cylinder" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid3""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .OuterRadius ""10""" & vbCrLf & _
          "     .InnerRadius ""0.0""" & vbCrLf & _
          "     .Axis ""y""" & vbCrLf & _
          "     .Yrange ""0"", ""H""" & vbCrLf & _
          "     .Xcenter ""0""" & vbCrLf & _
          "     .Zcenter ""0""" & vbCrLf & _
          "     .Segments ""0""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define cylinder: component1:solid3", cmd
    
    '===========================================================
    ' Step 9: 定义参数 h2 (直接执行,不进历史)
    '===========================================================
    ' StoreDoubleParameter "h2", "0"
    
    '===========================================================
    ' Step 10: 创建沿X轴的圆柱 cylinder_radial
    '===========================================================
    cmd = "With Cylinder" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""cylinder_radial""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .OuterRadius ""10""" & vbCrLf & _
          "     .InnerRadius ""0.0""" & vbCrLf & _
          "     .Axis ""x""" & vbCrLf & _
          "     .Xrange ""0"", ""r""" & vbCrLf & _
          "     .Ycenter ""h2""" & vbCrLf & _
          "     .Zcenter ""0""" & vbCrLf & _
          "     .Segments ""0""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Create cylinder along X at h2", cmd
    
    '===========================================================
    ' Step 11: Transform旋转圆柱对齐螺旋线方向
    '===========================================================
    cmd = "With Transform" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""component1:cylinder_radial""" & vbCrLf & _
          "     .Origin ""Free""" & vbCrLf & _
          "     .Center ""0"", ""h2"", ""0""" & vbCrLf & _
          "     .Angle ""0"", ""360*n*h2/H"", ""0""" & vbCrLf & _
          "     .MultipleObjects ""False""" & vbCrLf & _
          "     .GroupObjects ""False""" & vbCrLf & _
          "     .Repetitions ""1""" & vbCrLf & _
          "     .MultipleSelection ""False""" & vbCrLf & _
          "     .AutoDestination ""True""" & vbCrLf & _
          "     .Transform ""Shape"", ""Rotate""" & vbCrLf & _
          "End With"
    AddToHistory "Rotate cylinder toward helix", cmd
    
    '===========================================================
    ' Step 12: 定义螺旋线径向线
    '===========================================================
    cmd = "With Polygon3D" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Version 10" & vbCrLf & _
          "     .Name ""3dpolygon_1""" & vbCrLf & _
          "     .Curve ""3D-Analytical""" & vbCrLf & _
          "     .Point ""0"", ""h2"", ""0""" & vbCrLf & _
          "     .Point ""10*cos(2*pi*3*h2/30)"", ""h2"", ""-10*sin(2*pi*3*h2/30)""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Radial Line at h2", cmd
    
    '===========================================================
    ' Step 13: 起始端圆柱 solid4 (向下延伸)
    '===========================================================
    cmd = "With Cylinder" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid4""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .OuterRadius ""wire_d/2""" & vbCrLf & _
          "     .InnerRadius ""0.0""" & vbCrLf & _
          "     .Axis ""y""" & vbCrLf & _
          "     .Yrange ""-100"", ""-wire_d/2-0.4""" & vbCrLf & _
          "     .Xcenter ""r""" & vbCrLf & _
          "     .Zcenter ""wire_d/2""" & vbCrLf & _
          "     .Segments ""0""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define cylinder: component1:solid4 (start cap)", cmd
    
    '===========================================================
    ' Step 14: 拾取 solid2 起始端面 face 4
    '===========================================================
    cmd = "Pick.PickFaceFromId ""component1:solid2"", ""4"""
    AddToHistory "Pick face: component1:solid2 face 4", cmd
    
    '===========================================================
    ' Step 15: 拾取 solid4 端面 face 3
    '===========================================================
    cmd = "Pick.PickFaceFromId ""component1:solid4"", ""3"""
    AddToHistory "Pick face: component1:solid4 face 3", cmd
    
    '===========================================================
    ' Step 16: Loft连接起始端 solid5
    '===========================================================
    cmd = "With Loft" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid5""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .Tangency ""0.2""" & vbCrLf & _
          "     .Minimizetwist ""true""" & vbCrLf & _
          "     .CreateNew" & vbCrLf & _
          "End With"
    AddToHistory "Define loft: component1:solid5 (start cap)", cmd
    
    '===========================================================
    ' Step 17: 末端圆柱 solid6 (向上延伸)
    ' Xcenter = r*cos(2*pi*n): n整数时为 r,n半圈时为 -r,自动适配
    ' Zcenter = -wire_d/2*cos(2*pi*n): n整数时为 -wire_d/2,n半圈时为 wire_d/2
    '===========================================================
    cmd = "With Cylinder" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid6""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .OuterRadius ""wire_d/2""" & vbCrLf & _
          "     .InnerRadius ""0.0""" & vbCrLf & _
          "     .Axis ""y""" & vbCrLf & _
          "     .Yrange ""H+wire_d/2+0.4"", ""H+100""" & vbCrLf & _
          "     .Xcenter ""r*cos(2*pi*n)""" & vbCrLf & _
          "     .Zcenter ""-wire_d/2*cos(2*pi*n)""" & vbCrLf & _
          "     .Segments ""0""" & vbCrLf & _
          "     .Create" & vbCrLf & _
          "End With"
    AddToHistory "Define cylinder: component1:solid6 (end cap)", cmd
    
    '===========================================================
    ' Step 18: 拾取 solid2 末端端面 face 3
    '===========================================================
    cmd = "Pick.PickFaceFromId ""component1:solid2"", ""3"""
    AddToHistory "Pick face: component1:solid2 face 3", cmd
    
    '===========================================================
    ' Step 19: 拾取 solid6 端面 face 1
    '===========================================================
    cmd = "Pick.PickFaceFromId ""component1:solid6"", ""1"""
    AddToHistory "Pick face: component1:solid6 face 1", cmd
    
    '===========================================================
    ' Step 20: Loft连接末端 solid7
    '===========================================================
    cmd = "With Loft" & vbCrLf & _
          "     .Reset" & vbCrLf & _
          "     .Name ""solid7""" & vbCrLf & _
          "     .Component ""component1""" & vbCrLf & _
          "     .Material ""Copper (annealed)""" & vbCrLf & _
          "     .Tangency ""0.2""" & vbCrLf & _
          "     .Minimizetwist ""true""" & vbCrLf & _
          "     .CreateNew" & vbCrLf & _
          "End With"
    AddToHistory "Define loft: component1:solid7 (end cap)", cmd

    StopVersionStringOverrideMode
End Sub

上面这段代码实现效果如下图所示。

参考

电磁仿真–基本操作-CST-(4)-复杂空心电感
电磁仿真–基本操作-CST-(4)-复杂空心电感
[待整理] 如何用CST建立螺旋的模型
如何用CST建立螺旋的模型
CST中如何建立螺旋线

Windows 编译、运行与调试 Gridea-Pro 完整指南

Posted at 2026-06-20   Comments   Gridea Pro  

Gridea-Pro 是基于 Wails v2(Go + Vue 3) 开发的跨平台静态博客客户端,核心依赖 Go、Node.js、Wails 工具链,结合官方文档,记录源码拉取、环境配置、开发调试、编译打包全流程步骤,适配代码调试需求。

一、前置环境准备(按顺序安装)

  1. 安装 Git(拉取源码)
  2. 安装 Go 语言(核心后端依赖)
    从 Go官网下载安装包,选择 Windows (x86_64) MSI 安装器。
  3. 安装 Node.js(前端 Vue3 依赖)
  4. 安装 Wails v2
    (1)执行 Wails 安装命令(PowerShell):
    go install github.com/wailsapp/wails/v2/cmd/wails@latest
    
    (2)刷新环境变量(重启终端),验证安装:
    wails version
    
  5. WebView2 依赖
    Windows 编译 Wails 项目需要 WebView2 运行时,系统一般预装,缺失则手动安装。

二、拉取项目源码

(略)

三、安装前端依赖

Gridea-Pro 前端代码在 frontend 目录,需要单独安装 Vue3 等依赖。在powershell中运行以下命令:

# 进入前端目录
cd frontend
# 安装所有前端依赖
npm install
# 返回项目根目录(后续命令都在根目录执行)
cd ..

四、开发模式:运行 & 实时调试

1. 启动开发调试模式

在项目根目录执行:

wails dev

执行成功后会自动编译 Go 后端代码,启动 Vue3 前端开发服务,自动弹出 Gridea-Pro 客户端窗口

2. 代码调试方法

(1)前端 Vue3 代码调试
客户端窗口打开后,按 F12 调出浏览器开发者工具;
在 Sources 面板找到前端源码,添加断点、查看日志、排查样式 / 交互问题;
修改 frontend/src 下的 Vue/JS/CSS 代码,页面会即时刷新。
(2)Go 后端代码调试
安装 VS Code,并安装官方 Go 插件。用 VS Code 打开整个 gridea-pro 项目。
在 Go 源码(根目录 .go 文件、internal 目录)左侧行号处点击添加断点。
终端保持 wails dev 运行,操作客户端功能,代码运行到断点会自动暂停。
可查看变量、调用栈、单步执行,完成后端逻辑调试。
安装 Trae & Trae Solo,用 VS Code 打开整个 gridea-pro 项目,按照弹出提示安装 Go 和 Vue 插件。使用传统调试方法的同时,亦可使用Agent交互让AI分析、修改、调试和审查代码。由于上下文处理能力,以及修改文件更新覆盖等问题,应更注重版本控制和备份。

五、编译生产版本

调试完成后,可编译生成正式 .exe 安装包 / 绿色程序,命令依旧在项目根目录执行;

wails build

编译成功后,产物默认生成在项目 build/bin 目录下;Windows 平台会生成 Gridea-Pro.exe。
精简压缩包(体积更小)

wails build -compress

编译后会额外生成压缩包,方便分发。
只打包、不生成安装程序(纯绿色 exe)

wails build -nsis=false

只保留免运行的 exe 程序,去掉 Windows 安装包。
生产模式(关闭调试、优化性能,正式发布用)

wails build -production

代码会做混淆 / 优化,去掉调试信息,适合对外发布。
兼顾体积 + 正式版本

wails build -production -compress

首次编译较慢会自动拉取依赖、编译前后端,耐心等待即可。

如何在 GitHub 上提交 Pull Request

Posted at 2026-06-19   Comments   Git  

本文记录 Fork 仓库到创建分支、提交改动、发起 PR、再到删除分支的完整流程,防止自己忘记。

一、Fork远程仓库

1、打开原作者的仓库页面,如https://github.com/Gridea-Pro/gridea-pro-themes;
2、点击右上角Fork按钮,自动跳转到Create a new fork界面,点击Create fork按钮。

二、保持主分支干净

Fork 完成后,仓库会有一个默认分支(通常为 master 或 main),请勿在主分支上开发。

三、拉取远程仓库代码

使用 git clone 将Fork后的远程仓库clone到本地。
克隆远程远程仓库的方法有很多。
(1) 使用clone命令下载远程仓库,git clone
远程URL是Git用于指代“代码存储位置”的专业术语。该URL可以是您在GitHub上的仓库、其他用户的分支,甚至位于完全不同的服务器上。
您只能向两种类型的URL地址发送推送:
一个类似 https://github.com/user/repo.git 的 HTTPS URL
一个 SSH URL,例如 git@github.com:user/repo.git
Git会将远程URL与名称关联,默认的远程路径通常称为“origin”。
有时你会选择使用Github文件加速网站加速下载文件,这时候 git clone 就会使用例如

git clone https://gh.xmly.dev/https://github.com/stilleshan/ServerStatus

提交到远程仓库时会提示

remote: Invalid username or token. Password authentication is not supported for Git operations.
fatal: Authentication failed for ……

此时需要把远程仓库地址从加速地址改成原始地址,这条命令不会影响你的分支和提交。

git remote set-url origin <远程仓库地址>

(2) 使用初始化仓库下载远程仓库

1. 在当前目录初始化一个空的本地仓库
git init

2. 将本地仓库与远程仓库关联(origin 是默认的远程名称)
git remote add origin <远程仓库地址>

3. 在 fetch 之前,先用这个命令查看远程仓库的默认分支叫什么
git remote show origin

4. 从远程仓库下载所有数据
git fetch --all

5. 创建并切换到本地 main 分支,并让它跟踪远程的 origin/main
git checkout -b main --track origin/main

6. 手动创建一个本地分支来跟踪远程分支,并检出文件
# 创建并切换到本地 main 分支,并让它跟踪远程的 origin/main
git checkout -b main --track origin/main

如果你想获取远程仓库的所有分支,其实不需要加 –all,因为 git fetch origin 默认就会下载该远程仓库下的所有分支和提交。

如果你的远程仓库只有一个(即 origin),直接写 git fetch origin 效果完全一样。

只想 fetch 到主分支(main 或 master),直接指定分支名即可。

# 如果主分支是 main
git fetch origin main

创建并切换到本地分支跟踪远程分支完全取决于你 fetch 了哪个远程分支。如果你 fetch 了 main,就写 origin/main:

git checkout -b main --track origin/main

Git 提供了一个更智能的快捷命令,它会自动识别远程分支名,并在本地创建同名的分支。

# 无论远程是 main、master 还是 develop,Git 都会自动取相同的名字
git checkout --track origin/main   # 本地自动生成 main 分支
git checkout --track origin/master # 本地自动生成 master 分支

四、新建功能/修复分支

(1) 创建一个新的分支
要创建新分支,请使用以下命令:

git branch <branch_name>

(2) 创建新分支并切换至该新分支
你可以使用以下方式创建新分支并立即切换:

git checkout -b <branch_name>

五、在分支上开发并提交

本地端需要执行的相关命令如下:

git checkout master
git pull upstream master        # 同步上游
git checkout -b feat/typography
# 添加或修改文件后:
git add .
git commit -m "feat(typography): 新增typography 主题(Jinja2移植)"
git push origin feat/typography

业界通用的 git 提交规范

AngularJS 在 github上 的提交记录被业内许多人认可,逐渐被大家引用。格式:

type(scope) : subject

( 1 ) type(必须) : commit 的类别,只允许使用下面几个标识:
feat : 新功能
fix : 修复bug
docs : 文档改变
style : 代码格式改变
refactor : 某个已有功能重构
perf : 性能优化
test : 增加测试
build : 改变了build工具 如 grunt换成了 npm
revert : 撤销上一次的 commit
chore : 构建过程或辅助工具的变动
( 2 ) scope(可选) : 用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。
( 3 ) subject(必须) : commit 的简短描述,不超过50个字符。
commitizen 是一个撰写合格 Commit message 的工具,
遵循 Angular 的提交规范。
安装:
全局安装 commitizen

npm install -g commitizen

进入项目文件夹,运行如下命令:

commitizen init cz-conventional-changelog --save --save-exact

使用:
用 git cz 命令取代 git commit,这时会出现如下选项:
( 1 )选择 type
( 2 )填写 scope(选填)

? What is the scope of this change (e.g. component or file name)? (press enter to skip)
core

( 3 )填写 subject

? Write a short, imperative tense description of the change:
set a to b

完成,运行 git log 命令,查看我们刚才提交的 commit message,如下:

fix(core): set a to b

六、发起 Pull Request

在你的 GitHub Fork 页面上,点击黄色横幅 Compare & pull request;或进入 Pull requests > New pull request。
Base repository 选择原作者仓库,base branch 选择 master(或指定的开发分支);
Head repository 选择你的 Fork 分支 feat/typography。
填写 PR 标题和描述,建议和提交的 message 写的一样,例如:

feat(typography): 新增typography 主题(Jinja2移植)

点击 Create pull request。

七、合并后删除分支

等待维护者审核并合并后,可在 PR 页面点击 Delete branch;
或者在本地和远程执行:

git checkout master
git pull upstream master
git push origin master
git branch -d feat/typography
git push origin --delete feat/typography

参考

如何在 GitHub 上提交 PR (Pull Request)
如何在github上进行PR
git commit 代码提交规范

PageNumber 1 / PageCount 8 

  Next 

© 2026 vigourpine

Theme Typography by Makito

Proudly published with Gridea Pro