关于 deslop
deslop 是用于清理分支中 AI 生成代码的编码技能,要求保持行为不变。它仅处理差异引入的代码,并保留针对不可信输入的安全检查。
主要功能
- 01清理变更代码而不重构
- 02对比分支与默认分支
- 03纳入未提交的变更
- 04保留安全与权限检查
- 05遵循文件现有风格
适用场景
- 01清除分支中的调试日志和未使用导入
- 02简化新改代码中的嵌套守卫
- 03整理变更引入的紧凑代码行
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ overextended/ skills --skill deslop安装后在 Claude Code 中输入/deslop确认已加载。
附带文件agents/openai.yamlCREDITS.md
详细介绍
移除 AI 生成的冗余代码
找出此分支中更改的代码,并移除其中引入的 AI 生成冗余内容。保持行为不变:这是一次清理,不是重构。
范围
- Git 仓库:与默认分支的合并基点比较(
git merge-base HEAD origin/HEAD,否则使用main或master),并包含未提交的更改。 - 没有 git(服务器文件夹中很常见):使用本次会话中更改的文件。如果没有更改,请询问要清理哪个资源或哪些文件。
仅修改差异中引入的行。周围的代码体现了项目风格,但不是清理目标。
重点检查
根据文件及其所属资源的其他内容来判断候选项:在某个资源中属于冗余内容的代码,在另一个资源中可能是项目惯例。
所有代码
- 叙述代码的注释(
-- 遍历玩家)、重复函数名含义的注释,或与文件注释风格不符的注释。 - 遗留调试代码:多余的
print或console.log、已注释掉的代码。 - 可通过提前
return简化的深层嵌套。 - 代码过于紧凑。参见排版。
- 分支新增但未使用的局部变量、导入或参数。
- 与文件风格不一致的命名(在 snake_case 文件中使用 camelCase,反之亦然)。
Lua
- 对可信路径中不可能为 nil 的值进行
pcall包装或 nil 检查:例如,资源清单声明为依赖项的资源导出,或同一函数刚刚设置的值。 - 与
true或false比较(if isOpen == true then),但文件中没有这样的写法。 - 文件声明使用
local时却新增全局变量。将变量改为局部变量前,先搜索整个资源中的其他用法:资源中的每个 Lua 文件在同一侧共享一个全局作用域,因此其他文件可能会读取它。 - 同一文件中混用
Citizen.CreateThread/Citizen.Wait和CreateThread/Wait。它们是相同的函数:遵循文件中已有的写法。 - 资源依赖 ox_lib 时,手写了资源已能从 ox_lib 获取的辅助函数:例如,在使用
lib.table.contains的代码旁写了contains循环;或在资源其他位置使用lib.requestModel时,写了RequestModel/HasModelLoaded等待循环。仅当 ox_lib 函数的行为相同时才替换;先阅读其源码,确认超时、返回值和错误处理方面的差异。
TypeScript / JavaScript
- 仅用于压制类型错误的
any强制类型转换、as unknown as,以及@ts-ignore/@ts-expect-error。 - 对可信调用使用
try/catch,但只记录日志后继续执行。
排版
Agent 会把代码挤在一起:一行写多个语句;多个守卫之间不留空行;把条件拆成多行以便塞进同一个 if。判断排版时,应参照默认分支中的文件或分支未修改的文件,而不要参照分支自身的代码:如果分支编写了文件的大部分内容,那么其中的排版本身就是冗余代码的一部分。
- 每行一条语句。
- 不同步骤之间留空行:守卫、查找、处理、返回。
- 仅当守卫很短且其主体只是一个
return或return false时,才将守卫写在一行。 - 守卫返回表格或执行更多操作时,应拆分为多行。
- 条件过长而无法写在一行时,应拆成多个守卫,不能将一个
if拆成多行。 - 调用或表构造超过约 120 个字符时,每个参数或字段单独占一行。较短的则保留在一行。
之前:
if type(data) ~= 'table' or type(data.slot) ~= 'number' or type(data.count) ~= 'number'
or data.count < 1 then return false end
local player = GetPlayer(source)
if not player or not player.shop then return false end
local item = player.shop.items[data.slot]
if not item then return false end
if item.license and not HasLicense(player, item.license) then return false, { type = 'error', description = 'missing_license' } end
之后:
if type(data) ~= 'table' then return false end
if type(data.slot) ~= 'number' or type(data.count) ~= 'number' or data.count < 1 then
return false
end
local player = GetPlayer(source)
if not player or not player.shop then return false end
local item = player.shop.items[data.slot]
if not item then return false end
if item.license and not HasLicense(player, item.license) then
return false, { type = 'error', description = 'missing_license' }
end
绝不删除
接收客户端任何内容的服务端代码都不是可信路径。请保留以下所有检查:
- 服务端网络事件处理器(
RegisterNetEvent、针对网络事件的AddEventHandler)的参数检查,以及服务端lib.callback.register回调的参数检查; - 基于
source的查找:玩家是否存在、是否拥有物品、是否靠近目标、是否拥有相应职业或组别; - 频率限制、距离检查和权限检查(
IsPlayerAceAllowed、框架的职业或组别检查)。
通用清理会把这些检查当作多余的防御性代码,而它们正是防止漏洞利用的关键。如果某项检查确实看起来多余,也请保留,并在摘要中说明。可以将检查拆分为独立的 guard 或单独一行:每项检查都必须保留。
此外,还要保留对数据库数据和 NUI 回调(RegisterNUICallback)数据的检查,以及对可选资源的保护检查(GetResourceState(name) == 'started')。
护栏
- 除非是修复明确的 bug,否则保持行为不变,并在摘要中注明任何 bug 修复。
- 优先进行最小范围的针对性修改,避免大规模重写。布局是例外:修复此分支新增的所有挤在一行的代码,而不是只修复示例。不要重新格式化未修改的代码。
- 最终摘要保持简洁(1-3 句话)。
评论
支持 Markdown