diff --git a/.codestable/epics/cs-continuous-learning-lifecycle.md b/.codestable/epics/cs-continuous-learning-lifecycle.md new file mode 100644 index 0000000..8b6443b --- /dev/null +++ b/.codestable/epics/cs-continuous-learning-lifecycle.md @@ -0,0 +1,228 @@ +--- +status: active +created: 2026-08-01 +work: ../work/epic-cs-continuous-learning-lifecycle.md +--- + +# CodeStable 持续学习生命周期 + +## 起点与目标 + +四个 task skill 已会检索项目 lesson,`cs-keep` 也有证据、查重与合并门槛,但闭环停在“读过”和 +“写下”:任务中不会稳定识别真正的晶化时刻,lesson 再次命中后不验证真伪,也没有项目内迁移 +效果的因果证据。现有 eval 每个 cell 只有一次 agent invocation,不能证明任务 A 的经验改善了 +fresh 任务 B。 + +本 Epic 先落地项目内三步闭环: + +```text +任务内静默观察 -> 强信号候选 -> 显式授权后写 observed lesson +-> fresh 任务 read-repair -> validated / retired +-> 测试、checker、项目文档、ADR 等 canonical owner +``` + +最终用 paired cross-session 实验证明:A 形成的 lesson 在不传递聊天上下文、不向 B 泄题时,能 +提高 fresh agent 完成 B 的正确率。跨项目、团队级与 CodeStable 全局方法论只预留边界。 + +## 范围与非目标 + +范围: + +- `cs-feat`、`cs-issue`、`cs-refactor`、`cs-epic` 的静默信号识别、read-repair 与收尾 UX。 +- `cs-keep` 的 lesson 创建、验证、反证、晋升、退役与清理。 +- 项目内 session candidate -> lesson -> canonical owner 的迁移。 +- 维护者侧 paired/sequential eval runner 与 `cs-learning-transfer-001`。 +- ADR、双语公开文档、契约/eval 测试与 package 回归验证。 + +非目标: + +- 不新增第九个 skill,不扩大 `cs` 最小决策核,不让 task skill 读取 sibling skill 文件。 +- 不恢复 `cs-feedback`、production feedback importer、transcript 扫描、状态机或自动上传。 +- 不创建 `.codestable/learning/`、全局 lessons、常驻索引、后台 telemetry 或集中 runtime。 +- 不持久化未收敛讨论、原始问答、完整思考过程或候选分支。 +- 不自动跨项目共享;脱敏反馈包、团队知识库与 shipped skill 上游更新留给后续 Epic。 +- 不用更醒目的文字代替能落成测试、lint、checker、类型或 deterministic helper 的约束。 + +## 核心模型 + +| 对象 | 生命周期与 owner | +|---|---| +| Learning signal | 当前任务内的可核实事件;未过门槛即丢弃 | +| Crystallization candidate | 会话内有界候选;不是项目事实,不自动获得写入授权 | +| Lesson | 项目级 staging,一条一文件;只保留尚未被更强 owner 完整承接的经验 | +| Canonical guard | 测试、checker、lint、类型或 helper,机械阻止同类错误 | +| Canonical knowledge | 项目既有文档、glossary、ADR 或 `attention.md` 中的唯一事实 owner | +| Transfer evidence | fresh B 在有/无 lesson 的同源仓库上产生的 paired 结果 | + +检索次数、模型自评置信度和“本次讨论过”不算学习证据。lesson 是可删、可纠偏的 staging, +canonical guard/knowledge 才是稳定归宿。 + +## 行为契约 + +### 开工:有效命中必须说明影响 + +只有 scope 符合、未退役、经当前代码/测试/canonical 文档核实,并真实改变计划或验证的条目才算 +有效命中。四个 task skill 必须报告:`经验命中:{path}({status});核验:{fact};影响: +{plan_or_check}`。纯关键词碰撞不报告为有效命中,也不能把“读过”冒充“采用”。 + +应用前 read-repair:`retired` 不应用;`observed` / `validated` 先核实再用;只是相关但没有改变 +行为时不制造复用证据;当前事实明确反证时立即停止应用并走窄退役,证据不足时不猜。 + +### 任务中:静默识别晶化时刻 + +任务内只在内存保留最多 3 条候选,按新证据替换低价值项,不暂停或询问。已有普通 work 时可 +复用证据节;不得为候选新建 work、transcript 或状态文件。 + +强信号限于:owner 纠正实际改变方案/代码/术语/验证;可复现证据推翻根因;同一路径失败两次后 +更换假设;blocking/important finding 暴露未编码不变量;新 red -> green 捕获可复发失败;lesson +真实改变本次行为或被反证;重复 workaround;方法显著降低重试、成本或风险。 + +候选还必须同时有可追溯证据、能写成未来动作、适用于本次精确 diff 之外、且没有现成 canonical +owner。网络波动、拼写、泛化口号、活动记录和已被机械 owner 完整覆盖的事实直接丢弃。选择时先看 +失败后果,再看证据与复发可能,最后优先低成本机械化,不生成伪精确分数。 + +### 收尾:低打扰与机械化优先 + +- 普通任务只在强信号成立时展示最高价值一条,首行固定 `晶化候选:{rule}`,并给证据、范围与 + 建议归宿;无强信号完全不显示模板。没有记忆写入授权时不落盘,候选随会话结束消失。 +- 用户已明确说“记住 / 更新 / 退役”时同轮按 `cs-keep` 处理,不重复确认。 +- Epic 子项不展示、不询问;每个子项至多把一条去重候选写入既有游标证据区,最终毕业清单一次 + 处理并复用最终 owner gate。 +- 当前任务范围内能直接落成 red -> green 测试/checker 的约束优先机械化,不另写重复 lesson; + 会扩大范围时只给候选,不借学习名义扩权。 + +## Lesson 生命周期与授权 + +新 lesson 最小格式: + +```markdown +--- +status: observed +scope: 模块 / 命令 / 场景关键词 +date: YYYY-MM-DD +--- +规则:未来可执行的结论。 +适用 / 不适用:边界与停止应用信号。 +证据:最多三个代表性路径、测试、diff 或任务指针。 +候选归宿:test | checker | attention | project-doc | adr | codestable-eval +``` + +| status | 判据 | +|---|---| +| `observed` | 单次任务有证据,尚未在独立后续任务验证 | +| `validated` | 非创建该 lesson 的任务和 agent invocation 中有效命中,真实改善行为并验证成功 | +| `retired` | 被事实反证、范围完全失效或已被更强 owner 替代;不得应用且不再复活原结论 | + +旧 lesson 缺 `status` 按 `observed` 读取,不批量迁移;只有真实状态变化或 `cs-keep` 本来就要更新 +时才补字段。不得保存原始对话、逐次命中日志或无限 evidence history。 + +创建、改写规则/scope、晋升、删除与跨项目反馈仍须用户显式 `cs-keep` 诉求、接受普通候选,或 +Epic 最终毕业 gate。为实现不中断的 read-repair,仅对已有且有效命中的 lesson 开放两种窄维护: + +- `observed -> validated`:独立后续任务确实采用并验证成功,只补一次代表性证据; +- `observed|validated -> retired`:当前仓库事实直接反证,只写原因与替代/反证指针。 + +窄维护不新建事实、不改规则、不扩 scope、不新增 gate,随当次代码、证据和游标进入同一语义原子 +milestone,最终报告文件变化;稳定 validated 命中不写文件。需要改写结论或证据不足时只给候选。 +任务内发现已有 canonical owner 时先 retire,删除留给 `cs-keep`;`cs-keep` 显式晋升则先验证新 owner, +再在同一更新中直接删除重复 lesson。新结论不得通过复活 retired 条目获得 validated 身份。 + +`cs-keep` 继续负责查重、合并与约 50 条预算,并按以下顺序路由:机械 guard 优先;高频必读事实进 +`attention.md`(≤25 条);同时满足难回退、缺少上下文会令人意外、源于真实取舍的结构性决定进 +ADR;其他方法进项目既有文档。目标不存在时请 owner 选择,不发明目录。`codestable-eval` 只标记 +未来上游候选,本轮不导出、不上传、不改 skill。 + +## 项目内跨会话迁移实验 + +现有 runner 每 cell 只调用一次 agent。新场景增加 `answerType/task.kind: learning-transfer`,由独立 +sequence 模块承载;fixture 明确 A/B owning skill,每次 invocation 只注入该 skill 的冻结快照。 + +每个 paired cell: + +1. 从 seed repo 运行 A,机械验证结果并捕获最多一条候选:普通任务读最终输出的 `晶化候选`, + Epic 子项读既有游标证据区;A 失败计入失败,不得丢弃。 +2. 复制完全相同的 post-A repo 为 treatment/control。 +3. 先验证候选确属 lesson 类;若应进 attention/ADR,则标 fixture invalid,不算 skill failure。 +4. 仅 treatment 用 fresh `cs-keep` invocation 接收候选、证据和显式“记录”授权;除 lesson 外的 + mutation 视为失败。 +5. 可选 `between_tasks` hook 必须同样作用于两份 repo;stale guard 用它制造真实版本反证。 +6. treatment/control 各启动 fresh B;skill snapshot、任务文本和 prompt hash 相同,不点名 lesson, + 不继承 A、curation 或彼此上下文,唯一处理变量是 lesson。 +7. 临时目录销毁前跑 hidden/deterministic checks;B diff 白名单为任务改动、必要 work 证据,以及 + matched lesson 的窄状态/证据迁移,禁止改规则/scope 或破坏 schema。checkpoint key 含 phase, + 半 pair 不算完成,treatment/control 顺序按 k 交替;只存结构化指标、必要 diff 与哈希。 + +Epic 正向 fixture 不豁免 owner gate:seed 提供 `active` 永久 Epic、有效批准 hash、完整执行策略和至少 +两个已批准子项,A/B 只执行边界内子项。这样测试恢复与学习,不伪造 owner/reviewer 授权。 + +`cs-learning-transfer-001` 至少含四个正向场景(feat/issue/refactor/epic)和无关、stale 两个 guard。 +正向 fixture 的 golden 必须全绿,naive 只在目标不变量上红;真实运行前冻结 fixture、hypothesis、 +primary metric、预算与失败判据,禁止看到结果后删案例。 + +主 oracle 均为 `[measured]`:A 结果、候选唯一性、lesson schema/mutation、B treatment/control hidden +pass、paired win/loss、stale retirement、prompt equality 和回归。语义质量只作 `[soft]`;统计不足时 +整体 verdict 标 `[underpowered]`。dry-run 成本必须累加 A、curation、B×2 及场景 hook 的实际 invocation, +不得沿用 one-shot 估算。校准可用 `k=2`,但不能形成接受证据。 + +最终运行至少两个 model family、每 fixture `k >= 5`,使每个 family 在四个正向 fixture 上至少有 +20 个完成 pair;任一 primary aggregate 仍标 `[underpowered]` 时扩大 fixture/repeat 后重跑,不能接受 +Epic。只有 treatment 比 control 高至少 25pp、两个 family 都为正、losses 不多于 wins、两 guard +无回退、隔离检查 100% 通过,才能完成;失败保留负结果并继续修正。 + +## 子项契约 + +- `LEARN-1`(feat):ADR-006、晶化算法、窄维护授权、四 task skills,以及 skill/architecture + 等集与负向契约测试;无依赖;测试先红,Epic 不新增暂停,lesson 状态变更进入当次 milestone。 +- `LEARN-2`(feat):`cs-keep` schema/lifecycle/晋升与清理;依赖 LEARN-1;同时拥有 WORKFLOW、 + README、SKILL_CATALOG、why-codestable 双语同步及 documentation tests;测试先红。 +- `LEARN-3`(feat):ADR-003 paired 增量、sequence runner/scorer、fixture 校验、成本模型、eval 单测与 + `cs-learning-transfer-001` 真实跨模型实验;依赖 LEARN-1/2;runner/fixture 测试先红。 + +## 影响面 + +必须修改: + +- 五个 shipped skills:`cs-feat`、`cs-issue`、`cs-refactor`、`cs-epic`、`cs-keep`。 +- `docs/adr/003-cs-skill-evaluation-loop.md`;新建 `docs/adr/006-project-learning-lifecycle.md`。 +- `WORKFLOW*`、`README*`、`SKILL_CATALOG*`、`docs/why-codestable*` 中英文。 +- `tests/test_skill_contracts.py`、`test_v2_architecture_contract.py`、`test_v2_documentation_contract.py`。 +- `.claude/skills/eval-cs-skill/SKILL.md` 与 scripts 的 `runner.py`、`fixtures.py`、`buildprompt.py`、 + `_model.py`、`config.py`、`metrics.py`、scorer registry、新 sequence/scorer 模块。 +- `tests/test_cs_skill_eval.py`;新建 `experiments/cs-learning-transfer-001/`。 + +需要验证:`cs`、`cs-onboard`、`cs-review`、alias、AGENTS/CLAUDE 与 build-cs-skill 不变;8 skills、 +README 精简结构、全部 Markdown ≤300、plugin/package/distribution tests 继续成立。发布元数据只在 owner +决定发布时处理。 + +仍待调查:LEARN-3 dry-run 核实两个 model family 的 adapter 与预算;不可用时按 ADR-003 报告, +不得用单模型冒充跨模型结论。 + +## 验收标准 + +- 四 task skills 对有效命中报告路径、核验与具体影响;候选静默、普通最多一条、Epic 最终处理。 +- 三态可解析且按需迁移;退役不再应用,validated 稳定命中无 churn,机械化与单一 owner 优先。 +- 新 lesson 与跨项目分享保持显式授权;无 transcript、feedback runtime、global lessons 或新 skill。 +- paired 单测证明四类 invocation 独立、B prompt 相同、只有 lesson 差异、半 pair 不完成、成本不低估。 +- 冻结实验在两个 model family 上以非 `[underpowered]` 证据满足 verdict;否则本 Epic 保持未完成。 +- 中英文契约对称;相关/全量 pytest、分发测试、plugin check 与 `git diff --check` 通过。 +- 最终由 fresh reviewer 按最新批准版本做 acceptance review,再由 owner 整体验收。 + +## 关键决策 + +- 持续学习靠任务证据与按需 read-repair,不靠后台采集或每任务仪式。 +- 已有 lesson 的两种窄状态维护可静默进行;新知识与跨项目分享仍须显式授权。 +- 项目到全局的未来路径必须经过脱敏 packet、fixture、跨模型 eval 与 regression,本轮不实现。 + +## 最终交付索引 + +待执行后以 ADR、diff、测试与 `experiments/cs-learning-transfer-001/` 指针填写,不复制完整日志。 + +## 整体验收 + +待全部子项完成后填写。 + +## 遗留风险 + +- 模型可能漏掉信号;以低打扰换取较低召回,不用 transcript 扫描补偿。 +- thin harness 无 runtime 强制;契约测试与 paired eval 是主要回退防线。 +- 校准小样本可能只能给 `[underpowered]` 证据;必须扩大最终运行后才能接受,不得隐藏该标签。 diff --git a/.codestable/work/epic-cs-continuous-learning-lifecycle.md b/.codestable/work/epic-cs-continuous-learning-lifecycle.md new file mode 100644 index 0000000..ae52da3 --- /dev/null +++ b/.codestable/work/epic-cs-continuous-learning-lifecycle.md @@ -0,0 +1,52 @@ +--- +epic: ../epics/cs-continuous-learning-lifecycle.md +phase: executing +approved_revision: 4b0b8e8e0596e7ee42611864843512ecd9402970bb3682b5605f0dc0f8dcf01a +current_item: LEARN-2 +next_action: implement LEARN-2 with tests first +blocked_by: null +item_progression: continuous +milestone_commit: authorized +remote_publish: each-milestone +--- + +## 子项进度 + +- [x] LEARN-1 +- [ ] LEARN-2 +- [ ] LEARN-3 + +## 临时决策与证据 + +- 现有四个 task skill 只有 lesson 检索与笼统收尾推荐,没有 read-repair 或强信号筛选。 +- 现有 eval runner 每个 cell 只有一次 invocation,无法证明跨会话迁移。 +- 设计调研已核对 domain-modeling、teach、diagnosing-bugs、prototype、neat-freak 与现有 ADR-003; + 只借鉴晶化、证据晋级、机械化优先与 read-repair,不引入它们的默认目录或全局状态。 +- design review round 1:Paseo `ceb5d5d6-4c74-4825-b600-64d5111b2f48`, + `claude-fable-5` / `plan-high`,冻结 SHA-256 `a660265a...e7ffd`;1 blocking / 4 important 已处理。 +- revised proposed Epic SHA-256:`a0107b6da2079556024c1a4807365e93a365149d37bedbbed7987188af45aa45`。 +- design review round 2:Paseo `50bf355c-fbbb-4ef0-a9fd-ec3d9c560e9f`, + `claude-fable-5` / `plan-high`,冻结 SHA-256 `a0107b6d...aa45`;0 blocking / 3 important 已处理。 +- final design review round 3:Paseo `00358ef1-46a8-4976-abe6-8a2d4b783a07`, + `claude-fable-5` / `plan-high`,冻结 SHA-256 `2c023c22...62cd`;0 blocking / 0 important / 3 nit, + 结论可交 owner 确认。 +- accepted nit interpretation:Epic 游标候选沿用 `晶化候选:` marker;25pp 是总体聚合阈值且每个 + model family 方向必须为正;“等集测试”指四 task skills 的一致契约断言。 +- owner 于 2026-08-01 确认 proposed Epic,并选择 `continuous` / `authorized` / + `each-milestone`;激活后批准版本 SHA-256 为 + `4b0b8e8e0596e7ee42611864843512ecd9402970bb3682b5605f0dc0f8dcf01a`。 +- LEARN-1 tests-first:新增契约测试先得到 `4 failed, 2 passed`,实现后定向契约 + `25 passed`、全量 `130 passed, 1 skipped`、分发 `3 passed, 1 skipped`,plugin package check 与 + `git diff --check` 通过;四个 task skill 与 ADR-006 均未超过 300 行。 +- LEARN-1 晶化候选:无。Markdown 换行造成的锚点误判已通过空白归一化 helper 机械化,不另写 + 重复 lesson。 +- LEARN-1 diff review round 1:Paseo `8a20cd70-9f55-4331-9b69-90e63bed4913`, + `claude-fable-5` / `plan-high`,冻结 staged patch SHA-256 `eff4faae...5d02`; + `0 blocking / 1 important / 3 nit`。已补齐 canonical-owner 退役、候选证据/范围/归宿、显式记忆 + 诉求同轮处理、窄维护报告,并加入旧 lesson 缺 `status` 的独立安装兼容;新增锚点先红后绿。 +- LEARN-1 修复后验证:三份契约套件 `37 passed`、全量 `130 passed, 1 skipped`、分发 + `3 passed, 1 skipped`,plugin package check 与 `git diff --check` 通过。 +- LEARN-1 diff review round 2:fresh Paseo `76255cc7-43b3-44d7-86af-90a2fffd3284`, + `claude-fable-5` / `plan-high`,冻结 staged patch SHA-256 `e3490e44...7deb`; + `0 blocking / 0 important / 4 nit`,结论可合。保留的 nit 仅为排版、授权来源概括、测试冗余和 + 范围外 cs-review 旧推荐句,不影响行为或授权边界。 diff --git a/docs/adr/006-project-learning-lifecycle.md b/docs/adr/006-project-learning-lifecycle.md new file mode 100644 index 0000000..92c48e9 --- /dev/null +++ b/docs/adr/006-project-learning-lifecycle.md @@ -0,0 +1,155 @@ +--- +adr: "006" +title: "CodeStable 项目内持续学习生命周期" +status: Accepted +date: 2026-08-01 +applies-to: + - "plugins/codestable/skills/cs-feat/" + - "plugins/codestable/skills/cs-issue/" + - "plugins/codestable/skills/cs-refactor/" + - "plugins/codestable/skills/cs-epic/" + - "plugins/codestable/skills/cs-keep/" + - ".codestable/lessons/" + - ".codestable/work/" +enforcement: test +stage: [author, execute, keep, review] +lint: "python3 -m pytest tests/test_skill_contracts.py tests/test_v2_architecture_contract.py tests/test_v2_documentation_contract.py" +--- + +# ADR-006: CodeStable 项目内持续学习生命周期 + +## Context + +四个 task skill 已会检索项目 lesson,`cs-keep` 也有证据、查重与合并门槛,但现有闭环只保证 +“读过”和“写下”。任务中不会稳定识别值得复用的晶化时刻;lesson 再次命中后没有统一的核验、 +纠偏与退役规则;命中次数和模型自评也无法证明旧经验真实改善了后续任务。 + +与此同时,自动保存对话、每次任务都询问是否记录,或把所有经验长期留在 lesson,会制造隐私、 +噪声、双重真相和流程中断。项目需要一个低打扰、证据驱动且最终收敛到更强 owner 的学习周期, +而不是新的 feedback runtime、后台采集系统或全局知识库。 + +## Decision + +### 生命周期对象与唯一 owner + +项目内学习采用以下分层: + +| 对象 | 生命周期与 owner | +|---|---| +| Learning signal | 当前任务内的可核实事件;未过门槛即丢弃 | +| Crystallization candidate | 会话内有界候选;不是项目事实,也不自动获得写入授权 | +| Lesson | 项目级 staging,一条一文件;只保留尚未被更强 owner 完整承接的经验 | +| Canonical guard | 测试、checker、lint、类型或 deterministic helper,机械阻止同类错误 | +| Canonical knowledge | `attention.md`、项目既有文档、glossary 或 ADR 中的唯一事实 owner | +| Transfer evidence | fresh 后续任务在有、无 lesson 条件下产生的 paired 结果;实验机制归 ADR-003 | + +检索次数、“讨论过”和模型自评置信度都不是学习证据。lesson 是可删除、可纠偏的暂存层;同一事实 +不得同时把 lesson 与 canonical guard/knowledge 当作并列真相。 + +### 强信号与晶化候选 + +四个 task skill 在任务中静默观察,内存中最多保留 3 条候选,并用新证据替换较低价值项,不为 +候选创建 work、transcript 或状态文件。强信号只包括: + +- owner 纠正真实改变了方案、代码、术语或验证; +- 可复现证据推翻根因,或同一路径失败两次后更换假设; +- blocking/important finding 暴露尚未编码的不变量; +- 新 red -> green 验证捕获了可复发失败; +- 已有 lesson 真实改变本次行为,或被当前事实反证; +- workaround 重复出现,或某方法显著降低重试、成本或风险。 + +候选还必须同时具备可追溯证据、可写成未来动作、适用于本次精确 diff 之外,且尚无现成 +canonical owner。网络波动、拼写、活动记录、泛化口号和已被机械 owner 完整覆盖的事实直接丢弃。 +排序先看失败后果,再看证据与复发可能,最后优先低成本机械化;不得生成伪精确分数。 + +普通任务仅在强信号成立时,于收尾展示最高价值的一条,首行固定为 +`晶化候选:{rule}`,并给出证据、范围和建议归宿;无强信号时不显示空模板。候选不暂停任务, +没有记忆写入授权就随会话结束消失。用户已明确要求“记住 / 更新 / 退役”时,同轮按 `cs-keep` +处理,不重复确认。 + +### Lesson 三态 + +新 lesson 必须声明 `status`、`scope` 与 `date`,正文只保留可执行规则、适用/停止应用边界、最多 +三个代表性证据指针和候选归宿。状态只允许: + +| status | 判据 | +|---|---| +| `observed` | 单次任务有证据,尚未在独立后续任务验证 | +| `validated` | 非创建该 lesson 的后续任务和 agent invocation 中有效命中,真实改善行为并验证成功 | +| `retired` | 被仓库事实反证、范围完全失效,或已被更强 owner 替代;不得再应用 | + +旧 lesson 缺少 `status` 时按 `observed` 读取,不批量迁移;只有真实状态变化或 `cs-keep` 本来就要 +更新时才补字段。`retired` 结论不得复活并继承 `validated` 身份;新结论必须重新从 `observed` 开始。 + +### Read-repair 与窄维护授权 + +task skill 应用 lesson 前必须 read-repair:`retired` 不应用;`observed` / `validated` 先用当前代码、 +测试或 canonical 文档核实。只有条目 scope 符合、事实仍成立且真实改变计划或验证时,才报告 +`经验命中:{path}({status});核验:{fact};影响:{plan_or_check}`;纯关键词碰撞或只是读过不算 +有效命中。 + +创建 lesson、改写规则或 scope、一般晋升、删除及跨项目反馈,仍需用户显式 `cs-keep` 诉求、接受 +普通任务候选,或 Epic 最终毕业 gate。为使 read-repair 不中断任务,仅对已有且有效命中的 lesson +开放两种无需新增确认的窄维护: + +- `observed -> validated`:独立后续任务确实采用并验证成功,只补一次代表性证据; +- `observed|validated -> retired`:当前仓库事实直接反证,或发现已有 canonical owner,只写退役原因 + 与替代/反证指针。 + +窄维护不得新建事实、改规则、扩 scope 或新增 gate,必须与当次代码、证据和游标组成同一语义原子 +milestone,并在最终报告列出文件变化。稳定的 `validated` 命中不写文件。证据不足或需要改写结论时 +只形成候选;发现 canonical owner 时 task skill 先 retire,删除与合并仍由 `cs-keep` 负责。 + +### 机械化优先与 canonical 归宿 + +当前任务范围内能直接落成 red -> green 测试或 checker 的约束,必须优先机械化,不再写重复 +lesson;机械化会扩大任务范围时只给候选,不借学习名义扩权。`cs-keep` 按以下顺序迁移: + +1. 能机械阻止的规则进入测试、checker、lint、类型或 owning skill 的 deterministic helper; +2. 高频必读且稳定的项目事实进入 `attention.md`,并维持不超过 25 条的预算; +3. 同时满足难回退、缺少上下文会令人意外、源于真实取舍的结构性决定进入项目 ADR; +4. 其他稳定方法进入项目既有文档;目标不存在时请 owner 选择,不发明目录; +5. `codestable-eval` 仅标记未来上游候选,本轮不导出、不上传,也不修改 shipped skill。 + +新 owner 写入并验证后,应在同一更新中删除完全重复的 lesson;若由 task skill 发现,则先退役,留待 +显式 `cs-keep` 清理。`cs-keep` 继续执行查重、合并和约 50 条 lesson 预算。 + +### Epic 聚合 + +Epic 子项不得展示或询问晶化候选。每个子项至多把一条去重候选写入既有 Epic 游标的证据区, +不得为此新建 work;该记录只是临时毕业输入,不是项目事实。全部子项完成后,毕业清单统一处理 +lesson、guard、项目文档与 ADR 的归宿,并复用 Epic 最终 owner gate,不新增逐项暂停或确认。 + +### 隐私与跨项目边界 + +不得保存原始对话、原始问答、完整思考过程、候选分支、逐次命中日志或无限 evidence history; +不得扫描 transcript 来补召回率。除上述有界 Epic 游标证据外,未获写入授权的候选只存在于当前 +会话内。 + +本 ADR 只建立项目内 session candidate -> lesson -> canonical owner 的闭环。不创建 +`.codestable/learning/`、全局 lessons、常驻索引、后台 telemetry、集中 runtime 或第九个 skill, +也不自动跨项目、团队或向 CodeStable 上游分享。未来跨项目路径必须另行获得显式授权,并经过脱敏 +packet、fixture、跨模型 eval 与 regression;本 Epic 只保留这一边界,不实现上传或同步。 + +## Consequences + +- task skill 会说明经验如何被当前事实核验、又如何改变行为,避免把检索命中冒充学习效果。 +- lesson 可以随新证据晋升、纠偏和退役,最终迁移到单一机械或文档 owner,减少陈旧知识与双重真相。 +- 普通任务只有高价值候选才出现一次提示,Epic 则聚合到最终 gate,持续学习不会增加常规暂停点。 +- 两种窄维护会让已有 lesson 随任务 diff 一起变化;实现与审查必须核对其证据、状态转换和原子性。 +- 不做 transcript 扫描、后台采集和自动跨项目共享会降低召回率,但同时限制隐私、噪声和权限风险。 +- thin-harness skills 缺少集中 runtime 强制,契约测试与 ADR-003 的 paired cross-session eval 是主要 + 回退防线;未达到跨模型、非 `[underpowered]` 的验收证据时,不得宣称学习迁移有效。 + +## Rejected alternatives + +- **每个任务固定询问是否沉淀**。拒绝:高频仪式会打断主任务,并把弱信号升级成噪声。 +- **自动保存候选、对话或反馈流**。拒绝:扩大隐私与持久化边界,并重新引入本 Epic 明确排除的 + feedback runtime、状态机和后台采集。 +- **把 lesson 当作永久、只增不减的知识库**。拒绝:事实变化后会继续误导,且与测试、ADR、项目 + 文档形成多个 owner。 +- **命中一次或模型自评后直接标记 validated**。拒绝:没有独立后续任务的行为与验证证据,无法 + 区分复用效果和自我确认。 +- **自动同步到全局或其他项目**。拒绝:项目事实、隐私与适用范围尚未经过脱敏和跨模型回归验证。 +- **用更醒目的规则文本代替机械 guard**。拒绝:能由测试、checker、类型或 helper 阻止的错误, + 不应继续依赖 agent 记得阅读。 diff --git a/plugins/codestable/skills/cs-epic/SKILL.md b/plugins/codestable/skills/cs-epic/SKILL.md index 7b460c8..759143d 100644 --- a/plugins/codestable/skills/cs-epic/SKILL.md +++ b/plugins/codestable/skills/cs-epic/SKILL.md @@ -18,6 +18,35 @@ argument-hint: "[大需求描述]" - handoff 只用于起草 proposed 永久 Epic 文档,不替代 fresh design review、批准 hash 或第一道 owner gate。 - 澄清需求:只问会改变拆解方向的问题(目标边界、优先级、验收口径),一次最多 3 个,形成共识即停。 +## 持续学习 + +检索到 lesson 后先做 read-repair。只有 scope 符合、未退役、经当前代码/测试/canonical 文档核实, +并真实改变计划或验证的条目才算有效命中;按 +`经验命中:{path}({status});核验:{fact};影响:{plan_or_check}` 报告。`retired` 不应用; +`observed` / `validated` 先核实再用;旧 lesson 缺 `status` 按 `observed` 读取,不批量迁移。只是相关 +但没有改变行为时不制造复用证据;当前事实明确反证时立即停止应用,证据不足时不猜。 + +任务内只在内存保留最多 3 条候选,按新证据替换低价值项,不暂停或询问。强信号只包括:owner +纠正实际改变方案/代码/术语/验证;可复现证据推翻根因;同一路径失败两次后更换假设; +blocking/important finding 暴露未编码不变量;新 red -> green 捕获可复发失败;lesson 真实改变本次行为 +或被反证;重复 workaround;方法显著降低重试、成本或风险。 + +候选还必须同时有可追溯证据、能写成未来动作、适用于本次精确 diff 之外、且没有现成 canonical +owner。网络波动、拼写、泛化口号、活动记录,以及已被机械 owner 完整覆盖的事实直接丢弃。 + +创建、改写规则/scope、晋升、删除与跨项目反馈仍须用户显式授权。为不中断 read-repair,仅对已有且 +有效命中的 lesson 开放两种窄维护:`observed -> validated` 仅在独立后续任务确实采用并验证成功时 +发生,只补一次代表性证据;`observed|validated -> retired` 仅在当前仓库事实直接反证或发现已有 +canonical owner 时发生,只写原因与替代/反证指针。窄维护不新建事实、不改规则、不扩 scope、不新增 +gate,随当次代码、证据和 +游标进入同一语义原子 milestone;稳定 validated 命中不写文件。需要改写结论或证据不足时只给 +候选,新结论不得通过复活 retired 条目获得 validated 身份;窄维护必须在最终报告列出文件变化。 + +当前任务范围内能直接落成 red -> green 测试/checker 的约束优先机械化,不另写重复 lesson;会扩大 +范围时只给候选。用户已明确说“记住 / 更新 / 退役”时,同轮按 `cs-keep` 处理,不重复确认。Epic +子项不展示、不询问;每个子项至多把一条去重候选写入既有游标证据区,使用 `晶化候选:{rule}` +marker,最终毕业清单一次处理并复用最终 owner gate。 + ## 双层 Epic 文档 Epic 天然跨会话,但稳定上下文和活动状态不得混写: @@ -90,4 +119,3 @@ owner 确认 proposed 文档与上述策略后,主流程机械置 `active`, - owner 接受后先把最终范围、关键决策、交付索引、整体验收、遗留风险与毕业清单写入永久 Epic,再用终态更新置 `accepted` 并移除 `work` 指针。稳定产品契约进 canonical requirement/项目文档,结构性决策进 ADR,经验进 lessons;目标位置不存在时请 owner 选择,确定前结论留在永久 Epic。 - 终态 `accepted` / `superseded` / `cancelled` 是不可恢复执行的持久信号。无论中断时还剩 work 指针、游标或所属子项 work,都从仓库事实幂等续做:补齐终态记录与毕业清单、移除指针、删除 Epic 游标,再按 frontmatter `epic:` 清理全部子项 work;不得恢复执行或创建重复 Epic,永久 Epic 文档不得删除。 - 不恢复 `cs-goal` 入口、goal package、`state.yaml`、逐轮 iteration 报告或 legacy runtime gate;目标契约、恢复游标、owner gate 和终态验收都由上述双层文档承担。 -- 本轮若踩坑或被纠偏,推荐用 cs-keep 沉淀一条;用户拒绝即跳过。 diff --git a/plugins/codestable/skills/cs-feat/SKILL.md b/plugins/codestable/skills/cs-feat/SKILL.md index fbd6d43..5255c8d 100644 --- a/plugins/codestable/skills/cs-feat/SKILL.md +++ b/plugins/codestable/skills/cs-feat/SKILL.md @@ -18,6 +18,35 @@ argument-hint: "[功能描述]" - 动手前先定归属:这能力属于哪里、沿用现有词汇叫什么——不丢进最近的文件、不起新同义词。结构与取舍拿不准时读 `references/code-design.md` 与 `references/economy.md`(最小充分 ≠ 最小 diff;有界简化必须记上限、触发与方向)。 - 对照检查:目标、现场上下文、边界与取舍、证据要求、验收标准。缺少会改变实现方向的事实时先问,一次最多 3 个问题,形成可执行共识即停;不问不影响方向的细节。 +## 持续学习 + +检索到 lesson 后先做 read-repair。只有 scope 符合、未退役、经当前代码/测试/canonical 文档核实, +并真实改变计划或验证的条目才算有效命中;按 +`经验命中:{path}({status});核验:{fact};影响:{plan_or_check}` 报告。`retired` 不应用; +`observed` / `validated` 先核实再用;旧 lesson 缺 `status` 按 `observed` 读取,不批量迁移。只是相关 +但没有改变行为时不制造复用证据;当前事实明确反证时立即停止应用,证据不足时不猜。 + +任务内只在内存保留最多 3 条候选,按新证据替换低价值项,不暂停或询问。强信号只包括:owner +纠正实际改变方案/代码/术语/验证;可复现证据推翻根因;同一路径失败两次后更换假设; +blocking/important finding 暴露未编码不变量;新 red -> green 捕获可复发失败;lesson 真实改变本次行为 +或被反证;重复 workaround;方法显著降低重试、成本或风险。 + +候选还必须同时有可追溯证据、能写成未来动作、适用于本次精确 diff 之外、且没有现成 canonical +owner。网络波动、拼写、泛化口号、活动记录,以及已被机械 owner 完整覆盖的事实直接丢弃。 + +创建、改写规则/scope、晋升、删除与跨项目反馈仍须用户显式授权。为不中断 read-repair,仅对已有且 +有效命中的 lesson 开放两种窄维护:`observed -> validated` 仅在独立后续任务确实采用并验证成功时 +发生,只补一次代表性证据;`observed|validated -> retired` 仅在当前仓库事实直接反证或发现已有 +canonical owner 时发生,只写原因与替代/反证指针。窄维护不新建事实、不改规则、不扩 scope、不新增 +gate,随当次代码、证据和 +游标进入同一语义原子 milestone;稳定 validated 命中不写文件。需要改写结论或证据不足时只给 +候选,新结论不得通过复活 retired 条目获得 validated 身份;窄维护必须在最终报告列出文件变化。 + +当前任务范围内能直接落成 red -> green 测试/checker 的约束优先机械化,不另写重复 lesson;会扩大 +范围时只给候选。用户已明确说“记住 / 更新 / 退役”时,同轮按 `cs-keep` 处理,不重复确认。普通任务 +只在强信号成立时展示最高价值一条,首行固定 `晶化候选:{rule}`,并给出证据、范围和建议归宿;无 +强信号完全不显示模板,没有记忆写入授权时不落盘。 + ## 默认执行 理解相关事实 → 实现 → 运行相称的验证 → 交付结果。普通任务不生成 CodeStable 产物,git diff、测试输出和交付说明就是证据。 @@ -50,4 +79,3 @@ argument-hint: "[功能描述]" - 报告:做了什么、改动文件、验证结果、遗留事项。 - 高风险任务的 work 文档在设计对齐时已建立;其余任务需要跨会话继续、多人交接或用户要求留痕时补建 `.codestable/work/feat-{slug}.md`(work 文档一律带类型前缀 feat- / issue- / refactor- / epic-,整理时按前缀分流去向)。work 文档含目标 / 现场 / 边界 / 证据 / 验收 / 状态与未决六节,随进展更新("状态与未决"记录进度与待用户确认项,供跨会话恢复);完成后先在最终报告列**毕业清单**——哪条结论进了哪个项目文档、沉了哪条 lesson,无可毕业内容则明说——然后才删除 work 文档;不列清单不得删。毕业目标位置不存在时不擅自发明目录:清单中给出建议落点请用户拍板,**拍板前 work 文档保留不删**。用户要求留档则保留。 - 属于某个 Epic 的子功能时:独立子功能 work 文档的 frontmatter 标 `epic: {epic-slug}`;日常进展和完成状态只更新 Epic work 游标中对应稳定 ID 的进度与证据指针。永久 Epic 文档在 `active` 期间保持冻结,不因日常进度或子功能 work 回链而修改;需要改变子项定义、依赖或验收时交 `cs-epic` 走边界重确认。 -- 本轮若踩坑或被纠偏,推荐用 cs-keep 沉淀一条;用户拒绝即跳过。 diff --git a/plugins/codestable/skills/cs-issue/SKILL.md b/plugins/codestable/skills/cs-issue/SKILL.md index c779982..aa5d108 100644 --- a/plugins/codestable/skills/cs-issue/SKILL.md +++ b/plugins/codestable/skills/cs-issue/SKILL.md @@ -16,6 +16,35 @@ argument-hint: "[问题描述]" - 对照检查:目标(期望行为)、现场(复现条件与环境)、边界(哪些不能动)、验收(怎么算修好)。缺少会改变修复方向的事实时先问用户,一次最多 3 个问题,形成共识即停。 - 诉求其实是新增能力而不是坏掉的行为时,转 `cs-feat`,不在 issue 里偷做。 +## 持续学习 + +检索到 lesson 后先做 read-repair。只有 scope 符合、未退役、经当前代码/测试/canonical 文档核实, +并真实改变计划或验证的条目才算有效命中;按 +`经验命中:{path}({status});核验:{fact};影响:{plan_or_check}` 报告。`retired` 不应用; +`observed` / `validated` 先核实再用;旧 lesson 缺 `status` 按 `observed` 读取,不批量迁移。只是相关 +但没有改变行为时不制造复用证据;当前事实明确反证时立即停止应用,证据不足时不猜。 + +任务内只在内存保留最多 3 条候选,按新证据替换低价值项,不暂停或询问。强信号只包括:owner +纠正实际改变方案/代码/术语/验证;可复现证据推翻根因;同一路径失败两次后更换假设; +blocking/important finding 暴露未编码不变量;新 red -> green 捕获可复发失败;lesson 真实改变本次行为 +或被反证;重复 workaround;方法显著降低重试、成本或风险。 + +候选还必须同时有可追溯证据、能写成未来动作、适用于本次精确 diff 之外、且没有现成 canonical +owner。网络波动、拼写、泛化口号、活动记录,以及已被机械 owner 完整覆盖的事实直接丢弃。 + +创建、改写规则/scope、晋升、删除与跨项目反馈仍须用户显式授权。为不中断 read-repair,仅对已有且 +有效命中的 lesson 开放两种窄维护:`observed -> validated` 仅在独立后续任务确实采用并验证成功时 +发生,只补一次代表性证据;`observed|validated -> retired` 仅在当前仓库事实直接反证或发现已有 +canonical owner 时发生,只写原因与替代/反证指针。窄维护不新建事实、不改规则、不扩 scope、不新增 +gate,随当次代码、证据和 +游标进入同一语义原子 milestone;稳定 validated 命中不写文件。需要改写结论或证据不足时只给 +候选,新结论不得通过复活 retired 条目获得 validated 身份;窄维护必须在最终报告列出文件变化。 + +当前任务范围内能直接落成 red -> green 测试/checker 的约束优先机械化,不另写重复 lesson;会扩大 +范围时只给候选。用户已明确说“记住 / 更新 / 退役”时,同轮按 `cs-keep` 处理,不重复确认。普通任务 +只在强信号成立时展示最高价值一条,首行固定 `晶化候选:{rule}`,并给出证据、范围和建议归宿;无 +强信号完全不显示模板,没有记忆写入授权时不落盘。 + ## 硬门槛 - **没有稳定、快速、能明确变红的验证,不许猜根因、不许改代码。** 优先失败测试;无法自动化时与用户确认一个手工复现步骤。 @@ -38,4 +67,3 @@ argument-hint: "[问题描述]" - reviewer 创建后绑定该运行并记录 run identity:目标有效、能力仍满足,且 reviewer 状态为 running,或 `Awaiting` 携带可查询的同一 run identity 且查询仍为活动态时为健康;状态健康时等待终态报告,不因后来发现更优创建方式而取消、重复创建或并行补发。仅在运行明确失败或终止无报告、idle / `Awaiting` 且无可恢复 run identity、能力不满足或目标失效时,本轮失败且不计轮次;不得盲目重发,先检查 task packet 与 agent 状态,再决定一次有界重试、更换创建方式或交用户。 - 报告:根因一句话、改动文件、验证结果。 - 需要跨会话继续时写 `.codestable/work/issue-{slug}.md`(目标 / 现场 / 边界 / 证据 / 验收 / 状态与未决六节;work 文档一律带类型前缀)。完成后先在报告列毕业去向(结论进哪、lesson 沉哪,或明说无可毕业)再删除;目标位置不存在时在清单中建议落点请用户拍板,拍板前不删。用户要求留档则保留。 -- 本轮若踩了新坑或被用户纠偏,推荐用 cs-keep 沉淀一条;用户拒绝即跳过。 diff --git a/plugins/codestable/skills/cs-refactor/SKILL.md b/plugins/codestable/skills/cs-refactor/SKILL.md index c092533..735fe4e 100644 --- a/plugins/codestable/skills/cs-refactor/SKILL.md +++ b/plugins/codestable/skills/cs-refactor/SKILL.md @@ -15,6 +15,35 @@ argument-hint: "[重构目标]" - 先确认诉求真是行为不变:一旦包含"顺便支持 X / 改成 Y",把那部分拆出去转 `cs-feat` 或 `cs-issue`,不夹带。 - 结构好坏用**深度**衡量:小接口承载大行为是深,接口和实现一样复杂是浅;重构应让调用方用更少认知换更多能力,不为"看起来干净"搬家,不把模块越拆越碎。 +## 持续学习 + +检索到 lesson 后先做 read-repair。只有 scope 符合、未退役、经当前代码/测试/canonical 文档核实, +并真实改变计划或验证的条目才算有效命中;按 +`经验命中:{path}({status});核验:{fact};影响:{plan_or_check}` 报告。`retired` 不应用; +`observed` / `validated` 先核实再用;旧 lesson 缺 `status` 按 `observed` 读取,不批量迁移。只是相关 +但没有改变行为时不制造复用证据;当前事实明确反证时立即停止应用,证据不足时不猜。 + +任务内只在内存保留最多 3 条候选,按新证据替换低价值项,不暂停或询问。强信号只包括:owner +纠正实际改变方案/代码/术语/验证;可复现证据推翻根因;同一路径失败两次后更换假设; +blocking/important finding 暴露未编码不变量;新 red -> green 捕获可复发失败;lesson 真实改变本次行为 +或被反证;重复 workaround;方法显著降低重试、成本或风险。 + +候选还必须同时有可追溯证据、能写成未来动作、适用于本次精确 diff 之外、且没有现成 canonical +owner。网络波动、拼写、泛化口号、活动记录,以及已被机械 owner 完整覆盖的事实直接丢弃。 + +创建、改写规则/scope、晋升、删除与跨项目反馈仍须用户显式授权。为不中断 read-repair,仅对已有且 +有效命中的 lesson 开放两种窄维护:`observed -> validated` 仅在独立后续任务确实采用并验证成功时 +发生,只补一次代表性证据;`observed|validated -> retired` 仅在当前仓库事实直接反证或发现已有 +canonical owner 时发生,只写原因与替代/反证指针。窄维护不新建事实、不改规则、不扩 scope、不新增 +gate,随当次代码、证据和 +游标进入同一语义原子 milestone;稳定 validated 命中不写文件。需要改写结论或证据不足时只给 +候选,新结论不得通过复活 retired 条目获得 validated 身份;窄维护必须在最终报告列出文件变化。 + +当前任务范围内能直接落成 red -> green 测试/checker 的约束优先机械化,不另写重复 lesson;会扩大 +范围时只给候选。用户已明确说“记住 / 更新 / 退役”时,同轮按 `cs-keep` 处理,不重复确认。普通任务 +只在强信号成立时展示最高价值一条,首行固定 `晶化候选:{rule}`,并给出证据、范围和建议归宿;无 +强信号完全不显示模板,没有记忆写入授权时不落盘。 + ## 硬门槛 - **先有能自证等价的验证,再动代码**:覆盖目标行为的测试、类型检查或可对照的输出基线。没有就先补验证或与用户确认等价判据,不许裸改。 @@ -36,4 +65,3 @@ argument-hint: "[重构目标]" - 报告:改了什么结构、等价性证据(验证输出)、遗留事项。 - 需要跨会话继续时写 `.codestable/work/refactor-{slug}.md`(目标 / 现场 / 边界 / 证据 / 验收 / 状态与未决六节;work 文档一律带类型前缀)。完成后先在报告列毕业去向(结论进哪、lesson 沉哪,或明说无可毕业)再删除;目标位置不存在时在清单中建议落点请用户拍板,拍板前不删。用户要求留档则保留。 -- 本轮若踩坑或被纠偏,推荐用 cs-keep 沉淀一条;用户拒绝即跳过。 diff --git a/tests/test_skill_contracts.py b/tests/test_skill_contracts.py index 8e7cae9..318c895 100644 --- a/tests/test_skill_contracts.py +++ b/tests/test_skill_contracts.py @@ -13,6 +13,7 @@ SKILLS = ROOT / "plugins/codestable/skills" LOCAL_SKILLS = ROOT / ".claude/skills" TASK_SKILLS = ("cs-feat", "cs-issue", "cs-refactor", "cs-epic") +ORDINARY_TASK_SKILLS = ("cs-feat", "cs-issue", "cs-refactor") LEGACY_KNOWLEDGE_DIRS = { ".codestable/roadmap/", ".codestable/features/", @@ -25,6 +26,54 @@ LEGACY_KNOWLEDGE_DIRS = { ".codestable/feedback/", } +LESSON_READ_REPAIR_CONTRACT = { + "经验命中:{path}({status});核验:{fact};影响:{plan_or_check}", + "旧 lesson 缺 `status` 按 `observed` 读取", + "`retired` 不应用", + "`observed` / `validated` 先核实再用", + "只是相关但没有改变行为时不制造复用证据", + "当前事实明确反证时立即停止应用", + "证据不足时不猜", +} + +CRYSTALLIZATION_SIGNAL_CONTRACT = { + "任务内只在内存保留最多 3 条候选", + "不暂停或询问", + "owner 纠正实际改变方案/代码/术语/验证", + "可复现证据推翻根因", + "同一路径失败两次后更换假设", + "blocking/important finding 暴露未编码不变量", + "新 red -> green 捕获可复发失败", + "lesson 真实改变本次行为或被反证", + "重复 workaround", + "方法显著降低重试、成本或风险", + "可追溯证据", + "能写成未来动作", + "本次精确 diff 之外", + "没有现成 canonical owner", + "网络波动、拼写、泛化口号、活动记录", + "已被机械 owner 完整覆盖", +} + +NARROW_LESSON_MAINTENANCE_CONTRACT = { + "仅对已有且有效命中的 lesson", + "`observed -> validated`", + "独立后续任务确实采用并验证成功", + "只补一次代表性证据", + "`observed|validated -> retired`", + "当前仓库事实直接反证", + "发现已有 canonical owner", + "只写原因与替代/反证指针", + "不新建事实", + "不改规则", + "不扩 scope", + "不新增 gate", + "稳定 validated 命中不写文件", + "需要改写结论或证据不足时只给候选", + "新结论不得通过复活 retired 条目", + "最终报告列出文件变化", +} + THIN_SKILL_SAFETY_INVARIANTS = { "cs": ( "同轮直转", @@ -103,6 +152,10 @@ def _read_skill(path: Path) -> tuple[dict[str, object], str]: return yaml.safe_load(frontmatter), body +def _contains_contract(text: str, anchor: str) -> bool: + return "".join(anchor.split()) in "".join(text.split()) + + def test_active_skills_do_not_use_legacy_frontmatter_contracts() -> None: for root in (SKILLS, LOCAL_SKILLS): for path in sorted(root.glob("*/SKILL.md")): @@ -213,6 +266,96 @@ def test_task_skills_retrieve_legacy_knowledge_read_only() -> None: assert "批量迁移" in kickoff, skill_name +def test_task_skills_share_the_complete_lesson_read_repair_contract() -> None: + signatures = {} + for skill_name in TASK_SKILLS: + _, body = _read_skill(SKILLS / skill_name / "SKILL.md") + signatures[skill_name] = { + anchor + for anchor in LESSON_READ_REPAIR_CONTRACT + if _contains_contract(body, anchor) + } + + assert len({frozenset(signature) for signature in signatures.values()}) == 1 + for skill_name, signature in signatures.items(): + assert signature == LESSON_READ_REPAIR_CONTRACT, skill_name + + +def test_task_skills_share_the_bounded_strong_signal_contract() -> None: + signatures = {} + for skill_name in TASK_SKILLS: + _, body = _read_skill(SKILLS / skill_name / "SKILL.md") + signatures[skill_name] = { + anchor + for anchor in CRYSTALLIZATION_SIGNAL_CONTRACT + if _contains_contract(body, anchor) + } + + assert len({frozenset(signature) for signature in signatures.values()}) == 1 + for skill_name, signature in signatures.items(): + assert signature == CRYSTALLIZATION_SIGNAL_CONTRACT, skill_name + + +def test_crystallization_closing_is_quiet_and_prefers_mechanical_guards() -> None: + for skill_name in TASK_SKILLS: + _, body = _read_skill(SKILLS / skill_name / "SKILL.md") + for anchor in ( + "当前任务范围内能直接落成 red -> green 测试/checker", + "优先机械化", + "不另写重复 lesson", + "会扩大范围时只给候选", + ): + assert _contains_contract(body, anchor), skill_name + + for skill_name in ORDINARY_TASK_SKILLS: + _, body = _read_skill(SKILLS / skill_name / "SKILL.md") + for anchor in ( + "普通任务只在强信号成立时展示最高价值一条", + "`晶化候选:{rule}`", + "证据、范围和建议归宿", + "无强信号完全不显示模板", + "没有记忆写入授权时不落盘", + ): + assert _contains_contract(body, anchor), skill_name + + for skill_name in TASK_SKILLS: + _, body = _read_skill(SKILLS / skill_name / "SKILL.md") + for anchor in ( + "用户已明确说“记住 / 更新 / 退役”", + "同轮按 `cs-keep` 处理", + "不重复确认", + ): + assert _contains_contract(body, anchor), skill_name + + _, epic = _read_skill(SKILLS / "cs-epic/SKILL.md") + for anchor in ( + "Epic 子项不展示、不询问", + "每个子项至多把一条去重候选写入既有游标证据区", + "最终毕业清单一次处理", + "复用最终 owner gate", + "不得询问“是否继续下一项”", + "不得把普通子项完成当作终态返回", + ): + assert _contains_contract(epic, anchor) + + +def test_task_skills_allow_only_two_narrow_lesson_maintenance_paths() -> None: + signatures = {} + for skill_name in TASK_SKILLS: + _, body = _read_skill(SKILLS / skill_name / "SKILL.md") + signatures[skill_name] = { + anchor + for anchor in NARROW_LESSON_MAINTENANCE_CONTRACT + if _contains_contract(body, anchor) + } + assert _contains_contract(body, "创建、改写规则/scope、晋升、删除与跨项目反馈仍须") + assert _contains_contract(body, "随当次代码、证据和游标进入同一语义原子 milestone") + + assert len({frozenset(signature) for signature in signatures.values()}) == 1 + for skill_name, signature in signatures.items(): + assert signature == NARROW_LESSON_MAINTENANCE_CONTRACT, skill_name + + def test_keep_never_writes_v1_compound() -> None: _, keep = _read_skill(SKILLS / "cs-keep/SKILL.md") assert "`.codestable/compound/` 是只读历史知识源" in keep diff --git a/tests/test_v2_architecture_contract.py b/tests/test_v2_architecture_contract.py index d1934f4..6912ffb 100644 --- a/tests/test_v2_architecture_contract.py +++ b/tests/test_v2_architecture_contract.py @@ -8,6 +8,7 @@ import yaml ROOT = Path(__file__).resolve().parents[1] +SHIPPED_SKILLS = ROOT / "plugins/codestable/skills" def _frontmatter(path: Path) -> dict[str, object]: @@ -94,3 +95,61 @@ def test_v1_feedback_promoter_is_explicitly_legacy_only() -> None: assert "not a CodeStable v2 production-feedback entry" in promoter assert "仅用于显式导入冻结的 v1" in eval_skill assert "新反馈如何进入 regression 不在当前协议中定义" in eval_skill + + +def test_project_learning_lifecycle_is_an_accepted_narrow_staging_contract() -> None: + adr6 = ROOT / "docs/adr/006-project-learning-lifecycle.md" + + assert adr6.is_file() + assert _frontmatter(adr6)["status"] == "Accepted" + text = adr6.read_text(encoding="utf-8") + for anchor in ( + "在任务中静默观察", + "最多保留 3 条候选", + "read-repair", + "必须优先机械化", + "`observed -> validated`", + "`observed|validated -> retired`", + "显式授权", + "feedback runtime", + "transcript", + "全局 lessons", + "集中 runtime", + "第九个 skill", + "不新增逐项暂停或确认", + ): + assert anchor in text + + +def test_project_learning_does_not_restore_feedback_or_global_runtime_state() -> None: + active_skills = { + path.parent.name for path in SHIPPED_SKILLS.glob("*/SKILL.md") + } + assert active_skills == { + "cs", + "cs-code-review", + "cs-epic", + "cs-feat", + "cs-issue", + "cs-keep", + "cs-onboard", + "cs-refactor", + "cs-review", + } + + for path in ( + SHIPPED_SKILLS / "cs-feedback", + ROOT / ".codestable/learning", + ROOT / ".codestable/global-lessons", + ROOT / ".codestable/state.yaml", + ROOT / ".codestable/session-state.yaml", + ): + assert not path.exists(), path + + runtime_artifacts = { + path.relative_to(SHIPPED_SKILLS).as_posix().lower() + for path in SHIPPED_SKILLS.rglob("*") + if path.is_file() + and ("transcript" in path.name.lower() or path.name == "state.yaml") + } + assert runtime_artifacts == set()