diff --git a/README.md b/README.md index 8eccc7e..9cd54c2 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,8 @@ node plugins/orchestrated-collaboration/tests/verify-three-party-review.test.ts - 当前目标、授权和边界:读取 `C:\Users\Morni\workspace\agent-control\README.md` 及其指向的当前任务; - 当前可安装内容:读取本 README 和对应 Plugin manifest; - `issue-workflow` 的三种模式、验收场景走读和当前证据等级:[`docs/issue-workflow-walkthrough.md`](docs/issue-workflow-walkthrough.md); -- Skill 的失效条件、最少复核、退役路径与复杂度预算:[`docs/lifecycle.md`](docs/lifecycle.md)。其中「必须声明失效条件与最少复核步骤」「数量与语料总量不得净增」由本仓 CI 强制; +- **每个 Skill 替你做什么、什么时候用、花掉多少复杂度预算:[`docs/skills-overview.md`](docs/skills-overview.md)**(生成产物,符合性测试钉住它不会漂); +- 源码即生产的工作方式、Skill 的失效条件、最少复核、退役路径与复杂度预算:[`docs/lifecycle.md`](docs/lifecycle.md)。其中「必须声明失效条件与最少复核步骤」「数量与语料总量不得净增」由本仓 CI 强制; - [方法资产模型](docs/asset-model.md)、[符合性检查](docs/conformance.md)、旧 Issue 和 `codex-work`:只作为历史或待核验材料,不默认指导新工作。 不要从旧交付顺序自行恢复工作。只执行 `agent-control` 当前任务明确授权的改动。 diff --git a/docs/lifecycle.md b/docs/lifecycle.md index fc8b788..493a954 100644 --- a/docs/lifecycle.md +++ b/docs/lifecycle.md @@ -8,6 +8,40 @@ 一条贯穿规则:**能落成检查就不写成文字。** 下面每条规则都标注它实际落在哪一层——只有第 4 层(文本)才依赖执行者自觉,而第 4 层的每一条都应被视为暂时状态。 +## 零、源码即生产 + +**`C:\Users\Morni\workspace\agent-plugins` 是生产源,不是工作目录。** + +三个运行端的 Marketplace 都注册为指向它的本机目录源,而 2026-08-15 实测确认:两个运行端把 Skill **解析到这份工作树本身**,不是 `plugins/cache/<插件>/<版本>/`。 + +- Claude:改工作树 `SKILL.md` 后不重装,`claude plugin details` 报的 on-invoke 成本立刻从 ~7.8k 变 ~14.3k,还原即回落; +- Codex:`codex plugin list` 的 `PATH` 列直接就是 `…/workspace/agent-plugins/plugins/<插件>`。 + +所以「已审源码 → 已安装内容 → 实际行为」这条链中间那一环不存在:**源码即行为**。在生产检出上切分支或留未提交改动,新 Session 会实时读到那份内容。 + +负责人 2026-08-15 决定接受这个事实并调整工作方式,而不是造一个受控快照层——Claude 端可行、Codex 端不可行,半边隔离比不隔离更难查。因此: + +| 位置 | 状态 | 谁动它 | +| --- | --- | --- | +| `workspace/agent-plugins` | 永远是 `origin/main` 的干净检出 | 只有 `plugin_release sync` 快进它 | +| `workspace/agent-plugins-work/<分支>` | git worktree,随便改 | 所有开发在这里 | + +新建开发 worktree: + +``` +git -C C:/Users/Morni/workspace/agent-plugins worktree add ../agent-plugins-work/<分支> -b <分支> +``` + +合并后让新正文生效: + +``` +python C:/Users/Morni/workspace/agent-control/tools/plugin_release/plugin_release.py sync --apply +``` + +`sync` 会校验生产检出干净且在 `main`、快进、**报告本次生效的行为变化**、刷新缓存账目并做指纹验收。生效时机因此可控:不 `sync` 就不生效。 + +强制层次:生产检出被污染时,`plugin_release check --hook` 在会话启动时提醒(第 2⁻ 层);`sync` 在检出不干净或不在 `main` 时直接拒绝执行(第 2 层)。**「开发不要在生产检出里做」本身仍是纪律(第 4 层)**——git 不阻止你在任何 worktree 里编辑。 + ## 一、失效条件与最少复核 每个 Skill 必须在 `tests/workflow-routing.json` 的 `skillLifecycle` 里声明两件事,缺一即 CI 失败: @@ -82,11 +116,14 @@ python tools/plugin_release/plugin_release.py retire <插件> --apply | 规则 | 层 | 承载 | | --- | --- | --- | +| 生产检出不干净或不在 main 时不得发布 | 2 拦截 | `plugin_release sync` 直接拒绝 | +| 选型面不得与来源漂移 | 2 拦截 | CI 重新生成并逐字节比对 | | 必须声明失效条件与最少复核步骤 | 2 拦截 | CI 断言存在且非空 | | `suspect` 标记的结构完整性 | 2 拦截 | CI 断言同时有命中条件与日期 | | 数量与语料总量不得净增 | 2 拦截 | CI 断言 `complexityBudget` | | 退役必须确认三处都不存在 | 3 生成 | `plugin_release retire` | | 复核到期提醒 | 3 生成 | `plugin_release check` 本地报告 | +| 开发不在生产检出里做 | **4 文本** | 无(会话启动钩子只在事后提醒) | | 被标记 `suspect` 后不得再作判据 | **4 文本** | 无 | | 写作判据(leading word/完成门/退出) | **4 文本** | 无 | diff --git a/docs/skills-overview.md b/docs/skills-overview.md new file mode 100644 index 0000000..eea9162 --- /dev/null +++ b/docs/skills-overview.md @@ -0,0 +1,168 @@ + + +# Skill 选型面 + +写给负责人:每个 Skill 替你做什么、什么时候会用到、你怎么看出它在起作用, +以及它花掉多少复杂度预算。**行为的唯一来源仍是各自的 `SKILL.md`**,本页不承载行为。 + +当前 12 个 Skill,共 208,420 字节,占复杂度预算 99.7%。 + +| Skill | 版本 | 体积 | 占预算 | 上次复核 | +| --- | --- | ---: | ---: | --- | +| [issue-workflow](#issue-workflow) | 0.3.18 | 60.1 KB | 29.5% | 2026-08-15 | +| [adaptive-problem-solving](#adaptive-problem-solving) | 0.2.11 | 22.0 KB | 10.8% | 2026-08-15 | +| [orchestrated-collaboration](#orchestrated-collaboration) | 0.2.6 | 21.2 KB | 10.4% | 2026-08-15 | +| [self-improvement](#self-improvement) | 0.1.6 | 16.3 KB | 8.0% | 2026-08-15 | +| [pr-integration](#pr-integration) | 0.3.18 | 15.6 KB | 7.7% | 2026-08-15 | +| [issue-delivery](#issue-delivery) | 0.3.18 | 15.2 KB | 7.4% | 2026-08-15 | +| [objective-to-issues](#objective-to-issues) | 0.3.18 | 13.0 KB | 6.4% | 2026-08-15 | +| [operating-ledger-maintenance](#operating-ledger-maintenance) | 0.3.18 | 11.5 KB | 5.6% | 2026-08-15 | +| [resource-observability](#resource-observability) | 0.2.4 | 9.7 KB | 4.8% | 2026-08-15 | +| [issue-contract-compaction](#issue-contract-compaction) | 0.3.18 | 8.6 KB | 4.2% | 2026-08-15 | +| [knowledge-maintenance](#knowledge-maintenance) | 0.1.3 | 6.3 KB | 3.1% | 2026-08-15 | +| [grilling](#grilling) | 0.1.2 | 3.9 KB | 1.9% | 2026-08-15 | + +## issue-workflow + +**它替你做什么** 让任何一个 Session 只凭远端 Issue 就知道自己该干什么、干到哪、能不能关。 + +**什么时候用** 手上有明确 Issue 时。没有 Issue 时它不挑活,会退回经营总账。 + +**你怎么知道它在起作用** Issue 上出现状态判定与关闭回执,且全链只有它一处作出这个判定。 + +**什么会让它失效** GitHub 原生 sub-issue 关系、Project v2 字段写入语义或 Issue 关闭权限模型发生变化,使正文描述的建图与状态判定不再可执行。 + +所属 Plugin `github-collaboration` `0.3.18`|体积 60.1 KB(占预算 29.5%)|上次复核 2026-08-15|正文 [`plugins/github-collaboration/skills/issue-workflow/SKILL.md`](../plugins/github-collaboration/skills/issue-workflow/SKILL.md) + +## adaptive-problem-solving + +**它替你做什么** 问题求解治理:选方法、控成本、该退出就退出,别在一条路上耗死。 + +**什么时候用** 第一次实质路径选择、进展停滞、范围明显扩大或要交接验收时。 + +**你怎么知道它在起作用** 方向变了会明确说出来;没变就直接继续,不额外汇报。 + +**什么会让它失效** 方法登记面的证据等级分布与登记面 README 声明不符;或三端 description 可见预算的实测基线(2026-08-11 测得 1000 UTF-8 字节)被新的实测推翻。 + +所属 Plugin `adaptive-problem-solving` `0.2.11`|体积 22.0 KB(占预算 10.8%)|上次复核 2026-08-15|正文 [`plugins/adaptive-problem-solving/skills/adaptive-problem-solving/SKILL.md`](../plugins/adaptive-problem-solving/skills/adaptive-problem-solving/SKILL.md) + +## orchestrated-collaboration + +**它替你做什么** 多 Agent、多 Session 时的写入所有权、派发合同与独立验收。 + +**什么时候用** 已授权的多席协作或共享写入碰撞。单 Session 不触发。 + +**你怎么知道它在起作用** Issue 上出现类型化派发合同,以及与实施者不同的独立验收回执。 + +**什么会让它失效** 所选协调后端(当前 orca orchestration)的 Run/Task/Dispatch 语义或标准释放回执格式变化;或其唯一原则源 agent-control/authority/05-resource-operations.md 的资源投入原则变化,使 R1–R6 派发门失去依据。 + +所属 Plugin `orchestrated-collaboration` `0.2.6`|体积 21.2 KB(占预算 10.4%)|上次复核 2026-08-15|正文 [`plugins/orchestrated-collaboration/skills/orchestrated-collaboration/SKILL.md`](../plugins/orchestrated-collaboration/skills/orchestrated-collaboration/SKILL.md) + +## self-improvement + +**它替你做什么** 把一次纠正变成系统改进,并判断该改入口、改 Skill 还是只记任务。 + +**什么时候用** 你指出漂移、误解,或同类错误复发时。一次性小错不触发。 + +**你怎么知道它在起作用** 纠正落到某个持久载体,而不只是当次口头改了。 + +**什么会让它失效** 入口、Skill、任务记录三个改进承载面之一消失或职责变更,使路由判据指向不存在的去向。 + +所属 Plugin `self-improvement` `0.1.6`|体积 16.3 KB(占预算 8.0%)|上次复核 2026-08-15|正文 [`plugins/self-improvement/skills/self-improvement/SKILL.md`](../plugins/self-improvement/skills/self-improvement/SKILL.md) + +## pr-integration + +**它替你做什么** 分支维护与合并前核对,防止把冲突或落后的分支合进主干。 + +**什么时候用** 要合并,或分支落后于主干时。 + +**你怎么知道它在起作用** 合并前有冲突逐项处理与相称验证,合并后只做 fast-forward。 + +**什么会让它失效** 分支保护、合并策略或 force-push 的 lease 保护语义变化,使正文的分支维护步骤不再安全。 + +所属 Plugin `github-collaboration` `0.3.18`|体积 15.6 KB(占预算 7.7%)|上次复核 2026-08-15|正文 [`plugins/github-collaboration/skills/pr-integration/SKILL.md`](../plugins/github-collaboration/skills/pr-integration/SKILL.md) + +## issue-delivery + +**它替你做什么** 叶子 Issue 出 PR 前的工程检查与 PR 现状快照,避免拿没跑过的东西当交付。 + +**什么时候用** 手上的叶子 Issue 要出 PR 时。 + +**你怎么知道它在起作用** PR 描述里有覆盖维度、实际运行了什么、以及剩余验证缺口。 + +**什么会让它失效** PR 创建、审阅请求或必需检查的语义变化,使交付前核对无法只从远端事实判定。 + +所属 Plugin `github-collaboration` `0.3.18`|体积 15.2 KB(占预算 7.4%)|上次复核 2026-08-15|正文 [`plugins/github-collaboration/skills/issue-delivery/SKILL.md`](../plugins/github-collaboration/skills/issue-delivery/SKILL.md) + +## objective-to-issues + +**它替你做什么** 新建 Issue 一次到位:七类前缀、类型与领域标签、父级、运营台条目同批完成。 + +**什么时候用** 要新建 Issue 时。 + +**你怎么知道它在起作用** 新 Issue 建出来就带类型、领域、父级和运营台条目,不需要事后补。 + +**什么会让它失效** 七类标题前缀闭集、两个 label 维度或经营总账 Project 的字段定义被负责人修改,使创建骨架的硬校验与实际仓库不符。 + +所属 Plugin `github-collaboration` `0.3.18`|体积 13.0 KB(占预算 6.4%)|上次复核 2026-08-15|正文 [`plugins/github-collaboration/skills/objective-to-issues/SKILL.md`](../plugins/github-collaboration/skills/objective-to-issues/SKILL.md) + +## operating-ledger-maintenance + +**它替你做什么** 维护经营总账,把诉求、候选、决定、交付与证据分开记,不混成一锅。 + +**什么时候用** 出现值得跨 Session 保留的诉求、决定或证据变化时;或你要看总账时。 + +**你怎么知道它在起作用** 总账上诉求状态、执行状态与证据等级三者分开,不互相冒充。 + +**什么会让它失效** agent-control/authority/10-operating-ledger.md 的事项结构或状态分级变化,使维护动作指向不存在的字段。 + +所属 Plugin `github-collaboration` `0.3.18`|体积 11.5 KB(占预算 5.6%)|上次复核 2026-08-15|正文 [`plugins/github-collaboration/skills/operating-ledger-maintenance/SKILL.md`](../plugins/github-collaboration/skills/operating-ledger-maintenance/SKILL.md) + +## resource-observability + +**它替你做什么** 分开看两件事:账户还剩多少额度何时恢复,和这次工作实际花了多少。 + +**什么时候用** 准备扩大并发、资源接近耗尽,或你问额度时。 + +**你怎么知道它在起作用** 给出带时间戳的读数;取不到就明说未知,不猜。 + +**什么会让它失效** orca account list --json 的返回结构变化:顶层 ok、result.rateLimits.claude/codex 的 status 与 updatedAt,或 usedPercent/windowMinutes/resetsAt/rateLimitResetCredits 这些窗口字段不再按 Skill 正文描述提供。 + +所属 Plugin `resource-observability` `0.2.4`|体积 9.7 KB(占预算 4.8%)|上次复核 2026-08-15|正文 [`plugins/resource-observability/skills/resource-observability/SKILL.md`](../plugins/resource-observability/skills/resource-observability/SKILL.md) + +## issue-contract-compaction + +**它替你做什么** 把长 Issue 压成能恢复的合同,不丢授权、状态与未收口关系。 + +**什么时候用** Issue 评论长到影响新 Session 恢复时。 + +**你怎么知道它在起作用** 恢复的 Session 读到的是压缩后的合同,而不是从头读全部历史。 + +**什么会让它失效** GitHub 返回的时间标量精度或字段序列化形态变化,使「优先比较原始序列化标量」不再能区分真实变更。 + +所属 Plugin `github-collaboration` `0.3.18`|体积 8.6 KB(占预算 4.2%)|上次复核 2026-08-15|正文 [`plugins/github-collaboration/skills/issue-contract-compaction/SKILL.md`](../plugins/github-collaboration/skills/issue-contract-compaction/SKILL.md) + +## knowledge-maintenance + +**它替你做什么** 知识准入:价值门、可信门、失效条件与下次最少复核步骤。 + +**什么时候用** 多来源调研、可重复实验,或结论会影响权威与重要决定时。 + +**你怎么知道它在起作用** 写下的知识带失效条件和下次复核步骤,做不到复核就退出当前知识。 + +**什么会让它失效** agent-control/authority/01-knowledge.md 的两道准入门或可信门八项条件发生变化。 + +所属 Plugin `knowledge-maintenance` `0.1.3`|体积 6.3 KB(占预算 3.1%)|上次复核 2026-08-15|正文 [`plugins/knowledge-maintenance/skills/knowledge-maintenance/SKILL.md`](../plugins/knowledge-maintenance/skills/knowledge-maintenance/SKILL.md) + +## grilling + +**它替你做什么** 用结构化追问压力测试一个计划或决定,把没想清楚的地方逼出来。 + +**什么时候用** 你直接要求,或明确接受建议时。它不会因为任务复杂就自动开始。 + +**你怎么知道它在起作用** 你会被连续追问,且随时可以喊停或换普通路径。 + +**什么会让它失效** 明示同意门被取消,或运行端改为按关键词自动进入长期盘问。 + +所属 Plugin `grilling` `0.1.2`|体积 3.9 KB(占预算 1.9%)|上次复核 2026-08-15|正文 [`plugins/grilling/skills/grilling/SKILL.md`](../plugins/grilling/skills/grilling/SKILL.md) diff --git a/scripts/skills-overview.ts b/scripts/skills-overview.ts new file mode 100644 index 0000000..cede2cb --- /dev/null +++ b/scripts/skills-overview.ts @@ -0,0 +1,118 @@ +#!/usr/bin/env node +// 生成 docs/skills-overview.md:写给负责人的选型面。 +// +// 只有三句话是手写的(写在 tests/workflow-routing.json 的 skillOverview 里): +// 它替你做什么、什么时候用、你怎么知道它在起作用。其余全部推导——版本来自 +// pluginVersions,复核状态来自 skillLifecycle,体积与预算占比来自实测字节。 +// +// **它是生成产物,不是手写文档。** 符合性测试会重新生成并与文件比对,因此 +// 它不可能与来源漂移——这正是 docs/lifecycle.md 要防的东西,选型面自己也得守。 +// +// 运行:node scripts/skills-overview.ts --write + +import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..'); +const read = (path: string): string => + readFileSync(path, 'utf8').replace(/^/, '').replace(/\r\n/g, '\n'); +const directories = (path: string): string[] => + readdirSync(path).filter((entry) => statSync(join(path, entry)).isDirectory()); + +type Manifest = { + skills: Record; + pluginVersions: Record; + complexityBudget: { corpusMaxUtf8Bytes: number }; + skillLifecycle: { + entries: Record< + string, + { lastVerified: string | null; suspect: boolean; invalidatedWhen: string } + >; + }; + skillOverview: { + entries: Record; + }; +}; + +const manifest = JSON.parse(read(join(repoRoot, 'tests', 'workflow-routing.json'))) as Manifest; + +/** SKILL.md 加它自己的 references/,与语料预算同一口径。 */ +function skillBytes(plugin: string, skill: string): number { + const root = join(repoRoot, 'plugins', plugin, 'skills', skill); + let total = Buffer.byteLength(read(join(root, 'SKILL.md')), 'utf8'); + const references = join(root, 'references'); + if (existsSync(references)) { + for (const file of readdirSync(references)) { + if (file.endsWith('.md')) total += Buffer.byteLength(read(join(references, file)), 'utf8'); + } + } + return total; +} + +export function render(): string { + const budget = manifest.complexityBudget.corpusMaxUtf8Bytes; + const rows = Object.entries(manifest.skills).map(([skill, spec]) => { + const bytes = skillBytes(spec.plugin, skill); + return { skill, plugin: spec.plugin, bytes, share: (bytes / budget) * 100 }; + }); + rows.sort((a, b) => b.bytes - a.bytes); + + const lines: string[] = [ + '', + '', + '# Skill 选型面', + '', + '写给负责人:每个 Skill 替你做什么、什么时候会用到、你怎么看出它在起作用,', + '以及它花掉多少复杂度预算。**行为的唯一来源仍是各自的 `SKILL.md`**,本页不承载行为。', + '', + `当前 ${rows.length} 个 Skill,共 ${rows.reduce((s, r) => s + r.bytes, 0).toLocaleString('en-US')} 字节,` + + `占复杂度预算 ${((rows.reduce((s, r) => s + r.bytes, 0) / budget) * 100).toFixed(1)}%。`, + '', + '| Skill | 版本 | 体积 | 占预算 | 上次复核 |', + '| --- | --- | ---: | ---: | --- |', + ]; + for (const row of rows) { + const life = manifest.skillLifecycle.entries[row.skill]; + const verified = life?.suspect + ? '**存疑**' + : life?.lastVerified + ? life.lastVerified + : '**从未**'; + lines.push( + `| [${row.skill}](#${row.skill}) | ${manifest.pluginVersions[row.plugin]} | ` + + `${(row.bytes / 1024).toFixed(1)} KB | ${row.share.toFixed(1)}% | ${verified} |`, + ); + } + lines.push(''); + + for (const row of rows) { + const overview = manifest.skillOverview.entries[row.skill]; + const life = manifest.skillLifecycle.entries[row.skill]; + lines.push( + `## ${row.skill}`, + '', + `**它替你做什么** ${overview.forYou}`, + '', + `**什么时候用** ${overview.whenToUse}`, + '', + `**你怎么知道它在起作用** ${overview.workingSignals}`, + '', + `**什么会让它失效** ${life.invalidatedWhen}`, + '', + `所属 Plugin \`${row.plugin}\` \`${manifest.pluginVersions[row.plugin]}\`|` + + `体积 ${(row.bytes / 1024).toFixed(1)} KB(占预算 ${row.share.toFixed(1)}%)|` + + `上次复核 ${life.suspect ? '**存疑**' : (life.lastVerified ?? '**从未**')}|` + + `正文 [\`plugins/${row.plugin}/skills/${row.skill}/SKILL.md\`](../plugins/${row.plugin}/skills/${row.skill}/SKILL.md)`, + '', + ); + } + return lines.join('\n'); +} + +if (process.argv.includes('--write')) { + const target = join(repoRoot, 'docs', 'skills-overview.md'); + writeFileSync(target, render(), 'utf8'); + console.log(`已生成 ${target}`); +} diff --git a/tests/workflow-routing.json b/tests/workflow-routing.json index 4ce972e..89e000f 100644 --- a/tests/workflow-routing.json +++ b/tests/workflow-routing.json @@ -121,8 +121,8 @@ "comment": "每个 Skill 的失效条件与最少复核步骤,对应 agent-control/authority/01-knowledge.md 可信门第 6、7 条。lastVerified 为 null 表示本制度建立以来尚未复核过——这是真实状态,不是占位。规则见 docs/lifecycle.md。", "entries": { "issue-workflow": { - "invalidatedWhen": "方法登记面的证据等级分布与登记面 README 声明不符;或三端 description 可见预算的实测基线(2026-08-11 测得 1000 UTF-8 字节)被新的实测推翻。", - "minimalRecheck": "逐卡取当前能力证据等级,与登记面 README 的 M0/M1 计数比对。三端 description 回显需要交互式观察运行端目录,不在本步内:预算门本身已由符合性测试守住,重测只在怀疑运行端截断行为变化时才做。", + "invalidatedWhen": "GitHub 原生 sub-issue 关系、Project v2 字段写入语义或 Issue 关闭权限模型发生变化,使正文描述的建图与状态判定不再可执行。", + "minimalRecheck": "在目标仓建一个父 Issue 与一个子 Issue,走一遍 recover→classify→leaf,确认原生父子关系、Project 条目写入与关闭三处仍按正文描述响应。", "lastVerified": "2026-08-15", "suspect": false }, @@ -157,14 +157,14 @@ "suspect": false }, "orchestrated-collaboration": { - "invalidatedWhen": "所选协调后端(当前 orca orchestration)的 Run/Task/Dispatch 语义或标准释放回执格式变化,使派发合同与收口判据无法从执行事实判定。", - "minimalRecheck": "对一个真实 Run 取一次 task 与 dispatch 列表,确认五字段派发合同与四项释放证明都能取到。", + "invalidatedWhen": "所选协调后端(当前 orca orchestration)的 Run/Task/Dispatch 语义或标准释放回执格式变化;或其唯一原则源 agent-control/authority/05-resource-operations.md 的资源投入原则变化,使 R1–R6 派发门失去依据。", + "minimalRecheck": "对一个真实 Run 取一次 task-list 与 worker-list,确认派发合同字段与释放证明可取;并读一次 authority/05 的资源投入原则,确认 R1–R6 投影仍成立且链接指向当前仓而非已冻结的老仓。", "lastVerified": "2026-08-15", "suspect": false }, "adaptive-problem-solving": { - "invalidatedWhen": "方法登记面的证据等级分布与正文声明不符,或三端 description 实测可见预算低于 1000 UTF-8 字节。", - "minimalRecheck": "重跑一次三端 description 回显实测,并核对登记面 M0/M1 计数与正文声明一致。", + "invalidatedWhen": "方法登记面的证据等级分布与登记面 README 声明不符;或三端 description 可见预算的实测基线(2026-08-11 测得 1000 UTF-8 字节)被新的实测推翻。", + "minimalRecheck": "逐卡取当前能力证据等级,与登记面 README 的 M0/M1 计数比对。三端 description 回显需要交互式观察运行端目录,不在本步内:预算门本身已由符合性测试守住,重测只在怀疑运行端截断行为变化时才做。", "lastVerified": "2026-08-15", "suspect": false }, @@ -187,13 +187,78 @@ "suspect": false }, "resource-observability": { - "invalidatedWhen": "agent-control/authority/05-resource-operations.md 的 R1–R6 规则变化,或额度与产能观测面不再可读。", - "minimalRecheck": "读一次该权威核对 R1–R6,并实际取一次当前额度观测。", + "invalidatedWhen": "orca account list --json 的返回结构变化:顶层 ok、result.rateLimits.claude/codex 的 status 与 updatedAt,或 usedPercent/windowMinutes/resetsAt/rateLimitResetCredits 这些窗口字段不再按 Skill 正文描述提供。", + "minimalRecheck": "跑一次 orca account list --json,确认 ok 为真、两个 Provider 的 status 与 updatedAt 可读、上述窗口字段仍存在且量纲未变。", "lastVerified": "2026-08-15", "suspect": false } } }, + "skillOverview": { + "comment": "写给负责人的选型面来源。只保留推导不出来的三句;版本、上次复核、失效条件、体积与预算占比由 scripts/skills-overview.ts 从 pluginVersions、skillLifecycle 与实测字节推出——写两遍就会漂。docs/skills-overview.md 是生成产物,符合性测试断言它与本表一致,因此不可能漂。", + "entries": { + "issue-workflow": { + "forYou": "让任何一个 Session 只凭远端 Issue 就知道自己该干什么、干到哪、能不能关。", + "whenToUse": "手上有明确 Issue 时。没有 Issue 时它不挑活,会退回经营总账。", + "workingSignals": "Issue 上出现状态判定与关闭回执,且全链只有它一处作出这个判定。" + }, + "issue-delivery": { + "forYou": "叶子 Issue 出 PR 前的工程检查与 PR 现状快照,避免拿没跑过的东西当交付。", + "whenToUse": "手上的叶子 Issue 要出 PR 时。", + "workingSignals": "PR 描述里有覆盖维度、实际运行了什么、以及剩余验证缺口。" + }, + "pr-integration": { + "forYou": "分支维护与合并前核对,防止把冲突或落后的分支合进主干。", + "whenToUse": "要合并,或分支落后于主干时。", + "workingSignals": "合并前有冲突逐项处理与相称验证,合并后只做 fast-forward。" + }, + "issue-contract-compaction": { + "forYou": "把长 Issue 压成能恢复的合同,不丢授权、状态与未收口关系。", + "whenToUse": "Issue 评论长到影响新 Session 恢复时。", + "workingSignals": "恢复的 Session 读到的是压缩后的合同,而不是从头读全部历史。" + }, + "objective-to-issues": { + "forYou": "新建 Issue 一次到位:七类前缀、类型与领域标签、父级、运营台条目同批完成。", + "whenToUse": "要新建 Issue 时。", + "workingSignals": "新 Issue 建出来就带类型、领域、父级和运营台条目,不需要事后补。" + }, + "operating-ledger-maintenance": { + "forYou": "维护经营总账,把诉求、候选、决定、交付与证据分开记,不混成一锅。", + "whenToUse": "出现值得跨 Session 保留的诉求、决定或证据变化时;或你要看总账时。", + "workingSignals": "总账上诉求状态、执行状态与证据等级三者分开,不互相冒充。" + }, + "orchestrated-collaboration": { + "forYou": "多 Agent、多 Session 时的写入所有权、派发合同与独立验收。", + "whenToUse": "已授权的多席协作或共享写入碰撞。单 Session 不触发。", + "workingSignals": "Issue 上出现类型化派发合同,以及与实施者不同的独立验收回执。" + }, + "adaptive-problem-solving": { + "forYou": "问题求解治理:选方法、控成本、该退出就退出,别在一条路上耗死。", + "whenToUse": "第一次实质路径选择、进展停滞、范围明显扩大或要交接验收时。", + "workingSignals": "方向变了会明确说出来;没变就直接继续,不额外汇报。" + }, + "self-improvement": { + "forYou": "把一次纠正变成系统改进,并判断该改入口、改 Skill 还是只记任务。", + "whenToUse": "你指出漂移、误解,或同类错误复发时。一次性小错不触发。", + "workingSignals": "纠正落到某个持久载体,而不只是当次口头改了。" + }, + "knowledge-maintenance": { + "forYou": "知识准入:价值门、可信门、失效条件与下次最少复核步骤。", + "whenToUse": "多来源调研、可重复实验,或结论会影响权威与重要决定时。", + "workingSignals": "写下的知识带失效条件和下次复核步骤,做不到复核就退出当前知识。" + }, + "grilling": { + "forYou": "用结构化追问压力测试一个计划或决定,把没想清楚的地方逼出来。", + "whenToUse": "你直接要求,或明确接受建议时。它不会因为任务复杂就自动开始。", + "workingSignals": "你会被连续追问,且随时可以喊停或换普通路径。" + }, + "resource-observability": { + "forYou": "分开看两件事:账户还剩多少额度何时恢复,和这次工作实际花了多少。", + "whenToUse": "准备扩大并发、资源接近耗尽,或你问额度时。", + "workingSignals": "给出带时间戳的读数;取不到就明说未知,不猜。" + } + } + }, "complexityBudget": { "comment": "上限取 2026-08-15 的实际值,不留余量:目的是让「加」必须伴随「换」。撞上限时的正确动作是给出被压缩或退役的对象,不是把数字改大;提高上限是负责人决定,且必须在 Issue 留下理由。corpusMaxUtf8Bytes 同时覆盖 SKILL.md 与 references/,否则把正文挪进 references 就能绕过。docs/ 与 README 写给人看,不计入。", "unit": "utf8-bytes", diff --git a/tests/workflow-routing.test.ts b/tests/workflow-routing.test.ts index 4373241..450da14 100644 --- a/tests/workflow-routing.test.ts +++ b/tests/workflow-routing.test.ts @@ -7,6 +7,7 @@ import assert from 'node:assert/strict'; import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; +import { render as renderSkillsOverview } from '../scripts/skills-overview.ts'; type EdgeKind = 'drive' | 'return' | 'reference'; @@ -2520,6 +2521,21 @@ for (const [skill, entry] of Object.entries(skillLifecycle)) { } } +// 两个 Skill 声明出一模一样的失效条件,几乎只有两种来源:复制粘贴没改,或者某次 +// 批量改写把别人的值覆盖了过来。2026-08-15 真发生过后者——一个按名字定位的改写脚本 +// 把三对编辑全落到了同一条上,issue-workflow 的声明被 adaptive-problem-solving 的 +// 覆盖,而目标三条一条没改到。生成的选型面把它显示出来才被发现。这道断言让它下次 +// 在 CI 就撞上。 +const invalidationTexts = new Map(); +for (const [skill, entry] of Object.entries(skillLifecycle)) { + const previous = invalidationTexts.get(entry.invalidatedWhen); + assert.ok( + !previous, + `${skill} 与 ${previous} 的失效条件逐字相同:要么是复制粘贴没改,要么是某次改写覆盖了别人的值`, + ); + invalidationTexts.set(entry.invalidatedWhen, skill); +} + const budget = manifest.complexityBudget as { unit: string; maxSkills: number; @@ -2554,6 +2570,35 @@ assert.ok( `Skill 语料 ${corpusBytes} UTF-8 字节,超过上限 ${budget.corpusMaxUtf8Bytes}:正确动作是给出被压缩或退役的对象,不是把数字改大`, ); +// ---------- 8.6 选型面是生成产物,不能与来源漂移 ---------- + +// docs/skills-overview.md 写给负责人。它由 scripts/skills-overview.ts 从 +// pluginVersions、skillLifecycle、skillOverview 与实测字节生成。这里重新生成 +// 一次并与提交在仓里的文件比对——文档一旦能被手改就会漂,而这正是 +// docs/lifecycle.md 要防的东西,选型面自己也得守这条。 +assert.deepEqual( + Object.keys(manifest.skillOverview.entries).sort(), + [...declared].sort(), + 'skillOverview 必须恰好覆盖全部 Skill:新增 Skill 必须同时给出写给负责人的三句话', +); +for (const [skill, entry] of Object.entries( + manifest.skillOverview.entries as Record>, +)) { + for (const field of ['forYou', 'whenToUse', 'workingSignals']) { + assert.ok( + typeof entry[field] === 'string' && entry[field].trim().length >= 10, + `${skill}: 选型面缺少 ${field}`, + ); + } +} +const overviewPath = join(repoRoot, 'docs', 'skills-overview.md'); +assert.ok(existsSync(overviewPath), 'docs/skills-overview.md 必须存在'); +assert.equal( + read(overviewPath), + renderSkillsOverview(), + 'docs/skills-overview.md 与来源不一致:它是生成产物,请跑 node scripts/skills-overview.ts --write,不要手改', +); + // ---------- 9. README 的版本总览不能落后于 manifest ---------- // 只钉「仓库目前包含…」这一句总览。README 其余段落是历史叙述,记录某个版本当时做了