diff --git a/AGENTS.md b/AGENTS.md index 2b2894f..12a86eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ ## 项目目标 -CraftKit 面向 Codex 插件市场,提供简洁、中性、可独立安装的 Skill。所有能力必须能够脱离来源项目独立理解、验证和维护。 +CraftKit 面向 Codex 插件市场,提供简洁、中性、可独立安装和维护的 Skill。每项能力都应具备清晰边界,并能够独立理解与验证。 ## 沟通与改动 @@ -23,8 +23,7 @@ CraftKit 面向 Codex 插件市场,提供简洁、中性、可独立安装的 | `plugins/doc/` | 文档转换、整理与写作 Skill | | `plugins/git/` | Git 操作与交付 Skill | | `plugins/knowledge/` | 交接、复盘与知识维护 Skill | -| `plugins/skill/` | Skill 创建、迁移、检查与同步工具 | -| `migration/` | 来源指纹、迁移计划和状态追踪,不进入插件发布内容 | +| `plugins/skill/` | Skill 与项目规范辅助工具 | ## Skill 设计规则 @@ -44,143 +43,51 @@ plugins//skills// ├─ SKILL.md ├─ agents/openai.yaml # 有 UI 元数据时使用 ├─ scripts/ # 有确定性自动化时使用 -├─ references/ # 有条件加载的详细资料时使用 +├─ references/ # 有条件加载的详细资料 └─ assets/ # 生成产物需要的素材 ``` +## 维护原则 + +- 以当前 Codex 能力、项目实际情况、公开标准和用户需求为依据设计 Skill。 +- 技术规则应注明适用版本;依赖外部资料时记录资料入口和验证日期。 +- 新增内容应具备明确用途,避免重复能力、过期说明和仅用于记录开发过程的文档。 + +## 新增与修改流程 + +1. 阅读目标插件、相邻 Skill、适用的 `AGENTS.md` 和当前 Git 状态,确认职责边界与已有改动。 +2. 明确用户目标、输入、输出、触发条件、副作用、权限和不包含事项。 +3. 检查是否应复用或扩展已有 Skill,避免名称不同但职责重复的实现。 +4. 修改前向用户说明目标文件、实现方式、风险和验证方法;高风险或范围不明确时等待确认。 +5. 按最小必要结构实现,优先使用项目证据和官方资料,不猜测接口、版本或运行行为。 +6. 完成结构、引用、插件清单及必要行为验证,并分别记录通过项和未验证边界。 +7. 展示实际变更、验证结果、剩余风险和建议提交信息;只有用户明确授权后才执行本地提交。 + ## CraftKit 项目目录 - `.craftkit/` 是项目级 Agent 配置、规范、知识和运行状态的统一目录,目录名固定为全小写;不能把整个目录视为本地缓存或整体排除。 - 可共享内容包括 `project.json`、`agents/`、`standards/`、`knowledge/` 和 `handoff/`,可以根据用户确认正常提交和同步。 - `project.json` 记录项目类型、技术栈、源码与包边界、内部依赖标识、常用命令和规范入口;不得记录凭据、完整连接信息或个人机器绝对路径。 -- 项目初始化区分成熟项目与空项目:成熟项目优先延续当前项目已有且有充分证据的风格;空项目根据需求、用户选择和已授权的参考项目建立最小上下文,不自行猜测框架、包名或内部依赖。 +- 成熟项目优先延续已有且有充分证据的风格;空项目根据需求、用户选择和已授权参考建立最小上下文,不自行猜测框架、包名或内部依赖。 - 引用其他项目时先确认参考范围,只提炼结构、依赖、命名、测试、规范或工具约定;共享配置优先记录相对路径,不能共享的本地位置放入 `local/`。 -- 公开框架版本差异由 CraftKit 公共 profile 基于官方资料独立维护;项目只在 `project.json` 选择实际版本和 profile,并在 `standards/` 保存内部框架与覆盖规则。普通任务不得跨 profile 混读,升级或版本比较除外。 +- 公共框架版本差异由 CraftKit 公共 profile 维护;项目在 `project.json` 选择实际版本和 profile,并在 `standards/` 保存项目补充规则。 - `agents/` 保存项目对特定 Agent 的补充说明;`standards/` 保存项目开发规范;`knowledge/pitfalls/` 保存经验证且可复用的问题经验;`knowledge/decisions/` 保存重要技术决策。 - 本地内容统一放入 `local/`,缓存统一放入 `cache/`。`.craftkit/.gitignore` 必须排除 `/local/` 和 `/cache/`,但不得排除整个 `.craftkit/`。 - 本地交接默认位于 `.craftkit/local/handoff/current.md`;只有用户明确选择共享时才写入 `.craftkit/handoff/current.md`。 - 只在实际需要时创建目录,不一次生成空的 `agents/`、`standards/`、`knowledge/`、`handoff/`、`local/` 或 `cache/`。 -- Git Skill 默认排除 `.craftkit/local/**` 和 `.craftkit/cache/**`;其他 `.craftkit` 内容按普通项目资产评估,并在提交建议中单独标识为 Agent、规范或知识变更。 -- Git 交付或变更导出默认排除环境配置、本地配置和机器配置。只有适用的 `AGENTS.md`、`.craftkit/agents/` 或 `.craftkit/standards/` 明确要求交付,并经用户查看预览后再次确认,才可纳入;私钥、真实密钥和检测到的凭据始终禁止导出。 +- Git Skill 默认排除 `.craftkit/local/**` 和 `.craftkit/cache/**`;其他 `.craftkit` 内容按普通项目资产评估,并在提交建议中单独标识。 - 如果本地目录已经被 Git 跟踪,或整个 `.craftkit/` 被全局规则、`.git/info/exclude` 或项目规则忽略,应提示冲突并停止自动处理;不得自动修改索引或历史。 - `.craftkit/` 不得保存凭据、令牌、私钥、个人机器绝对路径或无必要的个人信息。 -## 迁移与知识产权边界 - -- 迁移采用“分析能力 → 编写中性规格 → 脱离原文独立实现”的方式,不做目录复制、批量替词或近义改写。 -- 不复制来源 Skill 的正文、脚本、提示词、模板、示例、规则库、注释或独特文档结构。 -- 不写入公司名称、产品名称、内部域名、内部包名、项目名、人员信息、业务规则或环境路径。 -- 公司框架 API、组件契约、审批流程、版本矩阵和内部消息格式不得直接迁移;评估时应先抽象其通用问题,并优先设计基于项目配置、公开标准或用户显式输入的中性平替。只有不存在独立通用价值、无法安全替代或与 Codex 场景不成立时才排除。 -- 通用技术规范必须根据公开标准或官方资料重新设计;需要引用时记录来源和许可证。 -- 来源文件只用于人工分析和哈希追踪,不得进入 `plugins/` 发布目录。 -- 上游变化只触发复核,不自动覆盖已迁移 Skill。 - -详细顺序和门禁见 [迁移计划](migration/MIGRATION_PLAN.md)。 - -## Skill 迁移工作流 - -所有新增迁移、来源更新和已迁移 Skill 的重新评估,都必须按以下顺序处理。用户未明确确认前,只能进行只读分析,不得创建、复制、改写或删除目标 Skill 文件。 - -### 批次策略 - -- 迁移前期采用“特殊样本”方式,不以固定数量为目标,而是优先覆盖不同结构、依赖、权限和风险类型。 -- 特殊样本至少覆盖纯指令、脚本与生成物、只读 Git、有副作用 Git、模板化文档、大型参考资料和复合工作流;缺少某一类型时不得宣告样本阶段完成。 -- 每个特殊样本原则上单独完成迁移前确认、实现验证和提交确认,以便及时修正规则。 -- 特殊样本全部通过后,应先总结可复用的命名、目录、改写、验证和状态同步规则,再进入批量阶段。 -- 批量阶段按同一插件、相近能力和相同风险类型组织,每批建议 6~12 个 Skill;高度同质时可整组处理,但不得跨越不同权限边界强行合批。 -- 批量迁移仍执行完整的两次确认。任何样本失败或出现新类型,都应暂停扩批并补充对应特殊样本。 - -### 连续迁移波次 - -当用户明确要求将一组已完整列明的剩余来源“按顺序一次迁移完,再统一确认提交”时,可以建立连续迁移波次: - -- 迁移前必须一次性展示全部来源、目标映射、合并或排除关系、执行顺序、写入范围和总体验收门禁,并取得用户明确确认。 -- 确认后可以按既定阶段连续实现,不再逐阶段请求迁移确认;不得加入方案外来源、目标 Skill 或新权限。 -- 每个阶段仍须独立运行对应测试和敏感内容扫描。阶段失败时先在既定范围内修复;出现需扩大范围、改变目标或无法安全替代的情况时暂停并重新确认。 -- 波次执行期间不创建 Git 提交。全部阶段完成后统一展示来源状态、目标文件、测试证据、未验证边界和建议提交序列,等待一次最终确认。 -- 最终一次“确认提交”可以授权按已展示顺序创建多个逻辑清晰的本地提交;不得因此推送、发布、创建标签或修改远端历史。 - -### 1. 阅读迁移计划和现状 - -- 完整阅读 `migration/MIGRATION_PLAN.md`、`migration/README.md` 和 `migration/source-lock.json`。 -- 检查 Git 状态、目标插件目录和现有 Skill,避免覆盖未提交改动或重复实现。 -- 只读检查相关来源 Skill 及其辅助资源,确认来源提交、文件哈希、依赖关系和变化范围。 -- 区分首次迁移、上游更新、目标重构和排除项复核,不把不同类型混为一次迁移。 - -### 2. 列出来源 Skill 和作用 - -在任何写入前,向用户列出本批计划处理的来源 Skill: - -| 项目 | 内容 | -| --- | --- | -| 来源标识 | 使用本地迁移标识,不在发布目录写入公司品牌 | -| 来源 Skill | 原始名称和相对路径 | -| 当前作用 | 根据源码确认的能力、输入、输出和关键边界 | -| 迁移类型 | 新增、更新、合并、中性平替、公开资料重建或排除 | -| 风险 | 公司专属知识、内部依赖、重复能力和兼容性问题 | - -来源作用必须有文件依据;无法从静态内容确认的运行行为应明确标记为未验证。 - -同一目标能力存在多个来源时,应在一个迁移方案中共同评估,明确合并、取舍、中性平替或排除关系,不按来源机械创建多个目标 Skill。来源含有大量专有规则时,不得只罗列删除项;必须说明通用问题如何由配置、标准接口、用户输入或项目资料替代。 - -### 3. 给出目标名称、作用和组织结构 - -针对每个来源 Skill,先给出建议方案: - -| 项目 | 内容 | -| --- | --- | -| 目标插件 | `dev`、`doc`、`git`、`knowledge` 或 `skill` | -| 目标名称 | 简短、中性、动作或明确领域导向的名称 | -| 目标作用 | 迁移后保留的通用能力和明确排除的内容 | -| 组织结构 | `SKILL.md` 及确有需要的 `agents/`、`scripts/`、`references/`、`assets/` | -| 合并关系 | 独立 Skill、合并到已有 Skill、替代旧 Skill 或排除 | -| 验证方案 | 静态校验、脚本测试、真实请求和敏感内容扫描 | - -此阶段只输出设计,不创建目标目录。若多个来源能力可以合并,应优先给出合并方案,避免一一照搬来源结构。 - -### 4. 与用户确认迁移方案 - -- 展示本批范围、目标映射、组织结构、排除内容和验证方式后,明确请求用户确认。 -- 用户可以确认全部方案,也可以调整名称、拆分或合并方式、迁移顺序和排除项。 -- 只有“确认执行”“按此方案迁移”等明确授权才允许进入写入阶段。 -- 用户仅要求评估、查看方案或继续讨论时,不视为迁移授权。 -- 确认后的授权只覆盖已展示范围;新增 Skill、扩大来源范围或改变组织方式必须重新确认。 - -### 5. 执行迁移并更新文档 - -获得确认后: - -1. 按中性能力规格独立实现目标 Skill,不复制来源目录或正文。 -2. 运行 `AGENTS.md` 中规定的全部验证,并记录真实结果和未验证边界。 -3. 更新 `migration/source-lock.json` 的来源哈希、目标路径、状态和复核日期。 -4. 更新 `migration/MIGRATION_PLAN.md` 的执行清单和批次进度。 -5. 更新根 `README.md`、所属插件说明或其他确实受影响的文档;不得创建重复状态文档。 -6. 向用户报告新增、更新、合并和排除结果,以及测试、扫描和剩余风险。 -7. 展示本批待提交文件清单和建议的 Conventional Commit 提交信息,进入第二次确认。 - -### 6. 与用户确认并提交到本地 - -- 迁移前确认只授权执行迁移,不自动授权 Git 提交。 -- 迁移和文档同步完成后,必须再次等待用户明确确认;“确认提交”“提交到本地”等表述才视为提交授权。 -- 用户要求调整、补测或继续检查时,先完成相应工作并重新展示结果,不得沿用此前的提交确认。 -- 获得确认后,只暂存本批迁移方案中已展示且验证通过的文件,不使用会混入其他改动的宽泛暂存命令。 -- 暂存后执行 `git diff --cached --check`,并复核暂存文件清单;发现额外文件或敏感内容时停止提交并报告。 -- 使用中文 Conventional Commit 信息创建本地提交,然后检查提交内容和工作区状态。 -- 本地提交成功后向用户报告提交哈希、提交信息、文件范围和剩余未提交改动。 -- 第二次确认只授权本地提交,不包含推送、创建标签、发布插件或修改远端历史;这些操作必须另行获得明确授权。 -- 连续迁移波次按“连续迁移波次”约定执行一次最终确认;若计划创建多个本地提交,必须在确认前展示每个提交的范围和建议信息。 - ## 验证要求 每个新增或修改的 Skill 至少完成: -1. 检查目录名、frontmatter 和引用路径。 -2. 运行 Skill 校验器。 -3. 运行所属插件 manifest 校验器。 -4. 执行公司标识、内部路径、凭据和占位符扫描。 -5. 有脚本时运行核心行为测试;有生成物时检查实际产物。 -6. 用一个真实请求验证触发边界和输出结果。 -7. 更新 `migration/source-lock.json` 中的状态、目标路径和复核日期。 +1. 检查目录名、frontmatter、引用路径和工具名称。 +2. 运行仓库现有的 Skill 校验器及所属插件校验器。 +3. 检查凭据、无效路径、未替换占位符和无效工具名称。 +4. 有脚本时运行核心行为测试;有生成物时检查实际产物。 +5. 以真实请求检查触发边界、权限边界和输出是否符合描述。 静态检查通过不代表真实行为已经验证,交付时应分别说明静态校验、脚本测试和实际场景验证结果。 @@ -189,5 +96,5 @@ plugins//skills// - 使用 Conventional Commits,提交说明使用中文。 - 推荐类型:`feat`、`fix`、`docs`、`refactor`、`test`、`chore`。 - 提交前执行 `git status --short`、`git diff --cached --check` 并检查暂存文件清单。 -- 不提交本地来源配置、凭据、缓存、运行产物和 IDE 私有状态。 +- 不提交本地配置、凭据、缓存、运行产物和 IDE 私有状态。 - 不使用破坏性历史改写,也不推送,除非用户明确授权。 diff --git a/README.md b/README.md index f9ad7f3..5b92fb6 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,23 @@ # CraftKit -CraftKit 是一组面向 Codex 插件市场的中性 Skill 工具。项目从通用问题出发独立实现能力,不携带任何公司的品牌、内部框架、业务规则、源码、模板或环境信息。 - -## 文档导航 - -- [协作约定](AGENTS.md):项目范围、Skill 设计、迁移边界、验证和 Git 规则。 -- [迁移计划](migration/MIGRATION_PLAN.md):迁移批次、依赖顺序、合并原则和验收门禁。 -- [迁移追踪说明](migration/README.md):来源指纹和状态维护方式。 +CraftKit 是面向 Codex 的通用插件工具集,覆盖软件开发、文档处理、Git 交付、项目知识维护和 Skill 管理。每个插件均可独立安装,能力描述保持简洁、中性,并以当前 Codex 能力和公开标准为基础维护。 ## 插件组成 -| 插件 | 用途 | 当前状态 | -| --- | --- | --- | -| `dev` | 软件设计、实现、审查、测试、升级与估算 | 已迁移 22 个 Skill,覆盖完整通用开发流程 | -| `doc` | 文档转换、整理与写作 | 已迁移 8 个 Skill,新增报告、消息与需求整理 | -| `git` | 分支、提交、变更提取、集成与发布 | 已迁移 6 个 Skill,发布操作按阶段分别授权 | -| `knowledge` | 项目初始化、交接、复盘与经验 | 已迁移 `handoff`、`init`、`trace`、`distill`、`lessons`、`worklog` | -| `skill` | 项目规范及 Skill 创建、迁移与维护 | 已迁移 `guidance`、`guidance-edit` | +| 插件 | 用途 | +| --- | --- | +| `dev` | 软件需求分析、设计、实现、审查、测试、升级与估算 | +| `doc` | 文档转换、整理、归档与写作 | +| `git` | 分支、提交、变更提取、集成与发布准备 | +| `knowledge` | 项目初始化、任务交接、问题复盘与经验维护 | +| `skill` | 项目规范检索、维护与 Skill 辅助工具 | ## 目录结构 ```text CraftKit/ -├─ .agents/plugins/marketplace.json # 仓库级 Codex 市场清单 -├─ AGENTS.md # Codex 与贡献者协作约定 -├─ migration/ # 迁移计划、来源指纹与状态追踪 +├─ .agents/plugins/marketplace.json # 仓库级 Codex 插件市场清单 +├─ AGENTS.md # 项目协作与维护约定 ├─ plugins/ # 可独立安装的插件 │ ├─ dev/ │ ├─ doc/ @@ -34,15 +27,16 @@ CraftKit/ └─ README.md ``` -## 开发约束 +## 设计原则 -- Skill 必须独立重写,不复制公司插件的正文、脚本、模板、示例或规则库。 -- 专有能力应先抽象为可配置、基于公开标准或用户显式输入的中性平替;不能仅因专有内容较多就整体排除。 -- 发布内容不得出现公司名称、产品名称、内部域名、内部包名、项目名或人员信息。 -- 每个 Skill 文件夹名必须与 `SKILL.md` frontmatter 的 `name` 一致。 -- 新增或修改插件后必须执行 Skill 校验、插件校验和去公司化扫描。 -- 上游变化只用于触发人工复核,不得自动覆盖已迁移内容。 +- 每个 Skill 聚焦一个清晰能力,名称与触发描述准确,避免宽泛兜底或重复功能。 +- 优先读取当前项目的代码、配置、规范和用户输入,不把特定组织、产品或环境假设写入通用能力。 +- 公共技术规则基于适用版本的官方资料维护;项目专属规则保存在项目自己的 `.craftkit/standards/` 中。 +- 涉及写文件、Git 状态、外部系统或发布的操作必须明确边界,并按风险取得用户授权。 +- 静态检查、脚本测试和真实场景验证分别记录,不用其中一种替代另一种。 ## 本地使用 -这是仓库级 marketplace。安装前先将本仓库注册为本地 marketplace,再按需安装其中的插件。正式发布前还需要补充许可证、公开仓库地址、作者信息、隐私政策及市场素材。 +本仓库提供仓库级 marketplace。将仓库注册为本地 marketplace 后,可按需安装 `dev`、`doc`、`git`、`knowledge` 或 `skill` 插件。 + +正式公开发布前,还需补充许可证、公开仓库地址、作者信息、隐私政策和市场素材。 diff --git a/migration/MIGRATION_PLAN.md b/migration/MIGRATION_PLAN.md deleted file mode 100644 index 6aaea3f..0000000 --- a/migration/MIGRATION_PLAN.md +++ /dev/null @@ -1,305 +0,0 @@ -# Skill 迁移计划 - -## 1. 目标 - -将两个本地来源中的通用能力整理为 CraftKit Skill。迁移结果必须适用于 Codex 插件市场,并与来源项目的品牌、内部框架、业务知识和运行环境解耦。 - -迁移不追求来源 Skill 与目标 Skill 一一对应。重复能力应合并;公司专属实现不得迁移,但应优先将其解决的通用问题重建为中性平替,确无独立价值或无法安全替代时才排除。目标数量以职责清晰和实际复用价值为准。 - -## 2. 迁移路径 - -```text -来源发现 - → 风险分类 - → 重复能力合并 - → 中性能力规格 - → 独立实现 - → 行为验证 - → 敏感内容扫描 - → Codex 与插件校验 - → 更新迁移状态 -``` - -禁止采用“完整复制来源目录后批量替换名称”的方式。中性能力规格形成后,目标实现应基于该规格、当前 Codex 能力和公开资料完成。 - -## 3. 状态模型 - -| 状态 | 含义 | 进入条件 | -| --- | --- | --- | -| `pending` | 待评估 | 已发现来源 Skill | -| `specified` | 已完成中性规格 | 已明确目标、边界、依赖和排除内容 | -| `rewriting` | 独立实现中 | 已创建目标 Skill | -| `review` | 待复核 | 功能完成,但版权、依赖或行为仍需确认 | -| `migrated` | 已迁移 | 全部门禁通过 | -| `excluded` | 不迁移 | 公司专属或没有独立价值 | -| `superseded` | 已合并 | 能力已由另一个目标 Skill 覆盖 | - -## 4. 迁移批次 - -迁移分为“特殊样本”和“同质批量”两个阶段。前者用于覆盖迁移机制的不同风险类型,后者在规则稳定后提高吞吐量。批次数量不固定:特殊样本原则上逐个迁移,批量阶段每批建议 6~12 个同质 Skill。 - -### 第 0 批:迁移基础设施 - -第 0 批属于仓库内部维护工具,不发布为市场 Skill: - -1. `migration/scripts/scan_sources.py`:只读发现来源 Skill、辅助资源和文件哈希。 -2. `AGENTS.md` 迁移工作流:完成分类、目标设计和用户确认。 -3. `migration/scripts/check_skill.py`:检查结构、引用、占位符、敏感内容和凭据风险。 -4. `migration/scripts/update_lock.py`:预览或更新来源指纹和迁移状态。 - -完成门槛:能够对全部来源 Skill 生成稳定清单,并在不复制来源内容的情况下更新状态。 - -### 第 1 阶段:特殊样本 - -特殊样本不追求模块连续性,而是覆盖不同迁移机制: - -1. `doc/format-md`:纯指令型样本,已完成。 -2. `doc/docx-to-md`:多来源合并、脚本、依赖和生成物样本,已完成。 -3. `git/commit-msg`:只读 Git 状态分析样本,已完成。 -4. `git/branch`:修改仓库状态和二次授权边界样本,已完成。 -5. `knowledge/handoff`:模板化文档与项目上下文样本,已完成。 -6. `skill/guidance`:大型参考资料、索引和渐进式加载样本,已完成;只检索项目资料和基于公开一级资料独立重建的中性公共基线。 -7. `knowledge/init`:成熟项目与空项目双模式初始化、参考项目提炼和共享/本地信息边界样本,已完成。 -8. `skill/migrate`:不迁移。来源能力用于把 Claude Code Skill 转换为 Codex Skill;CraftKit 自始按 Codex 规范开发,不存在平台转换需求。来源扫描、目标检查和状态追踪继续由 `migration/scripts/` 作为仓库维护设施承担。 - -完成门槛:适用样本均通过对应验证,不适用样本记录排除依据,并形成可复用的命名、目录、独立实现、测试、扫描和状态同步规则。出现未覆盖的新结构或权限类型时,应补充样本,不直接扩批。当前特殊样本阶段已完成,可以进入同质批量迁移。 - -### 第 2 阶段:同质批量迁移 - -特殊样本完成后,按同一插件、相近能力和相同风险类型组织批量迁移: - -- 每批建议 6~12 个 Skill,高度同质时可以整组处理。 -- 不把只读能力与有副作用能力、纯指令与复杂脚本、普通迁移与公开资料重建强行合为一批。 -- 每批仍需迁移前确认和提交前确认,并统一更新 README、迁移计划和来源状态。 -- 任一验收门禁失败时暂停该批,不继续扩大范围。 - -### 文档转换批次 - -所属插件:`doc` - -1. `docx-to-md` -2. `md-to-docx`(已完成) -3. `xlsx-to-md`(已完成) -4. `archive`(已完成,以可配置规则平替固定目录、业务文件名、专有章节拆分和内部系统校验) - -重点验证图片、表格、合并单元格、编码、覆盖策略和路径安全。脚本及测试数据必须独立创建。 - -### Git 工作流批次 - -所属插件:`git` - -1. `branch`(已完成) -2. `identity`(已完成,只管理 Git 提交用户名和邮箱) -3. `export`(已完成,环境配置默认排除,规范明确要求并再次确认后才可导出) -4. `integrate`(已完成,按项目规范通过隔离 worktree 评估、准备和发布预集成分支) - -`commit-msg` 将作为只读 Git 特殊样本先行完成。涉及提交、合并和远端操作的 Skill 必须保留明确授权边界,并保护脏工作区。 - -### 知识管理批次 - -所属插件:`knowledge` - -1. `trace`(已完成) -2. `distill`(已完成) -3. `lessons`(已完成,合并初始化、维护、审计和提升) -4. `worklog`(已完成) -5. `init`(已完成) - -来源中与经验初始化、提升和回扫相关的多个能力统一合并为 `maintain-lessons`,通过模式区分具体工作。 - -### 开发主流程批次 - -所属插件:`dev` - -```text -plan-change - ├─ design-backend → implement-backend → test-backend - └─ design-frontend → implement-frontend → test-ui - ↓ - review-code -``` - -建议顺序: - -1. `plan-change`(已完成) -2. `design-backend`(已完成) -3. `design-frontend`(已完成) -4. `prepare-api`(已完成) -5. `implement-backend`(已完成) -6. `implement-frontend`(已完成) -7. `review-code`(已完成) -8. `test-backend`(已完成) -9. `test-ui`(已完成) -10. `analyze-bugs`(已完成) - -多个来源中的代码检查、代码审查能力合并为 `review-code`,通过工作区、提交和分支三种模式覆盖。 - -### 项目规范与前端辅助批次 - -1. `dev/component`(已完成,基于项目证据完成组件选型与契约查证) -2. `dev/style`(已完成,基于项目视觉基线设计样式) -3. `dev/form`(已完成,设计表单结构、响应布局和可访问性) -4. `skill/guidance`(特殊样本已完成) -5. `skill/guidance-edit`(已完成,建立、检查和维护项目规范索引) - -这里只实现读取和维护“当前项目自身规范”的机制,不随 CraftKit 提供任何来源项目规范。 -原前端导航能力由上述 Skill 的精确触发描述和 Codex 自动发现替代,不再维护独立路由 Skill。混合任务按 `style` → `component` → `form` → 前端设计或实现的依赖顺序处理。 - -### 基于公开资料重建批次 - -以下能力不从来源文本改写,而是根据官方资料重新设计: - -1. `dev/design-api`(已完成) -2. `dev/design-db`(已完成) -3. `dev/review-java`(已完成) -4. `dev/review-frontend`(已完成) -5. `dev/design-frontend-data`(已完成) -6. `dev/review-mybatis`(已完成) -7. `dev/design-workflow`(已完成) - -中性规格必须记录所采用的公开标准、官方文档和许可证信息。 - -### 平替或排除结果 - -- 公司框架知识与专属规范由 `skill/guidance`、项目源码和 `.craftkit/standards/` 平替。 -- 内部组件契约、审批规则和消息格式不迁移,目标 Skill 只读取项目证据和用户输入。 -- 升级、估算与发布已重建为通用能力,不携带内部版本矩阵和仓库流程。 -- 浏览器代理配置由 Codex 浏览器或计算机操作能力平替。 -- Skill 路由依靠精确触发描述和 Codex 自动发现,不增加独立路由器。 -- 非 Codex 平台转换能力保持排除。 - -### 剩余迁移波次 - -截至 2026-08-26,原有 29 个 `pending` 来源已在一个连续波次内全部处理完成。所有来源均已闭合为 `migrated`、`superseded` 或 `excluded`;本波次完成验证后统一等待本地提交确认。 - -#### 阶段 1:公开基线与专项设计 - -| 来源能力 | 目标 Skill | 处理方式 | -| --- | --- | --- | -| API 约定 | `dev/design-api` | 基于 HTTP、OpenAPI 等官方资料独立重建 | -| 数据库约定 | `dev/design-db` | 基于目标数据库官方资料和项目证据独立重建 | -| Java 约定 | `dev/review-java` | 基于 Java 与所用框架对应版本官方资料独立重建 | -| 前端约定 | `dev/review-frontend` | 基于当前框架版本官方资料独立重建 | -| 前端数据约定 | `dev/design-frontend-data` | 建立请求、状态与视图模型的通用设计边界 | -| MyBatis 约定 | `dev/review-mybatis` | 基于 MyBatis 官方资料独立重建 | -| 工作流约定 | `dev/design-workflow` | 以项目工作流契约和用户输入平替内部审批规则 | - -公开资料只采用一级官方来源,记录适用版本、重建日期和许可证或引用边界;不得改写来源规范正文。 - -#### 阶段 2:代码实现 - -| 来源 Skill | 目标 Skill | 状态关系 | -| --- | --- | --- | -| `back-code`、同类后端编码来源 | `dev/implement-backend` | 两个来源合并,一个迁移、一个 `superseded` | -| `front-code`、同类前端编码来源 | `dev/implement-frontend` | 两个来源合并,一个迁移、一个 `superseded` | - -实现 Skill 直接修改业务源码,必须保护脏工作区、使用项目真实版本与规范、执行风险相称的验证,且不自动提交或推送。 - -#### 阶段 3:审查、测试与问题分析 - -| 来源能力 | 目标 Skill | 处理方式 | -| --- | --- | --- | -| 两套代码检查与一套代码审查 | `dev/review-code` | 合并为工作区、提交和分支审查模式 | -| 后端单元测试 | `dev/test-backend` | 按当前测试框架和项目模式生成或修改测试 | -| UI 测试 | `dev/test-ui` | 按用户可观察行为设计并执行浏览器测试 | -| Bug 列表分析 | `dev/analyze-bugs` | 解析通用结构化问题清单并形成证据化报告 | - -本阶段不得把静态检查、单元测试、浏览器测试和真实环境验收混为同一结论。 - -#### 阶段 4:交付、升级与估算 - -| 来源能力 | 目标 Skill 或处理结果 | 处理方式 | -| --- | --- | --- | -| 后端升级、前端升级 | `dev/upgrade` | 合并为基于源版本、目标版本和官方迁移资料的通用升级流程 | -| 版本发布 | `git/release` | 只准备版本、变更摘要、标签和发布检查;远端动作单独授权 | -| 工作量评估 | `dev/estimate` | 基于范围、依赖、风险和假设输出区间估算 | -| 框架约定 | `skill/guidance` | `superseded`,内部规则由项目 `.craftkit/standards/` 提供 | -| 框架知识查询 | `skill/guidance` | `superseded`,改为检索项目证据和公开官方资料 | - -#### 阶段 5:写作与平台工具收尾 - -| 来源能力 | 目标 Skill 或处理结果 | 处理方式 | -| --- | --- | --- | -| 领导汇报 | `doc/report` | 中性化为面向不同受众的事实型工作汇报 | -| 消息模板 | `doc/message` | 中性化为项目消息与通知草稿 | -| 技术内容转需求 | `doc/requirements` | 将技术输入转换为可确认的需求说明 | -| 能力创建器 | 官方 `skill-creator` | `superseded`,不重复发布同类 CraftKit Skill | -| 浏览器代理配置 | Codex 浏览器或计算机操作能力 | `superseded`,不迁移宿主专属代理配置 | -| Skill 转 Cursor | 无 | `excluded`,CraftKit 只面向 Codex | - -#### 波次总门禁 - -1. 29 个来源全部更新为 `migrated`、`superseded` 或 `excluded`,不得遗留 `pending`。 -2. 所有目标 Skill 通过结构、引用、敏感内容和真实请求边界测试。 -3. 公开基线记录官方来源、适用版本和重建边界。 -4. 有脚本的 Skill 完成隔离行为测试;有外部工具的 Skill 明确权限和未验证边界。 -5. README、插件版本、迁移计划和来源锁同步一致。 -6. 全量测试、JSON 校验和 `git diff --check` 通过。 -7. 波次完成后只展示提交方案,不创建提交;用户统一确认后按逻辑范围创建本地提交。 - -## 5. 合并原则 - -来源数量不等于目标数量。优先执行以下合并: - -| 来源能力类型 | 目标 Skill | -| --- | --- | -| 代码检查、提交审查、分支审查 | `review-code` | -| 多套开发计划 | `plan-change` | -| 多套后端设计 | `design-backend` | -| 多套前端设计 | `design-frontend` | -| 多套后端实现 | `implement-backend` | -| 多套前端实现 | `implement-frontend` | -| 多套 Word 转 Markdown | `docx-to-md` | -| 经验初始化、提升、回扫 | `maintain-lessons` | -| 规范检索和索引维护 | `guidance`、`guidance-edit` | - -目标 Skill 总量不设硬指标,预期控制在约 30 个,避免细碎能力和重复触发。 - -## 6. 单个 Skill 的迁移步骤 - -1. 记录来源相对路径、提交和 SHA-256。 -2. 判断该能力是迁移、合并、重建还是排除。 -3. 只提炼目标、输入、输出、关键边界和真实用例,形成中性规格。 -4. 确定目标插件和简短 Skill 名称。 -5. 基于中性规格和公开资料独立实现。 -6. 对脚本执行单元或行为测试,对文档型 Skill 执行真实请求测试。 -7. 扫描敏感内容、来源残留、无效工具名和宿主绑定表达。 -8. 运行 Skill 与插件校验。 -9. 将状态更新为 `migrated`,记录目标路径、目标版本和复核日期。 -10. 展示迁移结果、验证证据、待提交文件和建议提交信息,等待用户再次确认。 -11. 用户确认后精确暂存本批文件并创建本地提交;推送和发布仍需独立授权。 - -## 7. 每批验收门禁 - -- Skill 名称简洁,目录名与 frontmatter 一致。 -- `description` 能准确触发,不是大而全的能力描述。 -- 不存在来源正文、脚本、模板、示例或独特结构的直接复制。 -- 公司标识、内部域名、包名、路径和人员信息扫描为零。 -- 所有引用文件存在,脚本和生成物经过实际验证。 -- Skill 校验和所属插件校验通过。 -- 至少一个真实请求用例通过。 -- `source-lock.json` 已更新。 - -任一门禁未通过时,不进入下一批的大规模迁移。 - -## 8. 当前执行顺序 - -- [x] 扩展 `source-lock.json` 的 Skill 级记录结构。 -- [x] 实现来源只读扫描脚本。 -- [x] 在 `AGENTS.md` 中固化分类、设计和确认流程。 -- [x] 实现 Skill 与敏感内容检查脚本。 -- [x] 实现迁移台账预览和更新脚本。 -- [x] 迁移并验证首个样板 `doc/format-md`。 -- [x] 迁移并验证脚本型特殊样本 `doc/docx-to-md`。 -- [x] 迁移并验证只读 Git 特殊样本 `git/commit-msg`。 -- [x] 迁移并验证有副作用 Git 特殊样本 `git/branch`。 -- [x] 迁移并验证模板化交接特殊样本 `knowledge/handoff`。 -- [x] 完成其余特殊样本并总结批量迁移规则。 -- [ ] 按插件和风险类型继续推进同质批量迁移。 -- [x] 完成首个同质批量:`md-to-docx`、`xlsx-to-md`、`archive`。 -- [x] 完成 Git 本地操作批次:`identity`、`export`。 -- [x] 完成 Git 高风险隔离集成样本:`integrate`。 -- [x] 完成知识管理批次:`trace`、`distill`、`lessons`、`worklog`。 -- [x] 完成前端辅助批次:`component`、`style`、`form`,并以精确触发替代独立路由。 -- [x] 完成项目规范维护能力:`guidance-edit`。 -- [x] 完成开发分析与设计批次:`plan-change`、`design-backend`、`design-frontend`、`prepare-api`。 diff --git a/migration/README.md b/migration/README.md deleted file mode 100644 index 78508a3..0000000 --- a/migration/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# 迁移追踪说明 - -本目录只记录来源指纹、迁移状态和人工复核结果,不存放或打包公司插件内容。 - -- [迁移计划](MIGRATION_PLAN.md):批次顺序、依赖、合并策略与验收门禁。 -- `source-lock.json`:可提交的来源指纹和迁移状态。 -- `local-sources.json`:本地来源路径,不提交;结构参考 `local-sources.example.json`。 -- `scripts/scan_sources.py`:只读生成来源 Skill 指纹清单。 -- `scripts/check_skill.py`:检查迁移后 Skill 的结构、引用和敏感内容。 -- `scripts/update_lock.py`:根据扫描报告预览或更新迁移台账。 -- `tests/`:迁移脚本的隔离行为测试。 - -扫描报告可能包含来源 Skill 名称和相对路径,应写入本地临时目录或忽略文件,不进入插件发布内容。 - -同步流程: - -1. 读取来源仓库当前提交与 Skill 文件哈希。 -2. 与 `source-lock.json` 比较,生成新增、修改、删除清单。 -3. 人工判断变化是否属于可公开迁移的通用能力。 -4. 在 CraftKit 中独立重写并完成验证。 -5. 更新来源指纹、目标路径、状态和复核日期。 - -允许的状态:`pending`、`specified`、`rewriting`、`review`、`migrated`、`excluded`、`superseded`。 diff --git a/migration/local-sources.example.json b/migration/local-sources.example.json deleted file mode 100644 index 4e1cc09..0000000 --- a/migration/local-sources.example.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "source-a": "D:/path/to/local/source-a", - "source-b": "D:/path/to/local/source-b" -} diff --git a/migration/scripts/check_skill.py b/migration/scripts/check_skill.py deleted file mode 100644 index fe368d3..0000000 --- a/migration/scripts/check_skill.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 -"""检查 CraftKit Skill 的基础结构、引用和敏感内容。""" - -from __future__ import annotations - -import argparse -import json -import re -from pathlib import Path - -NAME_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$") -LINK_RE = re.compile(r"\[[^]]+]\((?!https?://|#)([^)]+)\)") -SECRET_RE = re.compile( - r"(?i)(api[_-]?key|password|private[_-]?key|access[_-]?token)\s*[:=]\s*[^\s]+" -) - - -def frontmatter(text: str) -> dict[str, str]: - """解析当前校验所需的简单顶层 frontmatter 字段。""" - if not text.startswith("---\n"): - return {} - end = text.find("\n---", 4) - if end < 0: - return {} - values: dict[str, str] = {} - for line in text[4:end].splitlines(): - if ":" in line and not line.startswith((" ", "\t")): - key, value = line.split(":", 1) - values[key.strip()] = value.strip().strip("'\"") - return values - - -def check_skill(root: Path, forbidden: list[str]) -> dict: - """返回结构化问题列表,不修改被检查目录。""" - issues: list[dict[str, str]] = [] - skill_md = root / "SKILL.md" - if not skill_md.is_file(): - return {"skill": root.name, "issues": [{"code": "missing-skill-md", "path": "SKILL.md"}]} - text = skill_md.read_text(encoding="utf-8-sig") - metadata = frontmatter(text) - name = metadata.get("name", "") - if not NAME_RE.fullmatch(name): - issues.append({"code": "invalid-name", "path": "SKILL.md"}) - if name != root.name: - issues.append({"code": "name-path-mismatch", "path": "SKILL.md"}) - if not metadata.get("description"): - issues.append({"code": "missing-description", "path": "SKILL.md"}) - - for path in sorted(item for item in root.rglob("*") if item.is_file()): - relative = path.relative_to(root).as_posix() - content = path.read_text(encoding="utf-8-sig", errors="replace") - if "[TODO:" in content: - issues.append({"code": "todo-placeholder", "path": relative}) - if SECRET_RE.search(content): - issues.append({"code": "possible-secret", "path": relative}) - lowered = content.casefold() - for term in forbidden: - if term.casefold() in lowered: - issues.append({"code": f"forbidden:{term}", "path": relative}) - if path.suffix.lower() == ".md": - for link in LINK_RE.findall(content): - target = link.split("#", 1)[0] - if target and not (path.parent / target).resolve().is_file(): - issues.append({"code": "broken-link", "path": f"{relative}:{link}"}) - return {"skill": root.name, "issues": issues} - - -def main() -> None: - parser = argparse.ArgumentParser(description="检查 CraftKit Skill") - parser.add_argument("skill", nargs="+", type=Path) - parser.add_argument("--forbid", action="append", default=[]) - args = parser.parse_args() - results = [check_skill(path.resolve(), args.forbid) for path in args.skill] - print(json.dumps({"results": results}, ensure_ascii=False, indent=2)) - raise SystemExit(1 if any(item["issues"] for item in results) else 0) - - -if __name__ == "__main__": - main() diff --git a/migration/scripts/scan_sources.py b/migration/scripts/scan_sources.py deleted file mode 100644 index 789933b..0000000 --- a/migration/scripts/scan_sources.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -"""只读扫描来源目录中的 Skill,并输出稳定的 JSON 清单。""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import subprocess -from datetime import datetime, timezone -from pathlib import Path - - -def file_hash(path: Path) -> str: - """按二进制内容计算单文件 SHA-256。""" - digest = hashlib.sha256() - with path.open("rb") as stream: - for block in iter(lambda: stream.read(65536), b""): - digest.update(block) - return digest.hexdigest() - - -def directory_hash(root: Path) -> tuple[str, int]: - """把相对路径和文件内容共同纳入哈希,确保结构变化也可被识别。""" - digest = hashlib.sha256() - files = sorted(path for path in root.rglob("*") if path.is_file()) - for path in files: - relative = path.relative_to(root).as_posix() - digest.update(relative.encode("utf-8")) - digest.update(b"\0") - digest.update(bytes.fromhex(file_hash(path))) - return digest.hexdigest(), len(files) - - -def read_skill_name(skill_md: Path) -> str: - """从 YAML frontmatter 中读取 name;解析失败时回退为目录名。""" - for line in skill_md.read_text(encoding="utf-8-sig").splitlines(): - if line.startswith("name:"): - return line.split(":", 1)[1].strip().strip("'\"") or skill_md.parent.name - return skill_md.parent.name - - -def git_commit(root: Path) -> str | None: - """尽力读取来源提交;非 Git 目录时返回空值,不阻塞文件扫描。""" - result = subprocess.run( - ["git", "-c", f"safe.directory={root.as_posix()}", "-C", str(root), "rev-parse", "HEAD"], - capture_output=True, - text=True, - check=False, - ) - return result.stdout.strip() if result.returncode == 0 else None - - -def scan_source(source_id: str, root: Path) -> dict: - """扫描单个来源,结果仅包含相对路径和指纹,不包含本地绝对路径。""" - if not root.is_dir(): - raise ValueError(f"来源目录不存在:{root}") - skills = [] - for skill_md in sorted(root.rglob("SKILL.md")): - skill_root = skill_md.parent - digest, file_count = directory_hash(skill_root) - children = sorted( - child.name for child in skill_root.iterdir() if child.is_dir() - ) - skills.append( - { - "name": read_skill_name(skill_md), - "relativePath": skill_root.relative_to(root).as_posix(), - "sha256": digest, - "fileCount": file_count, - "resources": children, - } - ) - return { - "id": source_id, - "commit": git_commit(root), - "skillCount": len(skills), - "skills": skills, - } - - -def parse_source(value: str) -> tuple[str, Path]: - """解析 id=path 参数,避免把本地路径写入输出文件。""" - if "=" not in value: - raise argparse.ArgumentTypeError("来源参数必须使用 id=path 格式") - source_id, raw_path = value.split("=", 1) - if not source_id.strip() or not raw_path.strip(): - raise argparse.ArgumentTypeError("来源标识和路径均不能为空") - return source_id.strip(), Path(raw_path).expanduser().resolve() - - -def main() -> None: - parser = argparse.ArgumentParser(description="只读扫描 Skill 来源目录") - parser.add_argument("--source", action="append", required=True, type=parse_source) - parser.add_argument("--output", type=Path, help="可选 JSON 输出路径;省略时输出到终端") - args = parser.parse_args() - payload = { - "schemaVersion": 1, - "generatedAt": datetime.now(timezone.utc).isoformat(), - "sources": [scan_source(source_id, root) for source_id, root in args.source], - } - content = json.dumps(payload, ensure_ascii=False, indent=2) + "\n" - if args.output: - args.output.parent.mkdir(parents=True, exist_ok=True) - args.output.write_text(content, encoding="utf-8") - else: - print(content, end="") - - -if __name__ == "__main__": - main() diff --git a/migration/scripts/update_lock.py b/migration/scripts/update_lock.py deleted file mode 100644 index 0cb3574..0000000 --- a/migration/scripts/update_lock.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 -"""根据扫描报告更新迁移台账;默认预览,显式 --apply 才写入。""" - -from __future__ import annotations - -import argparse -import hashlib -import json -from datetime import date -from pathlib import Path - -VALID_STATUS = { - "pending", "specified", "rewriting", "review", - "migrated", "excluded", "superseded", -} - - -def merge(lock: dict, report: dict) -> dict: - """保留人工维护字段,仅同步来源提交、数量和 Skill 指纹。""" - lock.setdefault("schemaVersion", 1) - lock["updatedAt"] = date.today().isoformat() - lock.setdefault("sources", {}) - lock.setdefault("skills", {}) - for source in report.get("sources", []): - source_id = source["id"] - lock["sources"][source_id] = { - "commit": source.get("commit"), - "skillCount": source["skillCount"], - } - for skill in source["skills"]: - # 发布台账不保存来源名称和路径;本地扫描报告负责提供可读映射。 - path_hash = hashlib.sha256(skill["relativePath"].encode("utf-8")).hexdigest() - key = f"{source_id}:{path_hash[:16]}" - current = lock["skills"].get(key, {}) - status = current.get("status", "pending") - if status not in VALID_STATUS: - raise ValueError(f"非法迁移状态:{key}={status}") - lock["skills"][key] = { - **current, - "sourcePathHash": path_hash, - "sourceSha256": skill["sha256"], - "status": status, - } - return lock - - -def main() -> None: - parser = argparse.ArgumentParser(description="更新 Skill 迁移台账") - parser.add_argument("--lock", required=True, type=Path) - parser.add_argument("--report", required=True, type=Path) - parser.add_argument("--apply", action="store_true", help="确认写入台账") - args = parser.parse_args() - lock = json.loads(args.lock.read_text(encoding="utf-8")) - report = json.loads(args.report.read_text(encoding="utf-8")) - content = json.dumps(merge(lock, report), ensure_ascii=False, indent=2) + "\n" - if args.apply: - temporary = args.lock.with_suffix(args.lock.suffix + ".tmp") - temporary.write_text(content, encoding="utf-8") - temporary.replace(args.lock) - else: - print(content, end="") - - -if __name__ == "__main__": - main() diff --git a/migration/source-lock.json b/migration/source-lock.json deleted file mode 100644 index 724f1ed..0000000 --- a/migration/source-lock.json +++ /dev/null @@ -1,538 +0,0 @@ -{ - "schemaVersion": 1, - "updatedAt": "2026-08-26", - "sources": { - "source-a": { - "commit": "31883f7e81c2bdb19505e591ba9db8a3d9fd4352", - "skillCount": 12 - }, - "source-b": { - "commit": "2947a163538cd119a4e241c9d36f061d6dcae841", - "skillCount": 52 - } - }, - "skills": { - "source-a:2c317f4ba8859f5e": { - "sourcePathHash": "2c317f4ba8859f5e210c18e69e99e8e472d38d97e193c5f213508d4afb672c11", - "sourceSha256": "ed08a64bf0f5004d43a9c09a40087aebec8f52323c5a47d8fc73f9f9aca9be0e", - "status": "migrated", - "target": "plugins/dev/skills/review-code", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-a:1b7843684955ce5a": { - "sourcePathHash": "1b7843684955ce5a0beb3ef16c6e751c5e9eb5a17409084fed806b74ec0c522f", - "sourceSha256": "eabf9fabba7b751a0b85547b141d92821f461e8d904f4fb15d5746eca1965769", - "status": "migrated", - "target": "plugins/dev/skills/plan-change", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-a:5d76364c580248a5": { - "sourcePathHash": "5d76364c580248a54449e5ec7ede0079f19df21fef95922d3538fb82c095b181", - "sourceSha256": "9a664d17f276b01311f661aa6e8fd40063708550c97cc3340246922194d8390b", - "status": "migrated", - "target": "plugins/dev/skills/implement-backend", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-a:a0b791b6dac96b81": { - "sourcePathHash": "a0b791b6dac96b8180df558278b5c7ef643b4806cc4e534ffe6395b9236666d3", - "sourceSha256": "603b4fd6e4b52c9b5d368c5ba6bdff333d32f3e03e9b5393686b0729eb4ffc7c", - "status": "migrated", - "target": "plugins/dev/skills/design-backend", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-a:0124493991019160": { - "sourcePathHash": "0124493991019160a06c65d6200db0a50abb212a79bcefab4be4631be85c07c8", - "sourceSha256": "eca051fb929c5bd74c34564631872c2d6755b4427a7c4fbb4d6240eeb6331ee3", - "status": "migrated", - "target": "plugins/dev/skills/prepare-api", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-a:b80c5913e146321c": { - "sourcePathHash": "b80c5913e146321ca5efef5e45f11172e79c542855c2ae65478fe253414e66ea", - "sourceSha256": "5e33d979bb08fdcc47b2dee2e0d5cb39c24825b391f8bbf5606e57cbd4c2ef2c", - "status": "migrated", - "target": "plugins/dev/skills/implement-frontend", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-a:cd36505dd718e889": { - "sourcePathHash": "cd36505dd718e88966ad74421c65e6c7cccd71a1436f2843eb8cfa7a4f1534c5", - "sourceSha256": "a0137bd615988c49a8967b4e0129618858268b90391ab2ed912cc72f338f41f8", - "status": "migrated", - "target": "plugins/dev/skills/design-frontend", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-a:e14c1a3cd3b041f6": { - "sourcePathHash": "e14c1a3cd3b041f650d71eb5701c71c0de76cb7a909e9a419f1d277159cd763a", - "sourceSha256": "7780de4d979a6bafd1f5f503f08261858907f110acddb630dfab36d32e54c6a8", - "status": "superseded", - "target": "official:skill-creator", - "reason": "Codex 官方 skill-creator 已覆盖通用 Skill 创建与更新能力", - "reviewedAt": "2026-08-26" - }, - "source-a:fdde0da43c1ec4c7": { - "sourcePathHash": "fdde0da43c1ec4c7fddad96b9172435af6be96ceafa2cc824f70e04a3cf11085", - "sourceSha256": "46a02dbff9c3fff57a0802853e2424983927f53c7f1cc3d8a42542403e62a161", - "status": "migrated", - "target": "plugins/doc/skills/docx-to-md", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-a:4efb6f9dd0f39dff": { - "sourcePathHash": "4efb6f9dd0f39dff13339e62ff24b672552efcbcedb007e9ea86fe41950adbb0", - "sourceSha256": "a914823bdf50d9e5c8ee70d6363c5c0e7aea341ed390587a9285c70a7047558e", - "status": "migrated", - "target": "plugins/doc/skills/xlsx-to-md", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-a:32e7cfc21d252985": { - "sourcePathHash": "32e7cfc21d25298559bffbe7f0918f0f6e7d2aac29e3eca39d1f43d8226cf934", - "sourceSha256": "0f0486e3f25279a1a1cb8cecfef4dc01830471b5d31b52a9d17a425919997bc2", - "status": "excluded", - "reason": "CraftKit 原生面向 Codex,不需要 Claude Code 到 Codex 的平台转换能力", - "reviewedAt": "2026-08-25" - }, - "source-a:bc91b57fa2a50049": { - "sourcePathHash": "bc91b57fa2a500498b31b0f1a87dfa8ab83c32e4b361b4ea1614ea6721234b7c", - "sourceSha256": "1436c7e25325c927d55d1dfc2a3117ec8e30845d66e06d2e6d7cc75abe4e0792", - "status": "excluded", - "reason": "CraftKit 仅面向 Codex,不增加 Cursor 平台转换能力", - "reviewedAt": "2026-08-26" - }, - "source-b:3fb4282b0b034d52": { - "sourcePathHash": "3fb4282b0b034d5286bccd9924c37bedbe857c4673798aa844f540fe0ee5186a", - "sourceSha256": "196cd701c67d06fce4ee3b9894cbed95e6bc9101cc2089f83ae8f35b61a11332", - "status": "migrated", - "target": "plugins/git/skills/release", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:0dedfcd01cd89e28": { - "sourcePathHash": "0dedfcd01cd89e28e83d841f78c270b1210399cc867fd3e1865c14acf1ea865a", - "sourceSha256": "be6a694d4de9b8a792e19e1ed116e2fdd54d91731d9efcadfca1b0055a4a3145", - "status": "superseded", - "target": "plugins/dev/skills/implement-backend", - "targetVersion": "0.1.0", - "reason": "后端实现能力已与另一来源合并并按项目证据中性重建", - "reviewedAt": "2026-08-26" - }, - "source-b:3f05784a0a86ba5f": { - "sourcePathHash": "3f05784a0a86ba5fb8f268797ca1ceabe8ca237b3c9d3c4396e622c1ef421cc6", - "sourceSha256": "9a39cd7af138519fe3de3f7a3cb031ea5da1243ff5779504a13215fa03a5a3c2", - "status": "superseded", - "target": "plugins/dev/skills/design-backend", - "targetVersion": "0.1.0", - "reason": "后端设计能力已与另一来源合并并按项目证据中性重建", - "reviewedAt": "2026-08-25" - }, - "source-b:41934e94a1922a1b": { - "sourcePathHash": "41934e94a1922a1bbccec4377246e67bd84f1ffbd2b9d1bf4027c1ed15d12260", - "sourceSha256": "a0db48d88dab0537e061146edf6dbdeb4b7f8a935febd1556f798ef234b29e5b", - "status": "migrated", - "target": "plugins/dev/skills/test-backend", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:0cfb12fd60357a34": { - "sourcePathHash": "0cfb12fd60357a346c8e0079536540fba81e406d58205098f1a5162c6ce7e9d1", - "sourceSha256": "aa722e0dbcb613748b75bc2f5c81b8a97cbff07905b1fcee6d58cb176135fc08", - "status": "superseded", - "target": "plugins/dev/skills/review-code", - "targetVersion": "0.1.0", - "reason": "代码检查能力已合并到统一代码审查入口", - "reviewedAt": "2026-08-26" - }, - "source-b:f171b05e239c2e2c": { - "sourcePathHash": "f171b05e239c2e2c5f2a0c636dfac0c2456b9d3b5215fdaa7fb86a33be1ee188", - "sourceSha256": "cd16d17204009509e6798badab786b9bd4d3969117b3ad4b9005337b76a4791b", - "status": "superseded", - "target": "plugins/dev/skills/plan-change", - "targetVersion": "0.1.0", - "reason": "开发计划能力已与另一来源合并为通用变更计划", - "reviewedAt": "2026-08-25" - }, - "source-b:3b29e4441f642e65": { - "sourcePathHash": "3b29e4441f642e65d442fd284f37a45364cebb7a6c53446c354d15c70e532410", - "sourceSha256": "d2689d3ac45070389473cb8cc414df44e82ad267915d8cd7c53c023f5b8f3696", - "status": "superseded", - "target": "plugins/dev/skills/implement-frontend", - "targetVersion": "0.1.0", - "reason": "前端实现能力已与另一来源合并并按项目证据中性重建", - "reviewedAt": "2026-08-26" - }, - "source-b:f6ec7bd5501e6ea5": { - "sourcePathHash": "f6ec7bd5501e6ea5f6e907a586a8d7cbe5fbbeebd3e5c8efead9b250742a148e", - "sourceSha256": "25905b3b294b0be2e245099acb1a557793215f157b96b9e964e78170838652f0", - "status": "migrated", - "target": "plugins/dev/skills/component", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:c49d1db42acd7220": { - "sourcePathHash": "c49d1db42acd722071d61420be58ba8ccbc54f5370bdf951abfb5981914271cf", - "sourceSha256": "20875df87fb84c7d5830d6eaaff544c9e32e3f9a2359ed099de5f3a4a36eaa7d", - "status": "superseded", - "target": "plugins/dev/skills/design-frontend", - "targetVersion": "0.1.0", - "reason": "前端设计能力已与另一来源合并并移除内部组件与固定版本约束", - "reviewedAt": "2026-08-25" - }, - "source-b:9a3f3d3c43d9017a": { - "sourcePathHash": "9a3f3d3c43d9017a080a9832df5b975e6ddd3f8c39947ee768aeb973fc23974e", - "sourceSha256": "47c072e792d1cf80ddcc69536236ccb525d21e7d5408ed41cca55483556e8acb", - "status": "superseded", - "target": "plugins/dev/skills", - "reason": "前端导航能力已由 component、style、form 的精确触发描述和 Codex 自动发现替代", - "reviewedAt": "2026-08-25" - }, - "source-b:eb730c56bc7a4a15": { - "sourcePathHash": "eb730c56bc7a4a158ddec74c7497a10fdea2d0b628783b968e3ee9ee4572b15c", - "sourceSha256": "89aa46526fa7531958df46be4b162a9f7b9ea597e3e9121991cf25e9f25158f5", - "status": "migrated", - "target": "plugins/skill/skills/guidance", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:422174b0c00bbd65": { - "sourcePathHash": "422174b0c00bbd657f4c08415d4a722584556cfa5f3edc997b11e8ab0ef8656a", - "sourceSha256": "017a4ca0b7648ed91003deeb97a3757e8f17acd6963c6019140389a907f1345a", - "status": "migrated", - "target": "plugins/dev/skills/style", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:742ba3611bee4e34": { - "sourcePathHash": "742ba3611bee4e34dfee6dadbe192dd8be677c4b9f49108e2f04d9e22dc8801f", - "sourceSha256": "7582e333966b45ee2d145601831449d75c4f3159670c31df25e5d18e6a357bbe", - "status": "migrated", - "target": "plugins/dev/skills/test-ui", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:d6d4166800584a61": { - "sourcePathHash": "d6d4166800584a61ad6cbf96af57bb8ee269a16babba753f267b48efd66f6d2d", - "sourceSha256": "60f8708db24f368ecec55bd3f326683fac60bd6776ac69e0fafaf4c77333e703", - "status": "migrated", - "target": "plugins/doc/skills/docx-to-md", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:023767d9c7be6295": { - "sourcePathHash": "023767d9c7be62956d12fd01c94ac676b9fe6719741acd6f412c8b446efa2760", - "sourceSha256": "ba579f8d6a62b4745d8d3922b33e3c20974d8081ae4d1c87727d1a9e4b609945", - "status": "migrated", - "target": "plugins/doc/skills/format-md", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:b127c47139270a7f": { - "sourcePathHash": "b127c47139270a7f9157e18ad6a0c975b99caf07b9a0874daeac86d9a99ce48c", - "sourceSha256": "0214b80071f07beaff4f5837b983546ff71dbaa65bc29e34a5a23c9d946b683b", - "status": "migrated", - "target": "plugins/doc/skills/md-to-docx", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:34ab291ce3cfed2e": { - "sourcePathHash": "34ab291ce3cfed2e69cc6f08309b1a41142be91235b9a573daf98639b4a001cc", - "sourceSha256": "239d7efb1f9a53fc1b40f367954a6f6eaaf9d7a56cdf712128dcf393dc5a5067", - "status": "migrated", - "target": "plugins/dev/skills/design-api", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:3c8596a5d95de214": { - "sourcePathHash": "3c8596a5d95de214d381b2f04898dd7042a266aa7889569981a97a1cf50045d1", - "sourceSha256": "cc19258fcf63018ce2dac4d7253e23fbfb71596ee27235871ad78aae743b57e6", - "status": "migrated", - "target": "plugins/dev/skills/design-db", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:a388287911bc39cb": { - "sourcePathHash": "a388287911bc39cb3f88824044426782df3c3e6450c4db9e4948c28a47dbf9f1", - "sourceSha256": "20e13c3458f6af8d0b131f650f573eb824978438b52475a2f480a67c2f846cd6", - "status": "superseded", - "target": "plugins/skill/skills/guidance", - "targetVersion": "0.1.0", - "reason": "框架约定改由通用规范检索结合项目真实依赖解析", - "reviewedAt": "2026-08-26" - }, - "source-b:ca08ee8839bccc15": { - "sourcePathHash": "ca08ee8839bccc1504c253cdb9bfc09803ddf77f948605f8563d04e0466a2ce7", - "sourceSha256": "0086c8da048d94303e9d5765b91b7eff9a10a798ffa73de9f245be5422cf3255", - "status": "superseded", - "target": "plugins/dev/skills/component", - "targetVersion": "0.1.0", - "reason": "组件约定与契约查证能力已合并到 component", - "reviewedAt": "2026-08-25" - }, - "source-b:1b4cee54060ef399": { - "sourcePathHash": "1b4cee54060ef3995d6c98e99dee3c3200ab2e90dd62136cef07b9b09e31364d", - "sourceSha256": "24df169fbb18f39be5ec4046cc8d33535b1b86d2114673ee35d1a2a0324c1b25", - "status": "migrated", - "target": "plugins/dev/skills/review-frontend", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:7b7552c8c6e1b5f5": { - "sourcePathHash": "7b7552c8c6e1b5f56a283db304272da8cd63210aaf7327231a7b26f3cf903082", - "sourceSha256": "9c373fe6bb955d4431e92bba66239cb707a6179082a624b74bc77fa36663fc61", - "status": "migrated", - "target": "plugins/dev/skills/design-frontend-data", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:5f525e6aeda55dd8": { - "sourcePathHash": "5f525e6aeda55dd81c26d38cf59097cb52abbcfc5b05e35fe7e8101a1a3353b5", - "sourceSha256": "8068a6137951e94a01bc68cf9011db69f3d759304a3167291afa4c238531e34a", - "status": "migrated", - "target": "plugins/dev/skills/form", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:f2079362cf82e719": { - "sourcePathHash": "f2079362cf82e719e2dde6098c55ce984709a97446a7f66113f8b5f317f1f55b", - "sourceSha256": "f57f3c0d5175c3c1700b74e8489d820eed4ef36d10e79ab14e096cb2a20a0cec", - "status": "migrated", - "target": "plugins/dev/skills/review-java", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:b78122ad4813e9ba": { - "sourcePathHash": "b78122ad4813e9baef0ee5281bddb910b01fd89bdbbd2753011ac3deb52a8ce5", - "sourceSha256": "c06d7eb2674dcbb4bad01a8c65c6d372dffd95c585a16ab2c3f1bba19df199e0", - "status": "superseded", - "target": "plugins/skill/skills/guidance", - "targetVersion": "0.1.0", - "reason": "框架知识查询改由通用规范检索与项目源码证据完成", - "reviewedAt": "2026-08-26" - }, - "source-b:7fd143975b0071c3": { - "sourcePathHash": "7fd143975b0071c3efa271a9ad19c3d7e422f64d3e23aa08d7a091c805cb9467", - "sourceSha256": "33582dcda068247a5c56238134dad308d1f66b03abbfdc7bc282a9ac9ff918a2", - "status": "migrated", - "target": "plugins/dev/skills/review-mybatis", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:affd0805467646c1": { - "sourcePathHash": "affd0805467646c1fcb4c196776e9cb610fcec721e1a44df3bc02f5c08b84aeb", - "sourceSha256": "c7f565da3a1f412d3f15642fc9ad44733adee73c4d13cdebffaff1de4ca5652c", - "status": "migrated", - "target": "plugins/dev/skills/design-workflow", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:2a64acb63e8b2030": { - "sourcePathHash": "2a64acb63e8b203031c4ef292f957c0fe0d3722e079dc9a299730015b9e3359c", - "sourceSha256": "580da97b2fb16543db9aef62359f153fe5aca382d88987f10f233289bc02790d", - "status": "migrated", - "target": "plugins/git/skills/commit-msg", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:3a5d583c511959b3": { - "sourcePathHash": "3a5d583c511959b3d1934d15eaa9b0cd0590c558535b6a8307f870d7ce0c8dd0", - "sourceSha256": "6b69964b6e65ddc6eefa857fa1016587d3b2d2fac22d2bda4196762233fb025b", - "status": "migrated", - "target": "plugins/git/skills/branch", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:13bb77c66fee0964": { - "sourcePathHash": "13bb77c66fee09641fe61781ffbba4c51ed47b14ce225972ea35219e489405a3", - "sourceSha256": "abe529dcc85b1ac88791c95cf7be12cf5b1f545988b515f10edb55f0a8c94fb1", - "status": "migrated", - "target": "plugins/git/skills/export", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:fd03e996181699d9": { - "sourcePathHash": "fd03e996181699d95a09c1940f685ac366daeeda1aa901175d26dcf153f755fb", - "sourceSha256": "b4ef1f7847dd9081fdacaecae8853b2946e22c899b08bbe48ef280d21546e044", - "status": "migrated", - "target": "plugins/git/skills/identity", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:4ca0940d73b4b626": { - "sourcePathHash": "4ca0940d73b4b6264a37e1a344d669367dc313364a0077abf8bcaf97cfed42a1", - "sourceSha256": "92b3cea563f544806493245bdf099e2357112ccbce15007593c94d62c26c258c", - "status": "migrated", - "target": "plugins/git/skills/integrate", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:0b917cc63232dff3": { - "sourcePathHash": "0b917cc63232dff38c375d3bec3fabe57390201c34c3ce96f7885eb23bca91d7", - "sourceSha256": "5ecfa8040f5d71f347cfcfb89040a5cbfdb96f71867fb11b750b78d4aa17d66a", - "status": "migrated", - "target": "plugins/knowledge/skills/init", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:905e74ea51281da1": { - "sourcePathHash": "905e74ea51281da179c1eb31677af71569a95139a74fb139d39dadd52db2ce62", - "sourceSha256": "cfc1eb3fafb15dc51b730dd36d3ad507b5ae415d5f1a5da5138471170afdde38", - "status": "migrated", - "target": "plugins/knowledge/skills/trace", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:274c4aac900a23c3": { - "sourcePathHash": "274c4aac900a23c3ca0506807c4df8e09c95e21b84fd633951bae146f6c41028", - "sourceSha256": "b4beacdc745cd06ee4be20694d60bae0c9c6122fb61b917b12b66451497651e9", - "status": "migrated", - "target": "plugins/knowledge/skills/lessons", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:6e1a4d182b2e41fd": { - "sourcePathHash": "6e1a4d182b2e41fddd28051c20d7170821a87b518e49684cc4f12b731789831d", - "sourceSha256": "cf106df97100aabf260db7c8d45c2d6d499c643b4898d7e46c98852a90b7134d", - "status": "superseded", - "target": "plugins/knowledge/skills/lessons", - "targetVersion": "0.1.0", - "reason": "经验提升能力已合并到 lessons 的 promote 模式", - "reviewedAt": "2026-08-25" - }, - "source-b:cd4c15d66d5cfba0": { - "sourcePathHash": "cd4c15d66d5cfba0c568b6f3ac4ceac9a7cea797eca8c6bf5eab81187e70f7d7", - "sourceSha256": "a9b109dc659e8e65645b6507ab2a39a04598d0c0ed63feaef0985765d3ff9f3f", - "status": "superseded", - "target": "plugins/knowledge/skills/lessons", - "targetVersion": "0.1.0", - "reason": "经验回扫能力已合并到 lessons 的 audit 模式", - "reviewedAt": "2026-08-25" - }, - "source-b:a6062c4036828615": { - "sourcePathHash": "a6062c4036828615945db311cf68c2ac886feef91c79b53d882bbe41fd71143f", - "sourceSha256": "94b7e58d4c4903498cf29365fe466aaba8bfdf4675754f65cd30bad37c99e02f", - "status": "migrated", - "target": "plugins/knowledge/skills/distill", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:f58c6cd52bc983e8": { - "sourcePathHash": "f58c6cd52bc983e87d265308ade0da8d89bd74d5d4f1e96fc463ef299a9fe94a", - "sourceSha256": "98392eb0fe65dab6877495e6863a6722cd2777f5767dfd95b3a829ed7798a5e7", - "status": "migrated", - "target": "plugins/knowledge/skills/handoff", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:258ee10299925494": { - "sourcePathHash": "258ee1029992549420c8465c8de2d63c1bfaf9334b812d6b79a07a9823b49733", - "sourceSha256": "b4958d4aee3c06b778273d87a07dd423cf15fc8fbfdde27e00552ff60138795e", - "status": "migrated", - "target": "plugins/dev/skills/analyze-bugs", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:52592d4a59314c74": { - "sourcePathHash": "52592d4a59314c74de9b58cfb4cda6e9f5d9514bb05c9b586044c12754982d52", - "sourceSha256": "e8be1b2c17ab3aaccbb5559a07b64da8a7c718293e83cb8dd685c87708c7cbdc", - "status": "migrated", - "target": "plugins/dev/skills/upgrade", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:f478193705d1d02d": { - "sourcePathHash": "f478193705d1d02d6bb6375728dc10d34487e3f67ab8d164a80104f6a71fccdc", - "sourceSha256": "218eb591d1944b2280853bbf39cb028f9a41e55dee01218838b14f87755fad72", - "status": "superseded", - "target": "official:browser-computer-use", - "reason": "Codex 浏览器与计算机操作能力已覆盖宿主浏览器代理配置", - "reviewedAt": "2026-08-26" - }, - "source-b:e4b14f4b2719e28b": { - "sourcePathHash": "e4b14f4b2719e28b409bc66980b3698588684dde81a0c627e862262e76c34b2f", - "sourceSha256": "de185022a0cf8bfce340709f47052578ec761664c96723172c48bdd4f10e71f1", - "status": "superseded", - "target": "plugins/dev/skills/review-code", - "targetVersion": "0.1.0", - "reason": "分支、提交与目录审查已合并到统一代码审查入口", - "reviewedAt": "2026-08-26" - }, - "source-b:aa42f7c1a045bb49": { - "sourcePathHash": "aa42f7c1a045bb496bbc9e62beb7a7b74544153ed9551492f5b801ca4c9df098", - "sourceSha256": "aff6fad01afa2a15756a36d210c67b229a1eb2155981292450f0487c3005125d", - "status": "migrated", - "target": "plugins/knowledge/skills/worklog", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:3763e17cff825df6": { - "sourcePathHash": "3763e17cff825df6920f57d84736093082014f2a0316d9e84ee18ac8078ff6e5", - "sourceSha256": "bd2e4ae7f8a9bded1f7a983289fb49556f30cd5d6c6382500dd8c464fdd66e02", - "status": "migrated", - "target": "plugins/doc/skills/archive", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:9e3e75840a46af96": { - "sourcePathHash": "9e3e75840a46af96c33a1885f192813239b511eb972a4962b30b76de7392c88c", - "sourceSha256": "379c41e41d833880653ce065a63380e9cf29d2b7663f7ee91a3d77594202d042", - "status": "superseded", - "target": "plugins/dev/skills/upgrade", - "targetVersion": "0.1.0", - "reason": "前后端升级合并为按真实版本与官方迁移资料执行的统一能力", - "reviewedAt": "2026-08-26" - }, - "source-b:766516f82e8e9be3": { - "sourcePathHash": "766516f82e8e9be32e1ff3f29123202b6bad85bf9a88e36f87f256446ea31eaf", - "sourceSha256": "c66ca1651923b20cd62395ba2cc6b8532093d5df10f420c1673a103a88dbf5aa", - "status": "migrated", - "target": "plugins/doc/skills/message", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:31dec2fa162b06d3": { - "sourcePathHash": "31dec2fa162b06d379aa676d426d2de01c3b082061ff15adb7a8c721cd5635af", - "sourceSha256": "a146d79ccfec7b1f9275cda8e6cf3ab1190430d0c3bc11886a722ce1d6fabc90", - "status": "migrated", - "target": "plugins/skill/skills/guidance-edit", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-25" - }, - "source-b:988b200efd0b8be0": { - "sourcePathHash": "988b200efd0b8be024060e0785da2f864bf7ee30d7c7b434fde7ab1e4cdcc9d8", - "sourceSha256": "e7fa073d592167c07b06698d664192baafb126afa1a6bfdc50a056e1ea13e56f", - "status": "excluded", - "reason": "CraftKit 原生面向 Codex,不需要 Claude Code 到 Codex 的平台转换能力", - "reviewedAt": "2026-08-25" - }, - "source-b:8a7cdf719d8f8a3e": { - "sourcePathHash": "8a7cdf719d8f8a3e0200781a82062cc5a839e4d2c2fd400982771968245d62dd", - "sourceSha256": "3461485ba35dceffc5fc0e9266793003a0355d412aa8a609c93709745226d00a", - "status": "migrated", - "target": "plugins/dev/skills/estimate", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:6e5eb2b9ab9273cf": { - "sourcePathHash": "6e5eb2b9ab9273cf889bd908ff446eda59cc3e121ee7a1861fb2664d7fa049e9", - "sourceSha256": "31eef261e6e3e930cd88b18c938176c3ef1d3e8d039986712ee6c3191481872d", - "status": "migrated", - "target": "plugins/doc/skills/report", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - }, - "source-b:a985b2c0fb927117": { - "sourcePathHash": "a985b2c0fb9271174d9d46425eb6792d2483ebcd6da89ecc1cbfb61407259499", - "sourceSha256": "e2f92df8dc489e79bfdd74de8817abf742f425422765c76c4a46263d1fbd7a9e", - "status": "migrated", - "target": "plugins/doc/skills/requirements", - "targetVersion": "0.1.0", - "reviewedAt": "2026-08-26" - } - } -} diff --git a/migration/tests/test_dev_design_batch.py b/migration/tests/test_dev_design_batch.py deleted file mode 100644 index 10cfad0..0000000 --- a/migration/tests/test_dev_design_batch.py +++ /dev/null @@ -1,58 +0,0 @@ -"""开发分析与设计批次的职责边界测试。""" - -from pathlib import Path -import unittest - - -ROOT = Path(__file__).resolve().parents[2] -SKILLS = ROOT / "plugins" / "dev" / "skills" - - -def read_skill(name: str) -> str: - """合并读取 Skill 入口及引用文件。""" - folder = SKILLS / name - files = [folder / "SKILL.md", *sorted((folder / "references").glob("*.md"))] - return "\n".join(path.read_text(encoding="utf-8") for path in files) - - -class DevDesignBatchTest(unittest.TestCase): - """验证四个 Skill 的通用化设计和相互边界。""" - - def test_plan_is_evidence_based_and_does_not_implement(self) -> None: - content = read_skill("plan-change") - self.assertIn("不执行设计或编码", content) - self.assertIn("验收标准", content) - self.assertIn("运行行为标为待验证", content) - self.assertNotIn("docs/{moduleCode}", content) - - def test_backend_design_uses_project_version(self) -> None: - content = read_skill("design-backend") - self.assertIn("精确版本", content) - self.assertIn("guidance", content) - self.assertIn("事务边界", content) - self.assertIn("不生成业务代码", content) - - def test_frontend_design_composes_existing_helpers(self) -> None: - content = read_skill("design-frontend") - for helper in ("component", "style", "form", "prepare-api"): - self.assertIn(helper, content) - self.assertIn("不编造组件", content) - self.assertIn("不跨版本混用", content) - - def test_prepare_api_does_not_assume_contract(self) -> None: - content = read_skill("prepare-api") - self.assertIn("不预设请求客户端", content) - self.assertIn("未匹配字段", content) - self.assertIn("不发起真实请求", content) - self.assertIn("不能反向覆盖", content) - - def test_descriptions_are_distinct(self) -> None: - descriptions = [] - for name in ("plan-change", "design-backend", "design-frontend", "prepare-api"): - text = (SKILLS / name / "SKILL.md").read_text(encoding="utf-8") - descriptions.append(text.split("---", 2)[1]) - self.assertEqual(4, len(set(descriptions))) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_doc_batch.py b/migration/tests/test_doc_batch.py deleted file mode 100644 index cc41e7c..0000000 --- a/migration/tests/test_doc_batch.py +++ /dev/null @@ -1,78 +0,0 @@ -"""验证文档批次三个独立实现的核心行为和安全边界。""" - -from __future__ import annotations - -import json -import subprocess -import tempfile -import unittest -from pathlib import Path - -from docx import Document -from openpyxl import Workbook - - -ROOT = Path(__file__).resolve().parents[2] -MD_SCRIPT = ROOT / "plugins/doc/skills/md-to-docx/scripts/convert.py" -XLSX_SCRIPT = ROOT / "plugins/doc/skills/xlsx-to-md/scripts/convert.py" -ARCHIVE_SCRIPT = ROOT / "plugins/doc/skills/archive/scripts/archive.py" - - -class DocumentBatchTests(unittest.TestCase): - """使用临时目录执行真实转换,不依赖仓库外测试材料。""" - - def run_script(self, script: Path, *args: object) -> subprocess.CompletedProcess[str]: - """使用当前受控 Python 运行时执行目标脚本。""" - - return subprocess.run( - [str(Path(__import__("sys").executable)), str(script), *(str(value) for value in args)], - capture_output=True, text=True, encoding="utf-8", check=False, - ) - - def test_markdown_to_docx_preserves_common_blocks(self) -> None: - """标题、列表、表格和代码块应生成可重新打开的 DOCX。""" - - with tempfile.TemporaryDirectory() as temp: - folder = Path(temp); source = folder / "sample.md"; output = folder / "sample.docx" - source.write_text("# 标题\n\n- 项目\n\n| 名称 | 值 |\n| --- | --- |\n| A | 1 |\n\n```py\nprint('ok')\n```\n", encoding="utf-8") - result = self.run_script(MD_SCRIPT, source) - self.assertEqual(0, result.returncode, result.stderr) - document = Document(output) - self.assertEqual("标题", document.paragraphs[0].text) - self.assertEqual(1, len(document.tables)) - self.assertNotEqual(0, self.run_script(MD_SCRIPT, source).returncode) - - def test_xlsx_to_markdown_handles_merges_and_escaping(self) -> None: - """合并区域应填充值,表格特殊字符应安全转义。""" - - with tempfile.TemporaryDirectory() as temp: - folder = Path(temp); source = folder / "sample.xlsx" - workbook = Workbook(); sheet = workbook.active; sheet.title = "数据" - sheet.append(["名称", "值"]); sheet.append(["A|B", "=1+1"]) - sheet.merge_cells("A3:B3"); sheet["A3"] = "合并" - workbook.create_sheet("空表"); workbook.save(source) - result = self.run_script(XLSX_SCRIPT, source) - self.assertEqual(0, result.returncode, result.stderr) - markdown = source.with_suffix(".md").read_text(encoding="utf-8") - self.assertIn(r"A\|B", markdown) - self.assertIn("| 合并 | 合并 |", markdown) - self.assertIn("_空工作表_", markdown) - - def test_archive_is_dry_run_first_and_keeps_source(self) -> None: - """预演不得写目标,执行后仍须保留来源并只抽取指定章节。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); source = root / "specs/api.md"; source.parent.mkdir() - source.write_text("---\nowner: team\n---\n# 概述\n正文\n## API\n接口\n## 其他\n忽略\n", encoding="utf-8") - config = root / "rules.json" - config.write_text(json.dumps({"version": 1, "archiveRoot": "archive", "rules": [{"match": "specs/*.md", "target": "{stem}.md", "section": "API", "stripFrontmatter": True, "requiredText": ["接口"]}]}, ensure_ascii=False), encoding="utf-8") - target = root / "archive/api.md" - preview = self.run_script(ARCHIVE_SCRIPT, "--root", root, "--config", config) - self.assertEqual(0, preview.returncode, preview.stderr); self.assertFalse(target.exists()) - applied = self.run_script(ARCHIVE_SCRIPT, "--root", root, "--config", config, "--apply") - self.assertEqual(0, applied.returncode, applied.stderr) - self.assertTrue(source.exists()); self.assertEqual("## API\n接口\n", target.read_text(encoding="utf-8")) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_docx_to_md.py b/migration/tests/test_docx_to_md.py deleted file mode 100644 index a11cea9..0000000 --- a/migration/tests/test_docx_to_md.py +++ /dev/null @@ -1,117 +0,0 @@ -import importlib.util -import sys -import tempfile -import unittest -import zipfile -from pathlib import Path - - -SCRIPT = ( - Path(__file__).parents[2] - / "plugins" - / "doc" - / "skills" - / "docx-to-md" - / "scripts" - / "convert.py" -) -SPEC = importlib.util.spec_from_file_location("docx_to_md_convert", SCRIPT) -MODULE = importlib.util.module_from_spec(SPEC) -assert SPEC.loader is not None -sys.modules[SPEC.name] = MODULE -SPEC.loader.exec_module(MODULE) - - -class DocxToMarkdownTest(unittest.TestCase): - """验证转换器的核心内容、覆盖保护和输入边界。""" - - def make_docx(self, path: Path) -> None: - """创建只包含公开 OOXML 结构的最小测试文档。""" - - document = """ - - - 测试标题 - 加粗正文 - 示例链接 - - 列表项目 - - - 名称值 - A1 - - -""" - styles = """ - - -""" - numbering = """ - - - -""" - relationships = """ - - - -""" - with zipfile.ZipFile(path, "w") as archive: - archive.writestr("word/document.xml", document) - archive.writestr("word/styles.xml", styles) - archive.writestr("word/numbering.xml", numbering) - archive.writestr("word/_rels/document.xml.rels", relationships) - archive.writestr("word/media/test.png", b"\x89PNG\r\n\x1a\n") - archive.writestr("word/comments.xml", "") - - def test_convert_common_content_and_report_warning(self) -> None: - """常见结构应转换,无法处理的批注应明确告警。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp) - source = root / "sample.docx" - output = root / "result" - self.make_docx(source) - - result = MODULE.DocxConverter(source, output).convert() - markdown = result.output_file.read_text(encoding="utf-8") - - self.assertIn("# 测试标题", markdown) - self.assertIn("**加粗正文**", markdown) - self.assertIn("[示例链接](https://example.com)", markdown) - self.assertIn("- 列表项目", markdown) - self.assertIn("| 名称 | 值 |", markdown) - self.assertIn("![图片](images/image-", markdown) - self.assertEqual(result.images, 1) - self.assertTrue(any("批注" in warning for warning in result.warnings)) - - def test_existing_output_requires_force(self) -> None: - """默认不得写入已有输出目录,显式覆盖后才可继续。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp) - source = root / "sample.docx" - output = root / "result" - self.make_docx(source) - output.mkdir() - - with self.assertRaises(FileExistsError): - MODULE.DocxConverter(source, output).convert() - - result = MODULE.DocxConverter(source, output, force=True).convert() - self.assertTrue(result.output_file.exists()) - - def test_cli_rejects_non_docx(self) -> None: - """命令行入口应拒绝扩展名不正确的文件。""" - - with tempfile.TemporaryDirectory() as temp: - source = Path(temp) / "sample.txt" - source.write_text("not docx", encoding="utf-8") - self.assertEqual(MODULE.main([str(source)]), 2) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_frontend_helpers.py b/migration/tests/test_frontend_helpers.py deleted file mode 100644 index 7caa9c8..0000000 --- a/migration/tests/test_frontend_helpers.py +++ /dev/null @@ -1,51 +0,0 @@ -"""前端辅助 Skill 的结构与行为边界测试。""" - -from pathlib import Path -import unittest - - -ROOT = Path(__file__).resolve().parents[2] -DEV_SKILLS = ROOT / "plugins" / "dev" / "skills" - - -def read_skill(name: str) -> str: - """读取指定 Skill 的入口及引用文档。""" - folder = DEV_SKILLS / name - files = [folder / "SKILL.md", *sorted((folder / "references").glob("*.md"))] - return "\n".join(path.read_text(encoding="utf-8") for path in files) - - -class FrontendHelpersTest(unittest.TestCase): - """验证三个 Skill 能互补协作且不恢复来源专有规则。""" - - def test_component_requires_project_evidence(self) -> None: - content = read_skill("component") - self.assertIn(".craftkit/project.json", content) - self.assertIn(".craftkit/standards/frontend/components.md", content) - self.assertIn("不编造结论", content) - self.assertIn("官方文档", content) - self.assertIn("不复制依赖库文档", content) - - def test_style_uses_existing_visual_authority(self) -> None: - content = read_skill("style") - self.assertIn("设计令牌", content) - self.assertIn("经用户确认后再修改", content) - self.assertIn("不内置固定矩阵", content) - - def test_form_does_not_invent_business_rules(self) -> None: - content = read_skill("form") - self.assertIn("不自行确定字段必填", content) - self.assertIn("不设计接口", content) - self.assertIn("component", content) - - def test_precise_skills_replace_router(self) -> None: - self.assertFalse((DEV_SKILLS / "guide").exists()) - descriptions = { - name: (DEV_SKILLS / name / "SKILL.md").read_text(encoding="utf-8").split("---", 2)[1] - for name in ("component", "style", "form") - } - self.assertEqual(3, len(set(descriptions.values()))) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_git_export.py b/migration/tests/test_git_export.py deleted file mode 100644 index 7e63e8e..0000000 --- a/migration/tests/test_git_export.py +++ /dev/null @@ -1,102 +0,0 @@ -"""验证 Git 变更导出的选择、配置保护和写入边界。""" - -from __future__ import annotations - -import json -import subprocess -import sys -import tempfile -import unittest -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -SCRIPT = ROOT / "plugins/git/skills/export/scripts/export.py" - - -class GitExportTests(unittest.TestCase): - """在临时 Git 仓库中验证真实命令行为。""" - - def git(self, repo: Path, *args: str) -> str: - """执行测试仓库内的 Git 命令。""" - - result = subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True, encoding="utf-8", check=True) - return result.stdout.strip() - - def run_export(self, repo: Path, target: Path, *args: str) -> subprocess.CompletedProcess[str]: - """使用当前受控 Python 运行导出脚本。""" - - return subprocess.run([sys.executable, str(SCRIPT), "--repo", str(repo), "--target", str(target), *args], capture_output=True, text=True, encoding="utf-8", check=False) - - def make_repo(self, folder: Path) -> Path: - """创建具有稳定身份和首个提交的隔离仓库。""" - - repo = folder / "repo"; repo.mkdir() - self.git(repo, "init", "-q"); self.git(repo, "config", "user.name", "Test User"); self.git(repo, "config", "user.email", "test@example.invalid") - (repo / "src").mkdir(); (repo / "src/app.txt").write_text("v1\n", encoding="utf-8") - self.git(repo, "add", "src/app.txt"); self.git(repo, "commit", "-q", "-m", "init") - return repo - - def test_worktree_preview_classifies_shared_and_protected_files(self) -> None: - """共享 CraftKit 文档可导出,环境配置默认受保护,私钥永久阻断。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); repo = self.make_repo(root); target = root / "out" - (repo / "src/app.txt").write_text("v2\n", encoding="utf-8") - (repo / "application-dev.yml").write_text("server: local\n", encoding="utf-8") - (repo / "private.pem").write_text("-----BEGIN PRIVATE KEY-----\nabc\n", encoding="utf-8") - standard = repo / ".craftkit/standards/export.md"; standard.parent.mkdir(parents=True); standard.write_text("规范\n", encoding="utf-8") - result = self.run_export(repo, target, "--mode", "worktree", "--snapshot", "worktree", "--include-untracked") - self.assertEqual(2, result.returncode) - manifest = json.loads(result.stdout) - self.assertIn("src/app.txt", manifest["exported"]) - self.assertIn(".craftkit/standards/export.md", manifest["exported"]) - self.assertIn("application-dev.yml", manifest["protected"]) - self.assertIn("private.pem", manifest["blocked"]) - self.assertFalse(target.exists()) - - def test_protected_file_requires_matching_project_evidence(self) -> None: - """环境配置只有在项目规范明确点名且执行确认后才写入。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); repo = self.make_repo(root); target = root / "out" - (repo / "application-dev.yml").write_text("server: local\n", encoding="utf-8") - evidence = repo / ".craftkit/standards/delivery.md"; evidence.parent.mkdir(parents=True) - evidence.write_text("交付必须包含 application-dev.yml。\n", encoding="utf-8") - result = self.run_export(repo, target, "--mode", "worktree", "--snapshot", "worktree", "--include-untracked", - "--allow-protected", "--policy-evidence", ".craftkit/standards/delivery.md", "--apply") - self.assertEqual(0, result.returncode, result.stderr) - self.assertTrue((target / "application-dev.yml").is_file()) - manifest = json.loads((target / "export-manifest.json").read_text(encoding="utf-8")) - self.assertEqual(".craftkit/standards/delivery.md", manifest["policyEvidence"]) - - def test_range_uses_explicit_snapshot_and_records_deleted_file(self) -> None: - """范围负责选路径,快照负责取内容;快照中删除的文件只进入清单。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); repo = self.make_repo(root); target = root / "out" - base = self.git(repo, "rev-parse", "HEAD") - (repo / "src/app.txt").unlink(); (repo / "src/new.txt").write_text("new\n", encoding="utf-8") - self.git(repo, "add", "-A"); self.git(repo, "commit", "-q", "-m", "change") - head = self.git(repo, "rev-parse", "HEAD") - result = self.run_export(repo, target, "--mode", "range", "--base", base, "--head", head, "--snapshot", head) - self.assertEqual(0, result.returncode, result.stderr) - manifest = json.loads(result.stdout) - self.assertIn("src/app.txt", manifest["deleted"]) - self.assertIn("src/new.txt", manifest["exported"]) - - def test_time_mode_selects_committed_paths_without_writing(self) -> None: - """时间模式应选择提交中出现的路径,并保持预演只读。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); repo = self.make_repo(root); target = root / "out" - (repo / "src/app.txt").write_text("v2\n", encoding="utf-8") - self.git(repo, "add", "src/app.txt"); self.git(repo, "commit", "-q", "-m", "update") - result = self.run_export(repo, target, "--mode", "time", "--since", "2000-01-01", "--snapshot", "HEAD") - self.assertEqual(0, result.returncode, result.stderr) - self.assertIn("src/app.txt", json.loads(result.stdout)["exported"]) - self.assertFalse(target.exists()) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_git_integrate.py b/migration/tests/test_git_integrate.py deleted file mode 100644 index 921fc58..0000000 --- a/migration/tests/test_git_integrate.py +++ /dev/null @@ -1,79 +0,0 @@ -"""验证预集成 Skill 的文档契约和隔离 Git 行为。""" - -from __future__ import annotations - -import subprocess -import tempfile -import unittest -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -SKILL = ROOT / "plugins/git/skills/integrate/SKILL.md" - - -class GitIntegrateTests(unittest.TestCase): - """在临时仓库中验证 worktree 集成不会移动输入引用。""" - - def git(self, repo: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]: - """执行测试仓库中的 Git 命令并保留失败状态供冲突断言。""" - - return subprocess.run(["git", "-C", str(repo), *args], capture_output=True, text=True, encoding="utf-8", check=check) - - def make_repo(self, root: Path, conflict: bool = False) -> tuple[Path, str, str]: - """创建具有源、目标分叉的隔离仓库。""" - - repo = root / "repo"; repo.mkdir(); self.git(repo, "init", "-q") - self.git(repo, "config", "user.name", "Test User"); self.git(repo, "config", "user.email", "test@example.invalid") - (repo / "base.txt").write_text("base\n", encoding="utf-8") - self.git(repo, "add", "base.txt"); self.git(repo, "commit", "-q", "-m", "base") - self.git(repo, "branch", "target"); self.git(repo, "switch", "-q", "-c", "source") - source_file = repo / ("base.txt" if conflict else "source.txt") - source_file.write_text("source\n", encoding="utf-8") - self.git(repo, "add", "-A"); self.git(repo, "commit", "-q", "-m", "source") - source_hash = self.git(repo, "rev-parse", "source").stdout.strip() - self.git(repo, "switch", "-q", "target") - target_file = repo / ("base.txt" if conflict else "target.txt") - target_file.write_text("target\n", encoding="utf-8") - self.git(repo, "add", "-A"); self.git(repo, "commit", "-q", "-m", "target") - target_hash = self.git(repo, "rev-parse", "target").stdout.strip() - return repo, source_hash, target_hash - - def test_isolated_merge_preserves_source_and_target_refs(self) -> None: - """预集成分支发生 merge 后,源和目标引用必须保持原哈希。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); repo, source_hash, target_hash = self.make_repo(root) - worktree = root / "integration" - self.git(repo, "worktree", "add", "-q", "-b", "pre/integration", str(worktree), "target") - self.git(worktree, "merge", "--no-edit", "source") - self.assertEqual(source_hash, self.git(repo, "rev-parse", "source").stdout.strip()) - self.assertEqual(target_hash, self.git(repo, "rev-parse", "target").stdout.strip()) - self.assertTrue((worktree / "source.txt").is_file()) - self.assertTrue((worktree / "target.txt").is_file()) - - def test_conflict_stops_before_automatic_resolution(self) -> None: - """真实冲突应停留在未合并状态,不移动源或目标引用。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp); repo, source_hash, target_hash = self.make_repo(root, conflict=True) - worktree = root / "integration" - self.git(repo, "worktree", "add", "-q", "-b", "pre/conflict", str(worktree), "target") - result = self.git(worktree, "merge", "--no-edit", "source", check=False) - self.assertNotEqual(0, result.returncode) - self.assertIn("UU base.txt", self.git(worktree, "status", "--short").stdout) - self.assertEqual(source_hash, self.git(repo, "rev-parse", "source").stdout.strip()) - self.assertEqual(target_hash, self.git(repo, "rev-parse", "target").stdout.strip()) - - def test_skill_separates_stages_and_protects_delivery(self) -> None: - """Skill 必须保留阶段授权、环境配置阻断和强推禁用边界。""" - - text = SKILL.read_text(encoding="utf-8") - for value in ("assess", "prepare", "publish", "独立 worktree", "环境配置", "私钥"): - self.assertIn(value, text) - publish = (SKILL.parent / "references/publish.md").read_text(encoding="utf-8") - self.assertIn("默认不改用 `--force`", publish) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_guidance_edit.py b/migration/tests/test_guidance_edit.py deleted file mode 100644 index 0ca62aa..0000000 --- a/migration/tests/test_guidance_edit.py +++ /dev/null @@ -1,83 +0,0 @@ -"""规范索引维护 Skill 的确定性行为测试。""" - -import json -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest - - -ROOT = Path(__file__).resolve().parents[2] -SKILL = ROOT / "plugins" / "skill" / "skills" / "guidance-edit" -SCRIPT = SKILL / "scripts" / "check_index.py" - - -class GuidanceEditTest(unittest.TestCase): - """验证索引检查器和读写职责边界。""" - - def run_check(self, standards: Path) -> subprocess.CompletedProcess[str]: - """以 JSON 模式执行检查器。""" - return subprocess.run( - [sys.executable, str(SCRIPT), str(standards), "--json"], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - ) - - def test_clean_index_is_reachable(self) -> None: - """根索引能够逐级访问所有规范时应检查通过。""" - with tempfile.TemporaryDirectory() as temp: - standards = Path(temp) - (standards / "frontend").mkdir() - (standards / "index.md").write_text( - "[前端](frontend/index.md)\n", encoding="utf-8" - ) - (standards / "frontend" / "index.md").write_text( - "[组件](components.md)\n", encoding="utf-8" - ) - (standards / "frontend" / "components.md").write_text( - "# 组件\n", encoding="utf-8" - ) - result = self.run_check(standards) - self.assertEqual(0, result.returncode, result.stderr) - report = json.loads(result.stdout) - self.assertEqual([], report["unindexed"]) - self.assertEqual([], report["broken"]) - - def test_reports_unindexed_broken_and_cycle(self) -> None: - """未索引文件、失效链接和循环引用应同时报告。""" - with tempfile.TemporaryDirectory() as temp: - standards = Path(temp) - (standards / "index.md").write_text( - "[主题](topic.md)\n[缺失](missing.md)\n", encoding="utf-8" - ) - (standards / "topic.md").write_text( - "[返回](index.md)\n", encoding="utf-8" - ) - (standards / "orphan.md").write_text("# 未索引\n", encoding="utf-8") - result = self.run_check(standards) - self.assertEqual(1, result.returncode) - report = json.loads(result.stdout) - self.assertEqual(["orphan.md"], report["unindexed"]) - self.assertEqual("missing.md", report["broken"][0]["target"]) - self.assertTrue(report["cycles"]) - - def test_missing_entry_is_input_error(self) -> None: - """入口不存在时应返回输入错误,而不是覆盖问题。""" - with tempfile.TemporaryDirectory() as temp: - result = self.run_check(Path(temp)) - self.assertEqual(2, result.returncode) - self.assertIn("索引入口不存在", result.stderr) - - def test_skill_separates_read_and_write_modes(self) -> None: - """检查默认只读,所有写入均需预览与确认。""" - content = (SKILL / "SKILL.md").read_text(encoding="utf-8") - self.assertIn("检查默认只读", content) - self.assertIn("经用户确认后写入", content) - self.assertIn("不强制创建固定层级", content) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_guidance_init.py b/migration/tests/test_guidance_init.py deleted file mode 100644 index 995a217..0000000 --- a/migration/tests/test_guidance_init.py +++ /dev/null @@ -1,112 +0,0 @@ -import json -import unittest -from pathlib import Path - - -ROOT = Path(__file__).parents[2] -GUIDANCE = ROOT / "plugins" / "skill" / "skills" / "guidance" -INIT = ROOT / "plugins" / "knowledge" / "skills" / "init" - - -class GuidanceInitTest(unittest.TestCase): - """验证规范检索和项目初始化之间的职责边界。""" - - def test_guidance_is_read_only_and_uses_progressive_sources(self) -> None: - """检索 Skill 应声明项目优先、渐进读取且不隐式初始化。""" - - content = (GUIDANCE / "SKILL.md").read_text(encoding="utf-8") - self.assertIn("以只读方式", content) - self.assertIn("不得先递归加载整个知识库", content) - self.assertIn("不得在检索过程中隐式初始化", content) - self.assertIn(".craftkit/project.json", content) - - def test_public_baseline_routes_frontend_and_backend(self) -> None: - """公共基线应提供前后端入口,并让索引引用实际规则文件。""" - - root = GUIDANCE / "references" / "guidance" - index = (root / "index.md").read_text(encoding="utf-8") - self.assertIn("frontend/index.md", index) - self.assertIn("backend/index.md", index) - self.assertEqual(6, len(list((root / "frontend").glob("*.md")))) - self.assertEqual(6, len(list((root / "backend").glob("*.md")))) - - sources = (root / "sources.md").read_text(encoding="utf-8") - for authority in ("RFC 9110", "WCAG 2.2", "OWASP", "MDN Fetch API"): - self.assertIn(authority, sources) - - def test_framework_profiles_are_selected_by_project_version(self) -> None: - """普通任务只能使用当前 profile,且不存在的 profile 不得被猜测。""" - - routing = (GUIDANCE / "references" / "profile-routing.md").read_text( - encoding="utf-8" - ) - self.assertIn("普通开发任务只加载当前 profile", routing) - self.assertIn("升级、迁移或版本比较", routing) - self.assertIn("不默认最新版本", routing) - self.assertIn("不创建空目录", routing) - - init = (INIT / "SKILL.md").read_text(encoding="utf-8") - config = (INIT / "references" / "project-config.md").read_text( - encoding="utf-8" - ) - self.assertIn("精确版本、公共 profile", init) - for field in ('"name"', '"version"', '"profile"'): - self.assertIn(field, config) - - def test_init_supports_existing_and_new_projects(self) -> None: - """初始化 Skill 应同时包含成熟项目与空项目流程。""" - - content = (INIT / "SKILL.md").read_text(encoding="utf-8") - self.assertIn("成熟项目", content) - self.assertIn("空项目", content) - self.assertIn("参考项目", content) - self.assertTrue((INIT / "references" / "existing.md").is_file()) - self.assertTrue((INIT / "references" / "new.md").is_file()) - - def test_project_template_has_safe_shared_fields(self) -> None: - """项目模板应包含检索入口,且不得预置内部标识或机器路径。""" - - path = INIT / "assets" / "project.json" - payload = json.loads(path.read_text(encoding="utf-8")) - self.assertEqual(1, payload["schemaVersion"]) - self.assertIn(payload["initialization"]["mode"], {"existing", "new"}) - self.assertEqual([], payload["dependencies"]["internal"]) - self.assertEqual([], payload["frontend"]["componentLibraries"]) - self.assertEqual([], payload["frontend"]["componentRoots"]) - self.assertEqual([], payload["frontend"]["documentation"]) - self.assertEqual( - ".craftkit/standards/index.md", - payload["guidance"]["standardsIndex"], - ) - serialized = json.dumps(payload, ensure_ascii=False) - self.assertNotIn(":\\", serialized) - - def test_init_collects_frontend_component_metadata(self) -> None: - """初始化应记录组件库证据和入口,但不能默认选择组件库。""" - - content = "\n".join( - path.read_text(encoding="utf-8") - for path in ( - INIT / "SKILL.md", - INIT / "references" / "project-config.md", - INIT / "references" / "existing.md", - INIT / "references" / "new.md", - ) - ) - self.assertIn("componentLibraries", content) - self.assertIn("组件根", content) - self.assertIn("不默认选择流行方案", content) - self.assertTrue((INIT / "assets" / "frontend-components.md").is_file()) - - def test_local_directories_are_the_only_nested_ignores(self) -> None: - """初始化模板只排除本地状态和缓存,不得排除共享资料。""" - - rules = (INIT / "assets" / "craftkit.gitignore").read_text( - encoding="utf-8" - ) - active = [line for line in rules.splitlines() if line and not line.startswith("#")] - self.assertEqual(["/local/", "/cache/"], active) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_knowledge_batch.py b/migration/tests/test_knowledge_batch.py deleted file mode 100644 index f21f499..0000000 --- a/migration/tests/test_knowledge_batch.py +++ /dev/null @@ -1,59 +0,0 @@ -"""验证知识管理批次的职责边界、中性模板和模式路由。""" - -from __future__ import annotations - -import unittest -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -KNOWLEDGE = ROOT / "plugins/knowledge/skills" - - -class KnowledgeBatchTests(unittest.TestCase): - """检查四个 Skill 不重叠,并保持项目知识目录一致。""" - - def read(self, relative: str) -> str: - """读取待验证的 Skill 资源。""" - - return (KNOWLEDGE / relative).read_text(encoding="utf-8") - - def test_trace_is_evidence_based_and_does_not_auto_write(self) -> None: - """偏离复盘应使用中性失效分类,不能自动修改规则。""" - - text = self.read("trace/SKILL.md") - for value in ("missing", "unread", "mismatch", "misapplied", "insufficient", "尚未执行"): - self.assertIn(value, text) - self.assertNotIn("七问", text) - - def test_distill_routes_long_term_knowledge_and_excludes_handoff(self) -> None: - """知识提炼和任务交接必须具有不同产物边界。""" - - text = self.read("distill/SKILL.md") - routing = self.read("distill/references/routing.md") - self.assertIn("不生成接续提示词", text) - for path in (".craftkit/knowledge/pitfalls/", ".craftkit/knowledge/decisions/", ".craftkit/standards/"): - self.assertIn(path, routing) - - def test_lessons_merges_full_lifecycle_without_fixed_categories(self) -> None: - """经验初始化、维护、审计和提升应共享一个目录与中性模板。""" - - text = self.read("lessons/SKILL.md") - for mode in ("`init`", "`curate`", "`audit`", "`promote`"): - self.assertIn(mode, text) - self.assertIn(".craftkit/knowledge/pitfalls/", text) - index = self.read("lessons/assets/index.md") - self.assertIn("当前尚未建立分类", index) - self.assertNotIn("lowcode", index.casefold()) - - def test_worklog_requires_git_evidence_and_separate_write_permission(self) -> None: - """工作日志不能推测工作量或自动写入日报文件。""" - - text = self.read("worklog/SKILL.md") - self.assertIn("不把提交数量当作工作量", text) - self.assertIn("范围内确实没有提交时如实说明", text) - self.assertIn("需要新的明确授权", text) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_local_state.py b/migration/tests/test_local_state.py deleted file mode 100644 index a6daaa2..0000000 --- a/migration/tests/test_local_state.py +++ /dev/null @@ -1,76 +0,0 @@ -import subprocess -import tempfile -import unittest -from pathlib import Path - - -class LocalStateTest(unittest.TestCase): - """验证 `.craftkit/` 能同时承载共享资料和本地状态。""" - - def run_git(self, root: Path, *args: str) -> subprocess.CompletedProcess[str]: - """在隔离仓库执行 Git,并保留输出供断言使用。""" - - return subprocess.run( - ["git", "-C", str(root), *args], - check=False, - capture_output=True, - text=True, - encoding="utf-8", - ) - - def test_nested_gitignore_separates_shared_and_local_content(self) -> None: - """共享资料应可提交,本地目录和缓存应由嵌套规则排除。""" - - with tempfile.TemporaryDirectory() as temp: - root = Path(temp) - initialized = self.run_git(root, "init") - self.assertEqual(initialized.returncode, 0, initialized.stderr) - - craftkit = root / ".craftkit" - craftkit.mkdir() - (craftkit / ".gitignore").write_text( - "# Local CraftKit data\n/local/\n/cache/\n", - encoding="utf-8", - ) - (craftkit / "README.md").write_text("# 项目资料\n", encoding="utf-8") - shared = craftkit / "standards" / "coding" / "rules.md" - shared.parent.mkdir(parents=True) - shared.write_text("# 编码规范\n", encoding="utf-8") - local = craftkit / "local" / "handoff" / "current.md" - local.parent.mkdir(parents=True) - local.write_text("# 本地交接\n", encoding="utf-8") - cache = craftkit / "cache" / "result.json" - cache.parent.mkdir(parents=True) - cache.write_text("{}\n", encoding="utf-8") - - ignored = self.run_git( - root, - "check-ignore", - "--no-index", - "-v", - ".craftkit/local/handoff/current.md", - ) - self.assertEqual(ignored.returncode, 0, ignored.stderr) - self.assertIn(".craftkit/.gitignore", ignored.stdout.replace("\\", "/")) - - cache_ignored = self.run_git( - root, - "check-ignore", - "--no-index", - ".craftkit/cache/result.json", - ) - self.assertEqual(cache_ignored.returncode, 0, cache_ignored.stderr) - - status = self.run_git(root, "status", "--short", "--untracked-files=all") - self.assertEqual(status.returncode, 0, status.stderr) - normalized = status.stdout.replace("\\", "/") - self.assertIn(".craftkit/.gitignore", normalized) - self.assertIn(".craftkit/README.md", normalized) - self.assertIn(".craftkit/standards/coding/rules.md", normalized) - self.assertNotIn(".craftkit/local", normalized) - self.assertNotIn(".craftkit/cache", normalized) - self.assertFalse((root / ".gitignore").exists()) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_migration_scripts.py b/migration/tests/test_migration_scripts.py deleted file mode 100644 index e95cc3c..0000000 --- a/migration/tests/test_migration_scripts.py +++ /dev/null @@ -1,95 +0,0 @@ -"""迁移内部脚本的最小行为测试。""" - -from __future__ import annotations - -import importlib.util -import hashlib -import json -import tempfile -import unittest -from pathlib import Path - -SCRIPT_ROOT = Path(__file__).parents[1] / "scripts" - - -def load_module(name: str): - """从脚本路径加载模块,避免要求项目安装为 Python 包。""" - spec = importlib.util.spec_from_file_location(name, SCRIPT_ROOT / f"{name}.py") - module = importlib.util.module_from_spec(spec) - assert spec.loader - spec.loader.exec_module(module) - return module - - -scan_sources = load_module("scan_sources") -check_skill = load_module("check_skill") -update_lock = load_module("update_lock") - - -class MigrationScriptTests(unittest.TestCase): - def test_scan_uses_relative_paths_and_stable_hash(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) - skill = root / "group" / "demo" - skill.mkdir(parents=True) - (skill / "SKILL.md").write_text( - "---\nname: demo\ndescription: 示例\n---\n", encoding="utf-8" - ) - first = scan_sources.scan_source("source-a", root) - second = scan_sources.scan_source("source-a", root) - self.assertEqual("group/demo", first["skills"][0]["relativePath"]) - self.assertEqual(first["skills"][0]["sha256"], second["skills"][0]["sha256"]) - self.assertNotIn(str(root), json.dumps(first, ensure_ascii=False)) - - def test_check_reports_name_mismatch_and_forbidden_term(self): - with tempfile.TemporaryDirectory() as directory: - root = Path(directory) / "expected" - root.mkdir() - (root / "SKILL.md").write_text( - "---\nname: other\ndescription: 示例\n---\n内部标识", - encoding="utf-8", - ) - result = check_skill.check_skill(root, ["内部标识"]) - codes = {issue["code"] for issue in result["issues"]} - self.assertIn("name-path-mismatch", codes) - self.assertIn("forbidden:内部标识", codes) - - def test_update_lock_preserves_manual_fields(self): - path_hash = hashlib.sha256("group/demo".encode("utf-8")).hexdigest() - key = f"source-a:{path_hash[:16]}" - lock = { - "schemaVersion": 1, - "sources": {}, - "skills": { - key: { - "status": "specified", - "target": "plugins/skill/skills/demo", - } - }, - } - report = { - "sources": [ - { - "id": "source-a", - "commit": "abc", - "skillCount": 1, - "skills": [ - { - "name": "demo", - "relativePath": "group/demo", - "sha256": "123", - } - ], - } - ] - } - merged = update_lock.merge(lock, report) - entry = merged["skills"][key] - self.assertEqual("specified", entry["status"]) - self.assertEqual("plugins/skill/skills/demo", entry["target"]) - self.assertEqual("123", entry["sourceSha256"]) - self.assertNotIn("name", entry) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_public_baseline_batch.py b/migration/tests/test_public_baseline_batch.py deleted file mode 100644 index 0ddcb68..0000000 --- a/migration/tests/test_public_baseline_batch.py +++ /dev/null @@ -1,39 +0,0 @@ -"""公开基线与专项设计 Skill 测试。""" - -from pathlib import Path -import unittest - - -ROOT = Path(__file__).resolve().parents[2] -SKILLS = ROOT / "plugins" / "dev" / "skills" -NAMES = ( - "design-api", "design-db", "review-java", "review-frontend", - "design-frontend-data", "review-mybatis", "design-workflow", -) - - -class PublicBaselineBatchTest(unittest.TestCase): - """验证公开来源登记和版本边界。""" - - def test_sources_are_official_and_dated(self) -> None: - for name in NAMES: - source = (SKILLS / name / "references" / "sources.md").read_text(encoding="utf-8") - self.assertIn("重建日期:2026-08-25", source) - self.assertIn("https://", source) - - def test_project_version_is_not_replaced_by_latest(self) -> None: - content = "\n".join( - (SKILLS / name / "SKILL.md").read_text(encoding="utf-8") for name in NAMES - ) - self.assertIn("不得默认最新", content) - self.assertIn("规则混用", content) - self.assertIn("精确版本", content) - - def test_workflow_does_not_embed_approval_rules(self) -> None: - content = (SKILLS / "design-workflow" / "SKILL.md").read_text(encoding="utf-8") - self.assertIn("不内置审批节点", content) - self.assertIn("项目实际流程引擎", content) - - -if __name__ == "__main__": - unittest.main() diff --git a/migration/tests/test_remaining_wave.py b/migration/tests/test_remaining_wave.py deleted file mode 100644 index 50f4e06..0000000 --- a/migration/tests/test_remaining_wave.py +++ /dev/null @@ -1,87 +0,0 @@ -"""剩余来源连续迁移波次验收测试。""" - -from pathlib import Path -import json -import unittest - - -ROOT = Path(__file__).resolve().parents[2] -DEV = ROOT / "plugins" / "dev" / "skills" -DOC = ROOT / "plugins" / "doc" / "skills" -GIT = ROOT / "plugins" / "git" / "skills" - -TARGETS = { - DEV: ( - "design-api", "design-db", "review-java", "review-frontend", - "design-frontend-data", "review-mybatis", "design-workflow", - "implement-backend", "implement-frontend", "review-code", - "test-backend", "test-ui", "analyze-bugs", "upgrade", "estimate", - ), - DOC: ("report", "message", "requirements"), - GIT: ("release",), -} - - -class RemainingWaveTest(unittest.TestCase): - """验证目标完整性、操作边界和来源状态闭合。""" - - def read_skill(self, root: Path, name: str) -> str: - return (root / name / "SKILL.md").read_text(encoding="utf-8") - - def test_all_targets_have_skill_and_ui_metadata(self) -> None: - names = [] - for root, targets in TARGETS.items(): - for name in targets: - names.append(name) - self.assertTrue((root / name / "SKILL.md").is_file(), name) - self.assertTrue((root / name / "agents" / "openai.yaml").is_file(), name) - self.assertEqual(19, len(names)) - self.assertEqual(len(names), len(set(names))) - - def test_implementation_preserves_workspace_and_authorization(self) -> None: - content = "\n".join( - self.read_skill(DEV, name) - for name in ("implement-backend", "implement-frontend") - ) - self.assertIn("保留用户已有改动", content) - self.assertIn("精确版本", content) - self.assertIn("不自动暂存", content) - self.assertIn("环境配置", content) - - def test_review_and_test_results_are_separated(self) -> None: - review = self.read_skill(DEV, "review-code") - backend = self.read_skill(DEV, "test-backend") - ui = self.read_skill(DEV, "test-ui") - self.assertIn("只读", review) - self.assertIn("不启动完整应用", backend) - self.assertIn("授权后", ui) - self.assertIn("不能替代浏览器结果", ui) - - def test_delivery_actions_are_independently_authorized(self) -> None: - upgrade = self.read_skill(DEV, "upgrade") - estimate = self.read_skill(DEV, "estimate") - release = self.read_skill(GIT, "release") - self.assertIn("源版本", upgrade) - self.assertIn("目标版本", upgrade) - self.assertIn("区间", estimate) - self.assertIn("假设", estimate) - self.assertIn("分阶段授权", release) - - def test_writing_skills_do_not_send_or_invent(self) -> None: - content = "\n".join(self.read_skill(DOC, name) for name in TARGETS[DOC]) - self.assertIn("不得虚构", content) - self.assertIn("默认只生成草稿", content) - - def test_source_lock_has_no_pending_items(self) -> None: - data = json.loads((ROOT / "migration" / "source-lock.json").read_text(encoding="utf-8")) - skills = data["skills"] - self.assertEqual(64, len(skills)) - self.assertNotIn("pending", {item["status"] for item in skills.values()}) - self.assertEqual( - {"migrated", "superseded", "excluded"}, - {item["status"] for item in skills.values()}, - ) - - -if __name__ == "__main__": - unittest.main()