diff --git a/.dist-sync/versions.json b/.dist-sync/versions.json index de1e38e..6b62149 100644 --- a/.dist-sync/versions.json +++ b/.dist-sync/versions.json @@ -2,7 +2,7 @@ "baimiao-table-ocr": "v1", "disk-master": "v1", "everything-file-search": "v1", - "init-workspace": "v1", + "init-workspace": "v2", "teach-me": "v2", "think-twice": "v3.1", "why-here": "v1" diff --git a/skills/init-workspace/SKILL.md b/skills/init-workspace/SKILL.md index cf4cb2f..861c731 100644 --- a/skills/init-workspace/SKILL.md +++ b/skills/init-workspace/SKILL.md @@ -1,73 +1,102 @@ --- name: init-workspace -description: 初始化或补全个人执行工作区,搭目录脚手架、生成 AGENTS.md 与可选 CODINGRULES.md。仅在用户明确要求初始化或重构工作区时使用。 +description: 检查当前开发环境,按需初始化或补全个人执行工作区,生成目录脚手架、AGENTS.md 与可选 CODINGRULES.md。 disable-model-invocation: true --- # Init Workspace -为个人执行工作区搭脚手架并生成规则文件。工作区模式:一个目录同时容纳源码仓库、本地文档库(`docs/`)和参考库(`pkgs/`),由 `AGENTS.md` 承载工作区规则。生成的文件假定目标机器装有 `$think-twice`。 +先检查环境,再根据用户要求决定只报告、初始化新目录或补全已有工作区。一个工作区可以容纳多个源码仓、`docs/` 本地文档和 `pkgs/` 参考库。环境检查不是初始化授权,也不是启动 worker 的授权。 -## Step 1 — 访谈 +## Step 1 — 检查 Herdr 与 skills -一轮问完,每题带默认值;已存在的工作区只问缺口: +先检查 `HERDR_ENV` 和 PATH 上的 `herdr`,再查找当前宿主列出或用户指定的 `herdr`、`think-twice`、`why-here` skill,并实际读取文件。记录来源和能否读取;目录存在不等于宿主可用,同名副本以宿主当前选择为准。 -1. 目标目录:在哪个路径创建新工作区,或补全哪个已有目录 -2. 工作区名/项目名(决定目录名与 `.code-workspace` 文件名) -3. 源码仓库:已有路径还是待克隆(待克隆的要拿远端 URL),一个还是多个 -4. Quick Context 内容:用户负责的模块、维护的上游库、需要防止误判的事项 -5. 是否生成 `CODINGRULES.md`(默认否) -6. 工作区是否需要 git 跟踪(默认否,有些工作区不需要) +- `HERDR_ENV` 不为 `1`:不查询或控制 Herdr 会话。可以记录 CLI 存在,并按用户要求为以后生成条件式规则。 +- 在 Herdr 内:读取可访问的 Herdr skill 后,按其要求查询 CLI 帮助与当前会话,核验可用性。宿主没有列出 Herdr skill 时,可运行 `herdr --skill` 获取内置文本并读取;记录其来源为 CLI,不声称已安装到所有宿主。 +- 缺少 skill:报告缺口,不自动安装,也不生成无法执行的 skill 引用。用户明确允许安装后,重新检查。 +- 主控可读不代表 worker 可读;协作规则要求启动 worker 后重新核验必需 skill。 -## Step 2 — 探测现状 +**完成条件:** Herdr 环境和三个 skill 的状态明确;“未查”“不可读”“当前可读”分开记录。 -检查:`AGENTS.md`、`CLAUDE.md`(是否已是可用的链接或 `@AGENTS.md` 桥)、`CODINGRULES.md`、`.agents/skills` 与 `.claude/skills` 的连接、`*.code-workspace`、PATH 上是否有 `just`、`think-twice` skill 是否可用(生成的文件引用它,不可用时提醒用户先安装)。 +## Step 2 — 检查工具、模型与目标目录 -已有文件一律不覆盖:逐节给出保留/替换/新增提案,模板不认识的章节原样保留,用户确认后才写入。 +读取 [references/environment-checks.md](references/environment-checks.md),按其中的命令和边界运行检查。脚本路径相对本 skill,不相对目标工作区。 -## Step 3 — 目录脚手架 +- 有 Python 3.9+ 时使用 `scripts/inspect_environment.py`。默认只检查 PATH、受支持 CLI 的版本/帮助、目标文件存在性;输出 JSON,不写工作区。 +- 将宿主找到的 skill 路径用 `--skill NAME=PATH` 传给脚本。脚本只核验文件可读性,Step 1 的宿主判断仍由 Agent 负责。 +- 包含 `pi`、`claude`、`opencode`、`grok`;`codex`、`gemini` 只查 PATH。用户要求列表时,按参考说明显式开启 Pi/Grok/OpenCode 模型查询和 OpenCode agent 定义查询。其他宿主接口按需补充,没有接口或无法确认副作用时标记未查。 +- 区分 agent 定义与运行实例,模型配置与 CLI 列表;这些都不证明真实调用成功。不推断额度,不发测试 prompt、不登录、不启动 worker。 +- 检查目标的 `AGENTS.md`、`CLAUDE.md` 桥、`CODINGRULES.md`、skills 连接、`*.code-workspace` 和 PATH 上的 `just`。新目录尚不存在时先扫描其现有父目录,并注明这不是目标目录现状。 +- 无 Python 时按同一清单人工检查并报告,不自动安装,不执行无法核验的命令。 -只创建缺失的部分: +只输出允许的字段,不展示配置全文、密钥或原始命令错误日志。脚本输出统一屏蔽 `sk-[0-9a-zA-Z-]+`;人工报告同样检查。匹配串可能不是密钥,其他格式也可能漏检;不据此宣称报告完全无敏感数据,不将脱敏后的路径或 ID 自动用于执行。 -``` --workspace/ +**完成条件:** 提供简短能力/缺口报告及检查范围。用户只要求检查时,到此结束;报告默认不持久化,需要保留时先确认位置。 + +## Step 3 — 补充需求与选择章节 + +根据已有要求一轮询问缺口,不重复询问探测能回答的事实: + +1. 目标目录、工作区名、维护者与用途。 +2. 源码仓库:已有路径还是待克隆、一个还是多个;待克隆需远端 URL。 +3. Quick Context:模块归属、维护的上游库、需要防止误判的事项。 +4. 是否采用 Herdr 并行协作(当前可用时建议采用,但不自动启用)。只读调研和评审不额外建 worktree,并行修改源码的 worker 用独立 worktree。 +5. 是否生成 `CODINGRULES.md`(默认否),工作区是否 git 跟踪(默认否)。 +6. 是否引用用户指定的模型策略文件(默认不生成)。策略表达工具/模型/额度偏好,不把本次扫描清单复制进去。 + +已有文件逐节提出保留/替换/新增方案,模板不认识的章节原样保留。确认后才写入。用户已明确授权的全新空目录无需再问一次。 + +**完成条件:** 明确本次修改范围、源码仓位置与章节选择,已获相应写入确认。 + +## Step 4 — 生成候选规则与脚手架 + +按 [references/environment-checks.md](references/environment-checks.md) 的 decisions 格式整理明确选择,用 `scripts/render_agents.py` 输出候选 `AGENTS.md` 到系统临时目录。它只做模板渲染,不读取扫描结果自行决策。 + +- `AGENTS.md`:使用 [assets/AGENTS.template.md](assets/AGENTS.template.md)。新文件核验后写入;已有文件逐节修改,不将完整候选重定向覆盖。重复补全先对照现有章节,避免重复规则。 +- 可选 Herdr 章节来自 [assets/sections/herdr.md](assets/sections/herdr.md),只有采用 Herdr 且可读其 skill 时加入。`think-twice` 用于正式任务书/交付;`why-here` 只在废弃尝试、纠正误解、完成迁移或替换实现后使用。 +- 模型策略引用必须指向目标工作区实际可读的文件;在本次明确授权创建该文件时可以先生成候选,交付前检查引用。没有文件就省略,不生成空策略文件。采用个人外部策略时,在工作区文件中写清实际来源、读取条件和失效处理。 +- `CLAUDE.md`:先尝试符号链接到 `AGENTS.md`;Windows 需要管理员权限或开发者模式。失败时写一行 `@AGENTS.md`。已有可用桥不动。 +- `CODINGRULES.md`:用户选择生成时使用 [assets/CODINGRULES.template.md](assets/CODINGRULES.template.md),同时保留 AGENTS 指针;未选择时不删已有文件。 +- Python 不可用时,Agent 根据同一模板与章节选择生成候选;仍执行占位符、指针、脱敏及行数检查。 + +只创建缺失的部分: + +```text +/ |- .agents/skills/ 真身目录 -|- .claude/skills Junction 指向 .agents/skills -|- docs/ 本地文档库(prd/ plans/ guides/ sop/ 按需) -|- / 源码仓库,可多个 +|- .claude/skills 指向 .agents/skills +|- docs/ 按需建立 prd/ plans/ guides/ sop/ +|- / 一个或多个源码仓 |- pkgs/ 参考库 |- scripts/ 工作区脚本 -|- .gitignore 至少含 nul(Git Bash 下 > nul 会产生真实文件) -|- .code-workspace 从 assets/project.code-workspace 填充 -|- justfile 仅当 PATH 上有 just 时创建空骨架 +|- .gitignore 至少含 nul +|- .code-workspace +|- justfile 仅当 PATH 有 just |- AGENTS.md, CLAUDE.md ``` -- Windows 目录 Junction 不需要管理员权限:`New-Item -ItemType Junction -Path .claude\skills -Target <绝对路径>\.agents\skills` -- POSIX:`ln -s ../.agents/skills .claude/skills` -- `code-workspace` 的 folders:Workspace(`.`)+ 每个源码仓一个入口 + Docs;有独立入口的目录在 `files.exclude` 里对 Workspace 视图隐藏,从 [assets/project.code-workspace](assets/project.code-workspace) 填充 -- 访谈给到远端 URL 的待克隆仓库,克隆到对应目录;没有 URL 的只建空目录并记入 `docs/TODO.md` -- `docs/` 的子目录与生成的 `AGENTS.md` Workspace Docs 一节保持一致,并写一个最小的 `docs/README.md` 目录地图,不留悬空指针 -- `justfile` 骨架只需一条默认任务:`default:` 下接 `@just --list` +- Windows Junction:`New-Item -ItemType Junction -Path .claude\skills -Target <绝对路径>\.agents\skills`,无需管理员权限。POSIX:`ln -s ../.agents/skills .claude/skills`。已有目录/连接先检查,不覆盖。 +- 从 [assets/project.code-workspace](assets/project.code-workspace) 填充:Workspace(`.`)、每个源码仓、Docs 各一个入口;有独立入口的目录在 Workspace 视图的 `files.exclude` 隐藏。 +- 有 URL 的待克隆仓按授权克隆到对应目录;没有 URL 的只建空目录并记入 `docs/TODO.md`。 +- 写最小 `docs/README.md` 目录地图,与生成规则中引用的目录保持一致。 +- `justfile` 空骨架:`default:` 下接 `@just --list`。 -## Step 4 — 生成规则文件 +**完成条件:** 仅授权且缺失的资源被创建,既有内容按确认方案修改,候选中的可选指针已有实际目标。 -- `AGENTS.md`:复制 [assets/AGENTS.template.md](assets/AGENTS.template.md),填访谈槽位。未生成 `CODINGRULES.md` 时,整节删除 Coding Rules。 -- `CLAUDE.md` 桥:先尝试符号链接指向 `AGENTS.md`(Windows 需要管理员权限或开发者模式);失败则写只有一行 `@AGENTS.md` 的文件。已有可用桥不动。 -- `CODINGRULES.md`(访谈答是才生成):复制 [assets/CODINGRULES.template.md](assets/CODINGRULES.template.md),并保留 `AGENTS.md` 的 Coding Rules 指针节。 +## Step 5 — git 跟踪(用户选择才执行) -## Step 5 — git 跟踪(访谈答是才做) - -1. `git init` -2. `pkgs/.gitignore`:屏蔽全部,只放行 `.gitignore` 与 `README.md`;`pkgs/README.md` 维护参考仓名单 -3. 已存在的源码仓用 `git submodule add ` 登记;拿不到远端 URL 的仓记入 `docs/TODO.md`,不强行处理。`pkgs/README.md` 只维护参考仓名单 +1. `git init`。 +2. `pkgs/.gitignore` 屏蔽全部,只放行 `.gitignore` 与 `README.md`;`pkgs/README.md` 维护参考仓名单。 +3. 对已有源码仓确认是否登记为 submodule;确认后用 `git submodule add ` 登记。拿不到远端 URL 的记入 `docs/TODO.md`,不强行处理。 ## Done when -- [ ] 写入已有文件前得到过用户确认;全新空目录且用户明确说过不必再问时可直接执行。任何已有内容都没有被覆盖 -- [ ] 每条规则只存在于一个文件,`AGENTS.md` 与 `CODINGRULES.md` 无重复 -- [ ] `AGENTS.md` 里的指针都带触发条件和动作,不是裸文件名 -- [ ] commit 尾注规则在 `AGENTS.md`,不在 `CODINGRULES.md` -- [ ] `.claude/skills` Junction 解析成功;`code-workspace` 是合法 JSON -- [ ] 生成的文件不含 `<占位符>`、模板注释或未替换的示例行 -- [ ] `AGENTS.md` 不超过 200 行 +- [ ] 只检查模式没有创建工作区资源、启动 Agent、登录或发起模型推理 +- [ ] 报告区分宿主可读/文件存在、agent 定义/运行实例、模型列出/调用验证 +- [ ] 写入已有文件前得到确认;未知章节和无关用户内容保留,重复补全无重复章节 +- [ ] 每条规则有唯一来源;`AGENTS.md` 与 `CODINGRULES.md` 无重复,commit 尾注规则只在前者 +- [ ] skill、文档与策略指针带触发条件和动作,且目标实际可读;Herdr 执行前会重新核验环境 +- [ ] skills 连接解析成功,CLAUDE 桥有效,code-workspace 是合法 JSON +- [ ] 无占位符、模板注释、未替换示例;`AGENTS.md` 不超过 200 行 +- [ ] 输出做字段筛选及密钥模式屏蔽,说明未验证能力与未完成事项 diff --git a/skills/init-workspace/assets/AGENTS.template.md b/skills/init-workspace/assets/AGENTS.template.md index 6b9b1da..ff2c534 100644 --- a/skills/init-workspace/assets/AGENTS.template.md +++ b/skills/init-workspace/assets/AGENTS.template.md @@ -1,52 +1,42 @@ -# <工作区名> Workspace Guide +# {{name}} Workspace Guide -这个工作区是 <谁> 进行 <项目> 工作时使用的个人执行目录。 +这个工作区是 {{owner}} 进行 {{purpose}} 工作时使用的个人执行目录。 ## Quick Context - -- <用户> 主要负责 <模块> 相关内容。 -- 任务模块不明确时先确认或结合最近上下文判断,不要直接假设。 -- <上游库> 也是 <用户> 维护的库。问题疑似来自这些库时,先说明判断并询问是否去上游排查,不在业务仓库堆绕行补丁。 +{{context}} ## Communication -- 默认中文;路径、命令、代码标识符、技术术语保留原文。 -- 描述工作时直接说动作和结果:做了什么、改了哪个文件、行为变化是什么;每个说法都能对应到具体的文件、命令或数据。 -- 结论先行;有取舍、风险或需要证据链时再补原因。 -- 如无特殊说明或指定,"文档"指工作区文档(`docs/` 与 `AGENTS.md`)。 +- 默认中文;路径、命令、代码标识符、技术术语保留原文。 +- 描述实际动作、文件与行为变化,结论先行;未验证的判断明确标注。 +- 如无特殊说明,“文档”指工作区文档(`docs/` 与 `AGENTS.md`)。 ## Workspace Docs - -- 处理需求、方案、review 或恢复上下文时,优先查看 `docs/README.md`、`docs/prd/`、`docs/plans/`、`docs/guides/`、`docs/sop/`。 -- 目录边界不用卡得太死,重点是快速找到信息并继续推进。 +- 处理需求、方案、review 或恢复上下文时,先读 `docs/README.md`,再按目录地图查找材料。 +{{docs}} -## Shell on Windows -- 临时文件放系统临时目录下的任务独立子目录,不在仓库里散落。 +## Shell +- 临时文件放系统临时目录下的任务独立子目录,不在仓库里散落。 - 路径含空格或中文时加引号。 -- Git Bash/MSYS2 下抑制输出用 `/dev/null`;`> nul` 会创建名为 `nul` 的真实文件,发现即删。 +- Git Bash/MSYS2 下抑制输出用 `/dev/null`;`> nul` 会创建名为 `nul` 的真实文件,发现即删。 ## Commits -- commit message 不加 `Co-Authored-By` 等 AI 署名 trailer,不加会话链接(如 claude.ai/code 的 session URL)。 +- commit message 不加 `Co-Authored-By` 等 AI 署名 trailer,不加会话链接。 ## Design Check -- 实现或 review 中发现修复在叠加条件、状态同步、调用方绕行或特殊分支(patch-on-patch)时,回到模块边界、数据流、调用关系重新看问题,说明更干净的方向。 +- 实现或 review 中发现修复在叠加条件、状态同步、调用方绕行或特殊分支时,重新检查模块边界、数据流和调用关系,说明更干净的方向。 ## Code Review -- findings 按 P0 到 Px 排序,P0.1 式细分可用。 -- 定级前 double-check;缺复现证据的低概率问题降级并标注不确定性。 -- 根据 diff、代码、测试和工作区文档反推修改意图或对应 PRD;推测标注为推测。 -- 提到路径、函数名时顺带说明这块是做什么的。 -- review 结论输出前用 `$think-twice` 做自包含、证据和表达检查。 +- findings 按 P0 到 Px 排序,可用 P0.1 式细分。 +- 定级前核验;缺复现证据的低概率问题降级并标注不确定性。 +- 根据 diff、代码、测试和工作区文档核对修改意图;推测标注为推测。 +- 提到路径、函数名时,说明它负责什么。 ## Output Quality -- 交付需要独立成立的文本(handoff、PR/issue 正文、文档)前,用 `$think-twice`。 - -## Coding Rules - -- 实现、修复或 review 代码前,先完整读取 @CODINGRULES.md 并遵循;宿主已自动加载其内容时不必重复读取。纯文档改动不适用。 +- 交付写清结论、范围、实际验证和未解决事项,使接收者只靠正文及可访问的文件就能继续工作。 +{{quality}} +{{collaboration}} +{{coding}} +{{models}} ## Structure - -- `/`: 源码仓库 -- `pkgs/`: 参考库,仅作补充上下文 -- `docs/`: 工作区文档、PRD、计划、SOP 与临时记录 -- `scripts/`: 工作区脚本 +{{structure}} diff --git a/skills/init-workspace/assets/sections/herdr.md b/skills/init-workspace/assets/sections/herdr.md new file mode 100644 index 0000000..961a1e8 --- /dev/null +++ b/skills/init-workspace/assets/sections/herdr.md @@ -0,0 +1,14 @@ +## Herdr Collaboration +- 本工作区采用 Herdr 管理并行 worker。操作前检查 `HERDR_ENV=1`,读取宿主可访问的 `$herdr`;宿主未列出时,在 Herdr 内运行 `herdr --skill` 并读取返回指令。核验 CLI 与当前会话,以当前 skill 和 CLI 帮助为准。不可用时串行推进或请用户进入 Herdr,不从外部操控会话或绕用其他后台方式。 +- 主控负责拆分、共同接口、任务负责人、跟踪、评审、集成、验收与清理;worker 只完成明确授权的任务。主控自身的子任务可以串行委派。 +- 只读调研和评审不额外创建 worktree。并行修改源码的 worker 通过 Herdr 的 worktree 功能管理独立工作树与 workspace;建在实际源码仓,不在外层资料目录建树。 +- 每项任务有唯一负责人;同一文件同时只有一个写入负责人。共享契约、依赖、锁文件和公共配置由主控指定修改者。共同接口未定时先设计、核验,不为填满并发提前实现。 +- 发现上游错误或需要跨范围修改时,回报成因与所需改动,不在调用方吞错或堆兼容分支。 +- 任务书写明目标、仓库/分支/基线提交、共同接口、允许修改的文件、必读材料、依赖、验收命令及失败情形、停下报告的条件、提交授权和交付接收方。材料必须在 worker 的 cwd 或 worktree 中可读。 +- 后台创建保持用户焦点;使用返回的 workspace/pane ID 和唯一 Agent 名称。启动后核验身份、模型、cwd、分支、必需 skill 的可访问性和可接收任务状态。只读任务也要明确使用的代码基线。 +- 每个任务独立使用测试数据、临时目录和端口;共享依赖明确使用约束。worktree 不隔离凭据或外部文件,不是安全沙箱。 +- 双向消息以 `[主控]` 或 `[任务名]` 开头。完成或阻塞时按本节开头的方式获取并读取 Herdr 指令,通过 Herdr 向实际接收方主动回传一次:完成情况、提交(或未提交原因/不适用)、改动、实际检查与结果、限制、需要主控的动作。只在自己的终端打印不算交付。 +- 回传不等待主控当前任务结束;发送失败时保留消息并报告,不连续重发或操纵对方按键。`idle` / `done` 只是运行状态,不表示验收通过。 +- worker 只在授权分支提交;主控按范围检查、独立评审、依赖集成、集成验证、验收的顺序推进。push、安装、发布另按项目授权执行。 +- 检查命令取自项目指令和实际配置;局部/全量测试、类型检查、真实模型、浏览器和宿主验证分别报告,不能相互替代。测试失败、实现不完整或关键条件未验证时保持未完成或阻塞,不用 skip 消除问题。 +- 关闭前确认交付已回传、提交已保全、未提交文件已处理、剩余事项及下一步已登记。只清理本轮创建的资源;移除共享依赖链接与目录前先核验,不关闭他人的终端。恢复时重新查询状态和 ID。 diff --git a/skills/init-workspace/references/environment-checks.md b/skills/init-workspace/references/environment-checks.md new file mode 100644 index 0000000..99f525d --- /dev/null +++ b/skills/init-workspace/references/environment-checks.md @@ -0,0 +1,87 @@ +# Environment Checks + +## 检查入口与结果边界 + +命令中的 `"${SKILL_DIR}"` 是当前已加载 `SKILL.md` 所在目录的绝对路径,由 Agent 根据实际位置设置;不要假定 cwd 在源码仓或安装目录。先由宿主读取 Herdr 环境与可访问的 skill,再使用脚本;`--skill` 只是候选路径,不验证宿主发现顺序或 worker 访问权限。 + +```bash +python -B "${SKILL_DIR}/scripts/inspect_environment.py" --target /existing/workspace +python -B "${SKILL_DIR}/scripts/inspect_environment.py" --target /existing/workspace --skill herdr=/accessible/herdr/SKILL.md +``` + +默认不保存文件。需要留档时,将 stdout 重定向到系统临时目录下本轮专用目录中的新文件;不要写进 skill 安装目录或直接覆盖工作区文件。所有脚本使用 Python 3.9+ 标准库,不安装依赖。Windows 上使用可用的 `python` / `py -3`,参数路径需加引号。 + +JSON 顶层字段:`schema_version`、`checked_at`、`target`、`herdr`、`skills`、`tools`、`workspace`、`limits`。 + +- 工具 `found` 只证明 PATH 可定位;`version.status=ok` 证明版本格式识别成功,不证明认证可用。 +- Herdr `candidate` 只证明环境变量与 CLI help 通过。Agent 按 Herdr skill 查询当前会话后,才能另行报告会话可用。 +- skill `readable_candidate` 只证明所传文件可读。Agent 实际读取、确认身份和适用范围后,才能将它写入生成决策。 +- 模型 `listed` / `evidence=cli_listed` 不等于调用成功;`call_verified` 始终为 false。 +- `not_checked` 不是空列表或不支持;`unrecognized_output` 不是“没有模型”。`timeout`、`command_failed`、`execution_failed` 和 `output_too_large` 分别保留。 + +单项失败仍输出其余检查,扫描进程返回 0;输入或脚本执行错误返回 2。成功退出不表示每个工具可用。每条命令默认超时 10 秒,可用 `--timeout` 调整到最多 60 秒。失败命令的输出丢弃;成功 help 的 stderr 也参与解析(OpenCode 将帮助写到该流),但原始文本不入报告,仅提取允许字段。CLI 子进程不是沙箱,工具自身可能写缓存或执行启动逻辑,不承诺系统零副作用。 + +## CLI 与模型、agent 的检查 + +默认适配 `pi`、`claude`、`opencode`、`grok` 的版本查询;后三者读取 help 并提取少量参数名。`codex`、`gemini` 仅查 PATH。扫描器不根据用户输入拼接 shell 命令。 + +| 工具 | 模型检查 | Agent 定义检查 | +|---|---|---| +| Pi | 显式 `--pi-models` 使用受限 `--list-models` | 用当前宿主扩展提供的只读 catalog/list;不假设统一内置接口 | +| Claude Code | 当前 help 未证明安全的模型枚举接口,默认未查 | `claude agents` 的含义依版本变化;当前帮助将其描述为后台实例管理,不能当定义清单 | +| OpenCode | 显式 `--opencode-models` 查询 `opencode models --pure`,不传 `--refresh` | 显式 `--opencode-agents` 查询 `opencode agent list --pure`,只提取名称和类型 | +| Grok Build | 显式 `--grok-models` 查询 `grok models` | `--agent` 是选择参数,不是列表接口;不读取配置全文猜定义 | + +按用户的列表查询要求显式开启对应选项;默认不查列表。Grok/OpenCode 查询可能访问目录服务或缓存,`--pure` 只排除 OpenCode 外部插件,不保证离线。命令不发推理 prompt、不登录、不启动 worker。已验证输出格式:Grok 1.0.13、OpenCode 1.15.13;格式无法识别时报告未识别,不猜列表。查询报错或超时就报告,不自动改成带认证、刷新或启动功能的命令。 + +```bash +python -B "${SKILL_DIR}/scripts/inspect_environment.py" --target /existing/workspace --grok-models --opencode-models --opencode-agents --timeout 30 +``` + +新 CLI 或其他列表接口,先检查本机帮助、文档或源码。涉及额外动作时先确认;没有可用接口就标记未查。 + +Pi 的受限查询: + +```bash +python -B "${SKILL_DIR}/scripts/inspect_environment.py" --target /existing/workspace --pi-models +``` + +内部使用 `--offline --no-extensions --no-skills --no-prompt-templates --no-themes --no-context-files --no-approve --no-session --list-models`,并设置 `PI_OFFLINE=1`、`PI_TELEMETRY=0`。这不是当前会话模型清单:扩展 provider 与项目资源被排除,仍会进入 Pi 运行时,可能涉及本地缓存或 provider 自定义行为。旧版本不支持参数时报告失败,不降级成裸命令。Pi 当前源码的 `dist/main.js` 与 `dist/cli/list-models.js` 可用于核验实现;安装位置随机器变化,不硬编码。 + +宿主能提供更完整的模型/agent 列表时,报告来源与查询范围,不混入脚本结果冒充统一扫描完成。仅输出 agent 名称、来源、启用状态(接口提供时)和配置模型;不复制 prompt、凭据、会话内容。运行实例的 pane ID 属于本轮,不写进长期规则。 + +## 生成决策与 stdout + +扫描结果不能直接输入渲染器。Agent 结合检查和用户授权,准备以下 JSON 到本轮临时目录: + +```json +{ + "schema_version": 1, + "name": "Example", + "owner": "维护者", + "purpose": "维护应用", + "context": ["app/ 是实际源码仓。"], + "docs": ["docs/sop/ 保存可重复执行的步骤。"], + "structure": ["app/:源码仓", "docs/:工作区文档", "pkgs/:只读参考库", "scripts/:工作区脚本"], + "skills": {"herdr": true, "think-twice": true, "why-here": false}, + "herdr": true, + "coding_rules": false, + "model_policy": null +} +``` + +`skills` 中的 true 表示 Agent 已实际读取并确认可访问,不是照抄文件存在性。`herdr` 表示用户选择采用该协作方式,独立于当前是否在 Herdr 中;启用时必须有可读 Herdr skill。`coding_rules` 表示候选需要该指针,同时确认目标文件会存在。 + +`model_policy` 为 null 或省略时不生成选型章节。非空时是工作区相对 `.md` 路径,例如 `docs/model-policy.md`;不接受绝对路径或 `..`。渲染器不验证目标存在,Agent 在交付前负责核验。策略内容必须来自用户,不由扫描推断额度、偏好或默认 provider。 + +```bash +python -B "${SKILL_DIR}/scripts/render_agents.py" --decisions /task-temp/decisions.json > /task-temp/AGENTS.candidate.md +``` + +stdout 是 UTF-8 Markdown,错误走 stderr。输出前校验字段、布尔值、占位符和 200 行上限。新工作区验证后保存;已有工作区对照候选逐节修改,保留未知章节,不用此命令覆盖原文件。无 Python 时,Agent 读取基础模板与可选章节,按同一决策填充并人工执行这些检查。 + +## 脱敏 + +`scripts/output_safety.py` 统一处理脚本输出,移除 ANSI 转义后将 `sk-[0-9a-zA-Z-]+` 匹配串替换为 `[REDACTED]`。JSON 值及键均处理,键发生碰撞时拒绝输出,避免静默丢字段。参数解析错误也经过这一边界;文件/JSON 错误只报告类型,不回显内容。 + +这是保守的最后一道检查:短字符串也可能误伤,非 `sk-` 凭据、分段或编码后的密钥不在保证范围内。因此主要防护是字段白名单和丢弃原始错误,不收集认证文件、环境变量全集或完整配置。Agent 自己写的报告同样执行字段筛选与匹配检查。若合法路径或模型 ID 被屏蔽,提示用户核验,不把 `[REDACTED]` 当执行参数。 diff --git a/skills/init-workspace/scripts/inspect_environment.py b/skills/init-workspace/scripts/inspect_environment.py new file mode 100644 index 0000000..1a2f808 --- /dev/null +++ b/skills/init-workspace/scripts/inspect_environment.py @@ -0,0 +1,218 @@ +"""Inspect installed tools without prompts, authentication or workspace writes.""" +import os +from pathlib import Path +import re +import shutil +import subprocess +import sys +from datetime import datetime, timezone + +sys.dont_write_bytecode = True +from output_safety import Parser, emit, entrypoint, redact + +TOOLS = ("pi", "claude", "opencode", "grok", "codex", "gemini") +SKILLS = ("herdr", "think-twice", "why-here") +VERSION = re.compile(r"(? 256_000: + return {"status": "output_too_large"}, "" + return {"status": "ok"}, redact(text) + + +def inspect_cli(name, cwd, timeout, *, which=shutil.which, runner=run_query): + executable = which(name) + row = {"name": name, "status": "found" if executable else "missing", + "models": {"status": "not_checked", "items": []}, + "agent_definitions": {"status": "not_checked", "items": []}} + if not executable: + return row + row["path"] = executable + # Unknown CLI adapters are discovery-only. pi help can load extensions. + if name not in ("pi", "claude", "opencode", "grok"): + row["version"] = {"status": "not_checked"} + return row + env = {**os.environ, "PI_OFFLINE": "1", "PI_TELEMETRY": "0", "NO_COLOR": "1"} + check, text = runner(executable, ["--version"], timeout, cwd, env) + match = VERSION.search(text) + row["version"] = {**check} + if check["status"] == "ok": + row["version"] = {"status": "ok", "value": match.group()} if match else {"status": "unrecognized_output"} + if name != "pi": + check, help_text = runner(executable, ["--help"], timeout, cwd, env) + row["help"] = check if check["status"] != "ok" or help_text.strip() else {"status": "unrecognized_output"} + row["options"] = [flag for flag in ("--model", "--agent", "--effort", "--reasoning-effort") + if re.search(r"(? 1_000_000: + raise ValueError("JSON input exceeds size limit") + return json.loads(text) + + +def entrypoint(main): + try: + return main() + except BrokenPipeError: + return 0 + except Exception as error: + # Last-resort CLI boundary: no exception message/path may bypass redaction. + # SystemExit and KeyboardInterrupt intentionally retain normal CLI behavior. + emit("Input or execution error: " + type(error).__name__, stream=sys.stderr) + return 2 diff --git a/skills/init-workspace/scripts/render_agents.py b/skills/init-workspace/scripts/render_agents.py new file mode 100644 index 0000000..c1eb07c --- /dev/null +++ b/skills/init-workspace/scripts/render_agents.py @@ -0,0 +1,85 @@ +"""Render a candidate AGENTS.md from explicit, host-confirmed decisions.""" +from pathlib import Path +import re +import sys + +sys.dont_write_bytecode = True +from output_safety import Parser, emit, entrypoint, read_json, redact + +ASSETS = Path(__file__).resolve().parent.parent / "assets" +FIELDS = {"schema_version", "name", "owner", "purpose", "context", "docs", "structure", + "skills", "herdr", "coding_rules", "model_policy"} + + +def line(value): + if not isinstance(value, str) or not value.strip() or "\n" in value or "\r" in value: + raise ValueError("expected a nonempty single line") + return value.strip() + + +def bullets(value): + if not isinstance(value, list) or not value: + raise ValueError("expected a nonempty list") + return "\n".join("- " + line(item) for item in value) + + +def validate_policy(value): + value = line(value) + # Only portable workspace-relative paths, never user-specific absolute paths. + if (value.startswith(("/", "\\")) or any(c in value for c in ("\\", ":", "`", "<", ">")) + or any(part in ("", ".", "..") for part in value.split("/")) + or not value.endswith(".md")): + raise ValueError("model_policy must be a workspace-relative Markdown path") + return value + + +def render(config): + if not isinstance(config, dict) or set(config) - FIELDS or config.get("schema_version") != 1: + raise ValueError("unsupported decisions object") + skills = config.get("skills", {}) + if not isinstance(skills, dict) or set(skills) - {"herdr", "think-twice", "why-here"}: + raise ValueError("unsupported skill decisions") + if any(type(value) is not bool for value in skills.values()): + raise ValueError("skill decisions must be boolean") + for name in ("herdr", "coding_rules"): + if type(config.get(name, False)) is not bool: + raise ValueError("section decisions must be boolean") + if config.get("herdr") and not skills.get("herdr"): + raise ValueError("Herdr rules require a host-confirmed readable Herdr skill") + slots = {name: line(config[name]) for name in ("name", "owner", "purpose")} + slots.update({name: bullets(config[name]) for name in ("context", "docs", "structure")}) + quality = [] + if skills.get("think-twice"): + quality.append("- 正式任务书、review 结论及独立交付文本(handoff、PR/issue 正文、文档)输出前,读取并使用 `$think-twice` 检查自包含、事实和表达。") + if skills.get("why-here"): + quality.append("- 工作涉及废弃尝试、纠正误解、完成迁移或替换实现时,交付前读取并使用 `$why-here` 检查遗留内容;检查本身不授权清理代码。") + slots["quality"] = "\n".join(quality) + slots["collaboration"] = (ASSETS / "sections/herdr.md").read_text(encoding="utf-8") if config.get("herdr") else "" + slots["coding"] = ("## Coding Rules\n- 实现、修复或 review 代码前,完整读取 `CODINGRULES.md` 并遵循;宿主已加载时不重复读取。纯文档改动不适用。\n" + if config.get("coding_rules") else "") + slots["models"] = "" + if config.get("model_policy") is not None: + policy = validate_policy(config["model_policy"]) + slots["models"] = ("## Agent and Model Selection\n" + f"- 分派任务前读取 `{policy}`,取得用户的工具、模型和额度偏好;启动前核验准确的 CLI、provider、model 与 thinking 参数。\n" + "- 文件表达偏好,不证明当前可用。模型列表不等于调用成功;策略缺失或与实际能力冲突时报告缺口并确认选择,不自行推断额度或替换模型。\n") + text = (ASSETS / "AGENTS.template.md").read_text(encoding="utf-8") + text = re.sub(r"\{\{([a-z_]+)\}\}", lambda match: slots[match[1]], text) + text = re.sub(r"\n{3,}", "\n\n", text).strip() + if "{{" in text or "}}" in text or "