@adamchanadam/cer-workflow 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/bin/cer-workflow.mjs +292 -0
- package/package.json +56 -0
- package/skills/cer-workflow/SKILL.md +63 -0
- package/skills/cer-workflow/VERSION +1 -0
- package/skills/cer-workflow/agents/openai.yaml +6 -0
- package/skills/cer-workflow/references/core-runtime.md +479 -0
- package/skills/cer-workflow/references/parallel-producers.md +159 -0
- package/skills/cer-workflow/references/roadmap.md +182 -0
- package/skills/cer-workflow/references/uat.md +605 -0
- package/skills/cer-workflow/scripts/validate_cer_skill.py +1887 -0
- package/skills/cer-workflow-en/SKILL.md +71 -0
- package/skills/cer-workflow-en/VERSION +1 -0
- package/skills/cer-workflow-en/agents/openai.yaml +6 -0
- package/skills/cer-workflow-en/references/core-runtime.md +602 -0
- package/skills/cer-workflow-en/references/parallel-producers.md +174 -0
- package/skills/cer-workflow-en/references/roadmap.md +198 -0
- package/skills/cer-workflow-en/references/uat.md +718 -0
- package/skills/cer-workflow-en/scripts/validate_cer_skill.py +1889 -0
|
@@ -0,0 +1,1887 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Validate the installed CER skill package using only the standard library."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import copy
|
|
8
|
+
import re
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path, PurePosixPath
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
EXPECTED_FILES = {
|
|
14
|
+
"SKILL.md",
|
|
15
|
+
"VERSION",
|
|
16
|
+
"agents/openai.yaml",
|
|
17
|
+
"references/core-runtime.md",
|
|
18
|
+
"references/roadmap.md",
|
|
19
|
+
"references/uat.md",
|
|
20
|
+
"references/parallel-producers.md",
|
|
21
|
+
"scripts/validate_cer_skill.py",
|
|
22
|
+
}
|
|
23
|
+
TEXT_FILES = EXPECTED_FILES - {"VERSION"}
|
|
24
|
+
SEMVER_RE = re.compile(r"(?<![0-9])\d+\.\d+\.\d+(?![0-9])")
|
|
25
|
+
OWNER_MARKER = "<!-- cer-parallel-producers-owner -->"
|
|
26
|
+
UNEXPECTED_FAILURE_OWNER_MARKER = "<!-- cer-unexpected-failure-gate-owner -->"
|
|
27
|
+
TRUTH_SOURCE_INTAKE_OWNER_MARKER = "<!-- cer-truth-source-intake-gate-owner -->"
|
|
28
|
+
DRIFT_CHECKPOINT_OWNER_MARKER = "<!-- cer-controller-drift-checkpoint-owner -->"
|
|
29
|
+
RESULT_DISPOSITION_OWNER_MARKER = "<!-- cer-result-disposition-gate-owner -->"
|
|
30
|
+
EXECUTION_PROFILE_OWNER_MARKER = "<!-- cer-execution-profile-gate-owner -->"
|
|
31
|
+
PUBLIC_RUNTIME_LANGUAGE_OWNER_MARKER = "<!-- cer-public-runtime-language-boundary-owner -->"
|
|
32
|
+
EXPECTED_DEFAULT_PROMPT = (
|
|
33
|
+
"使用 $cer-workflow 以唯一 writer 執行這項工作;按風險建立 fresh Reviewer,"
|
|
34
|
+
"必要時在內部自動加速,無需額外設定。"
|
|
35
|
+
)
|
|
36
|
+
FORMAL_COMMANDS = {
|
|
37
|
+
"/CER-auto",
|
|
38
|
+
"/CER-start",
|
|
39
|
+
"/CER-stop",
|
|
40
|
+
"/CER-close",
|
|
41
|
+
"/CER-status",
|
|
42
|
+
"/CER-help",
|
|
43
|
+
}
|
|
44
|
+
MAX_ROUTER_BYTES = 6000
|
|
45
|
+
|
|
46
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS = {
|
|
47
|
+
"frontmatter": (
|
|
48
|
+
"只在使用者明確帶 CER 的指令或同等語意時使用",
|
|
49
|
+
"/CER-auto",
|
|
50
|
+
"單獨「開工/收工」不是 CER 觸發",
|
|
51
|
+
),
|
|
52
|
+
"auto_row": ("路線裁決前不成立 C", "選到 CER 工作法才進完整 C/E/R,R 按風險決定", "Remote 首版不支援"),
|
|
53
|
+
"start_row": ("單獨 `開工` 不啟動 CER",),
|
|
54
|
+
"close_row": ("單獨 `收工` 不觸發 CER close",),
|
|
55
|
+
"auto_help_template": (
|
|
56
|
+
"顯示本表與 `/CER-auto` 任務寫法",
|
|
57
|
+
"目標+限制/不可做+成功驗收+權威來源/授權邊界",
|
|
58
|
+
"按使用者情境生成,不固定行業",
|
|
59
|
+
"如要作正式決策、付款、發布或外部承諾,先停下改用 CER 工作法",
|
|
60
|
+
),
|
|
61
|
+
"startup_owner": ("單獨 `開工` 屬於目標 workspace 既有治理,不是 CER trigger",),
|
|
62
|
+
"stop_owner": ("單獨 `收工` 屬於目標 workspace 既有治理,不映射為 CER stop 或 close",),
|
|
63
|
+
"uat_install_start": (
|
|
64
|
+
"`/CER-start`、`CER 啟動`、`CER 開始`、`CER 開工` 正常觸發 CER",
|
|
65
|
+
"單獨 `開工` 不觸發 CER",
|
|
66
|
+
),
|
|
67
|
+
"uat_install_auto": (
|
|
68
|
+
"`/CER-auto`、`CER 自適應` 正常觸發本地執行強度閘門",
|
|
69
|
+
"路線裁決前不成立 C",
|
|
70
|
+
),
|
|
71
|
+
"uat_install_close": (
|
|
72
|
+
"`/CER-close`、`CER 收工`、`CER 關閉`、`關閉 CER` 正常觸發 CER close",
|
|
73
|
+
"單獨 `收工` 不觸發 CER close,也不映射為 `/CER-stop`",
|
|
74
|
+
),
|
|
75
|
+
"uat_failure": ("單獨 `開工` 啟動 CER,或單獨 `收工` 觸發 CER close/stop",),
|
|
76
|
+
"uat_failure_auto": ("`/CER-auto` 在路線裁決前自稱 C",),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
EXECUTION_PROFILE_REQUIREMENTS = {
|
|
80
|
+
"sole_owner": "本節是 `/CER-auto` 的唯一 runtime owner",
|
|
81
|
+
"local_only": "首版只支援本地使用者 task;Remote `/CER-auto` 未支援",
|
|
82
|
+
"pre_identity": "入口 task 在路線裁決前不是 C",
|
|
83
|
+
"start_unchanged": "明示 `/CER-start` 的語義保持不變",
|
|
84
|
+
"selective_read": "先只讀本節及裁決所需的使用者要求與目標專案真源",
|
|
85
|
+
"single_read_bundle": "必須在同一次有界讀取中取得,不得只為 selector 另開讀取往返",
|
|
86
|
+
"minimum_strength": "以最低足夠協作強度選 ordinary execution、Goal、CER 工作法或 blocked",
|
|
87
|
+
"route_lines": "路線:CER 工作法 — <需要 CER 的原因與停點>",
|
|
88
|
+
"blocked_route_line": "路線:blocked — <缺少的權威/安全/驗收條件>",
|
|
89
|
+
"ordinary_boundary": "ordinary execution 不啟動 CER、不自稱 C/E/R、不顯示小熊卡,並停止載入其他 CER references",
|
|
90
|
+
"goal_boundary": "Goal 不提供 CER 的唯一 writer、C/E/R 身份或 authority owner",
|
|
91
|
+
"cer_boundary": "選 CER 工作法時才在需要 CER 的停點完整讀取本檔及 `roadmap.md`",
|
|
92
|
+
"decision_basis": "路線按下一步的後果、不確定性、可回復性及 owner 清晰度裁決",
|
|
93
|
+
"source_evidence_boundary": "source count、schema、hash 或 receipt 都不能代替 authority evidence",
|
|
94
|
+
"goal_route": "終點、驗證 loop、可停止條件和已知權威來源清楚",
|
|
95
|
+
"cer_gated_promotion": "formal data、model input、report paragraph、decision gate、handoff truth、release/readiness claim、public/external claim",
|
|
96
|
+
"blocked_boundary": "Goal 能力且無安全 fallback",
|
|
97
|
+
"bounded_reconciliation": "則不因觸及持久狀態而自動選 CER 工作法",
|
|
98
|
+
"cost_boundary": "成本永遠不能繞過安全、權威、持久化、外部授權、Reviewer 或目標 release owner",
|
|
99
|
+
"recheck_boundaries": "只在四個實質邊界重判:使用者要求、權威或後果改變;階段邊界;result disposition 改變承接、進度或權威效力;外部、公開、不可逆或其他高後果操作前",
|
|
100
|
+
"no_step_recheck": "不得在每個小步重判;token 壓力本身不是升降理由",
|
|
101
|
+
"existing_owners": "R 是否建立仍由既有 Reviewer owner 按風險決定,release assurance 仍由目標專案既有 release owner 決定",
|
|
102
|
+
"safe_step_down": "沒有 active batch,E1 已停止寫入,結果已讀回並完成 result disposition,必要持久化已回寫讀回,而且沒有 truth conflict",
|
|
103
|
+
"not_stop_close": "這是路線轉換,不是 `/CER-stop` 或 `/CER-close`",
|
|
104
|
+
"safe_step_up": "其草稿、診斷、Goal 輸出或普通 subagent 輸出預設只作 working material",
|
|
105
|
+
"baseline_readback": "E1 在首次寫入前重讀 workspace baseline",
|
|
106
|
+
"conditional_checkpoint": "只有轉換會跨 task、session 或 context,或會承接實質 artifact、裁決或風險時,才保存一個短、非權威的 route-transition checkpoint",
|
|
107
|
+
"no_new_structure": "不建新檔、schema、YAML 或 registry",
|
|
108
|
+
"checkpoint_block": "必要讀回缺失或互相矛盾時,下一次寫入或派工保持 blocked",
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
EXECUTION_PROFILE_UAT_REQUIREMENTS = {
|
|
112
|
+
"ordinary_route": "權威清楚、單一 writer、可回復、無外部副作用且既有驗收足夠的低風險任務",
|
|
113
|
+
"single_read_bundle": "以同一次有界讀取取得,不增加 selector 專用讀取往返",
|
|
114
|
+
"ordinary_subagent": "該 subagent 不取得正式 E/R 身份、ready/result 或 Reviewer 效力",
|
|
115
|
+
"bounded_reconciliation": "只剩同一 workspace、單一 writer、本地可回復的 metadata 對帳",
|
|
116
|
+
"bounded_reconciliation_limit": "不得把未解決的真相衝突改名為「機械修正」以降級",
|
|
117
|
+
"goal_route": "終點、驗證 loop、可停止條件和已知權威來源清楚",
|
|
118
|
+
"goal_no_promotion": "尚未要求把成果採納為正式資料、模型輸入、報告、decision gate、handoff truth、release/readiness claim 或 public/external claim",
|
|
119
|
+
"goal_vague": "不直接進 Goal",
|
|
120
|
+
"cer_route": "只在該需要 CER 的停點輸出一行 `路線:CER 工作法 — <需要 CER 的原因與停點>`",
|
|
121
|
+
"blocked_route": "輸出一行 `路線:blocked — <缺少的權威/安全/驗收條件>`",
|
|
122
|
+
"small_high_consequence": "只有一行文字但涉及刪除、發布、正式採用裁決或高後果決策時,必須選 CER 工作法或 blocked",
|
|
123
|
+
"false_evidence": "source count、schema、hash 或 receipt 當成 authority evidence",
|
|
124
|
+
"no_09_runtime": "`CER_docs/09` 被引用為 runtime routing authority",
|
|
125
|
+
"mixed_promotion": "只有後續需要 CER 的停點才選 CER 工作法",
|
|
126
|
+
"goal_unavailable_fallback": "Goal 不可用但 bounded ordinary 可安全完成時,不自動 blocked",
|
|
127
|
+
"external_background": "external claim 只作背景引用且不作正式聲稱時,不自動升格到 CER 工作法",
|
|
128
|
+
"start_unchanged": "明示 `/CER-start` 不經自適應降級",
|
|
129
|
+
"remote_unsupported": "Remote `/CER-auto` 在首版必須停下並報 unsupported",
|
|
130
|
+
"bounded_recheck": "普通小步和 token 壓力不觸發重判",
|
|
131
|
+
"owner_boundary": "自適應路由不能固定建立、固定省略或取代兩者",
|
|
132
|
+
"safe_step_down": "沒有 active batch、E1 停止寫入、結果讀回及 result disposition、必要持久化讀回和無 truth conflict 全部成立",
|
|
133
|
+
"safe_step_up": "普通草稿、診斷、Goal 輸出和 subagent 輸出只作 working material",
|
|
134
|
+
"startup_order": "合格 E1 零寫入 `ready` 尚未 direct-push 及讀回前,不顯示成功啟動卡、不派正式批次",
|
|
135
|
+
"conditional_checkpoint": "同一 task 且沒有實質 artifact、裁決或風險承接的路線轉換不建立 checkpoint",
|
|
136
|
+
"checkpoint_block": "必要讀回缺失或衝突時,下一次寫入或派工保持 blocked",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
EXECUTION_PROFILE_FORBIDDEN = {
|
|
140
|
+
"start_downgrade": "`/CER-start` 可自動降為 ordinary execution",
|
|
141
|
+
"pre_identity": "`/CER-auto` 路線裁決前就是 C",
|
|
142
|
+
"remote_supported": "Remote `/CER-auto` 已支援",
|
|
143
|
+
"file_count": "檔案數多就必須選 CER 工作法",
|
|
144
|
+
"token_bypass": "為了節省 token 可略過安全或權威 owner",
|
|
145
|
+
"fixed_reviewer": "`/CER-auto` 每次都建立 Reviewer",
|
|
146
|
+
"unsafe_step_down": "active batch 尚未結束也可降回 ordinary execution",
|
|
147
|
+
"draft_authority": "ordinary 草稿自動成為 authoritative_input",
|
|
148
|
+
"goal_authority": "Goal 自動成為 CER authority owner",
|
|
149
|
+
"false_evidence": "source count、schema、hash 或 receipt 足以證明權威",
|
|
150
|
+
"cite_09_runtime": "`CER_docs/09` 可作 `/CER-auto` runtime routing authority",
|
|
151
|
+
"whole_phase_cer": "後面有需要 CER 的停點所以整段任務都必須 CER",
|
|
152
|
+
"goal_unavailable_block": "Goal 不可用時必須 blocked,即使 bounded ordinary 可以安全完成",
|
|
153
|
+
"background_claim_gate": "external claim 只作背景引用也必須 CER 工作法",
|
|
154
|
+
"fixed_checkpoint": "每次路線切換都建立固定 YAML checkpoint",
|
|
155
|
+
"persistent_file_always_cer": "持久狀態檔案一律建立 C/E/R",
|
|
156
|
+
"unsafe_read_bundle": "即使權限或範圍不同也必須合併讀取",
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
PUBLIC_RUNTIME_LANGUAGE_REQUIREMENTS = {
|
|
160
|
+
"boundary_id": "`PUBLIC_SKILL_BOUNDARY_V1`",
|
|
161
|
+
"canonical_source": "英文 `cer-workflow-en` package 作 canonical authoring/validation source",
|
|
162
|
+
"compatibility_mirror": "相容入口與用戶語言鏡像",
|
|
163
|
+
"no_divergent_behavior": "不得另定或覆蓋 CER 行為",
|
|
164
|
+
"not_english_only": "英文 canonical runtime 不等於英文-only 操作",
|
|
165
|
+
"stable_commands": "`/CER-status`、`/CER-help` 保持穩定 ASCII 指令",
|
|
166
|
+
"chinese_triggers": "自然語意觸發仍須有效",
|
|
167
|
+
"reply_language": "面向使用者的回覆跟隨使用者或目標專案語言",
|
|
168
|
+
"readme_boundary": "`README.md`/`README.en.md` 只屬用戶展示與安裝說明",
|
|
169
|
+
"release_notes_order": "`RELEASE_NOTES.md` 維持現行先繁中後英文",
|
|
170
|
+
"maintainer_qa": "maintainer release-QA,不是 ordinary execution、Goal、CER 工作法或 `/CER-help` 的一般 runtime 步驟",
|
|
171
|
+
"external_auth": "另行授權、遷移驗收與讀回",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
PUBLIC_RUNTIME_LANGUAGE_UAT_REQUIREMENTS = {
|
|
175
|
+
"no_divergent_zh": "不得在繁中 package 另定或覆蓋 CER 行為",
|
|
176
|
+
"chinese_triggers": "中文 `/CER-auto`、`/CER-start`、`CER 自適應`、`CER 啟動` 等觸發仍有效",
|
|
177
|
+
"reply_language": "回覆跟隨中文或目標專案語言",
|
|
178
|
+
"release_notes_order": "Release Notes 維持先繁中後英文",
|
|
179
|
+
"readme_not_owner": "兩者不能覆蓋 `core-runtime.md` 的 runtime owner",
|
|
180
|
+
"maintainer_qa": "只屬 maintainer release-QA",
|
|
181
|
+
"not_user_step": "不把它們列為一般用戶 runtime 步驟",
|
|
182
|
+
"not_retired_by_source_only": "source-only 規則改動本身不得聲稱已完成退役",
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
PUBLIC_RUNTIME_LANGUAGE_FORBIDDEN = {
|
|
186
|
+
"english_reply_default": "英文 canonical runtime 代表中文輸入預設用英文回答",
|
|
187
|
+
"readme_owner": "README 是 runtime owner",
|
|
188
|
+
"full_audit_user_step": "Full Audit 是一般用戶 runtime 步驟",
|
|
189
|
+
"delete_zh_without_validation": "可不經驗證刪除繁中 package",
|
|
190
|
+
"zh_diverges": "繁中 package 可另定 CER 行為",
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
REVIEWER_PROPORTIONALITY_COUNTEREXAMPLES = {
|
|
194
|
+
"simple_fixed_fresh_reviewer": "簡單任務也固定建立 fresh Reviewer",
|
|
195
|
+
"every_simple_task_reviewer": "每個簡單任務均安排 Reviewer",
|
|
196
|
+
"simple_task_fixed_r": "簡單任務亦固定建立 R",
|
|
197
|
+
"reverse_low_risk_fresh_reviewer": "fresh Reviewer 預設用於所有低風險任務",
|
|
198
|
+
"passive_simple_task_reviewer": "Reviewer 皆會被安排給簡單工作",
|
|
199
|
+
"low_risk_independent_reviewer": "低風險任務總是由獨立審閱者覆核",
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
OWNER_REQUIREMENTS = {
|
|
203
|
+
"formal_roles": "CER 正式角色只有 C、E1、R、E2",
|
|
204
|
+
"not_fifth_role": "不是第五角色",
|
|
205
|
+
"no_new_lifecycle": "不使用正式 title、cycle、ready、result、batch lifecycle 或 Reviewer 身份",
|
|
206
|
+
"no_new_commands": "不得新增 slash command",
|
|
207
|
+
"two_independent_lanes": "至少兩條工作線互不依賴,不需要彼此結果、共享可變狀態或固定執行次序",
|
|
208
|
+
"frozen_input_version": "每條 lane 的輸入及來源身份已凍結",
|
|
209
|
+
"concurrent_controller_work": "C 同期有不重複的關鍵分析、守門或裁決工作,不退化為候選整理員",
|
|
210
|
+
"independently_verifiable_candidates": "每條候選可由 C 按權威來源獨立驗證",
|
|
211
|
+
"material_time_saving": "預期淨省時明顯高於啟動、讀回、hash、去重及裁決成本",
|
|
212
|
+
"available_execution_slots": "所需平行槽可用,且不會壓縮正式 E1 或 fresh R 的必要能力",
|
|
213
|
+
"read_only": "`read_only`",
|
|
214
|
+
"isolated_artifact": "`isolated_artifact`",
|
|
215
|
+
"read_only_zero_write": "在任何位置都必須零寫入",
|
|
216
|
+
"project_noncontainment": "與 target project 互不包含",
|
|
217
|
+
"dangerous_roots": "不是磁碟根、使用者根、系統根",
|
|
218
|
+
"link_boundary": "symlink、junction、Windows reparse point、mount",
|
|
219
|
+
"lane_nonoverlap": "彼此不相等、互不為祖先",
|
|
220
|
+
"actual_tool_permission_boundary": "實際工具權限只容許該 lane 的明示 root;不能以相對路徑、萬用字元、環境 fallback 或 producer 自選位置擴張",
|
|
221
|
+
"lane_contract_label": "`lane_label`",
|
|
222
|
+
"lane_contract_goal": "單一目標",
|
|
223
|
+
"lane_contract_input": "輸入身份與版本、來源身份及可核實座標",
|
|
224
|
+
"lane_contract_scope": "允許範圍與禁止範圍",
|
|
225
|
+
"lane_contract_output": "預期候選輸出",
|
|
226
|
+
"lane_contract_acceptance": "驗收方式",
|
|
227
|
+
"lane_contract_stop": "停止條件",
|
|
228
|
+
"scratch_root": "`scratch_root`",
|
|
229
|
+
"candidate_claims": "`claims`",
|
|
230
|
+
"candidate_unknowns": "`unknowns`",
|
|
231
|
+
"verifiable_source_coordinates": "實際來源座標",
|
|
232
|
+
"candidate_hash": "實際絕對路徑與 SHA-256",
|
|
233
|
+
"controller_readback": "C 親自讀回",
|
|
234
|
+
"rehash": "重算 SHA-256",
|
|
235
|
+
"no_vote": "不得 投票",
|
|
236
|
+
"merged_batch": "E1 只接收該 C 合流批次",
|
|
237
|
+
"no_direct_e1": "不得直接使用 producer 原始通訊",
|
|
238
|
+
"no_wait_poll": "C 不 wait、poll 或背景監察 producer",
|
|
239
|
+
"late": "遲到候選",
|
|
240
|
+
"input_drift": "輸入或來源漂移",
|
|
241
|
+
"tamper": "hash drift、tamper",
|
|
242
|
+
"out_of_bounds": "路徑越界",
|
|
243
|
+
"producer_failure": "producer 建立失敗",
|
|
244
|
+
"stop_close": "`/CER-stop` 與 `/CER-close` 不等待 producer",
|
|
245
|
+
"serial_fallback": "`producer_count=0`",
|
|
246
|
+
"user_simplicity": "使用者無須設定 producer",
|
|
247
|
+
"material_only_report": "只報告會影響結果的成果、未知、 阻礙或風險",
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
UAT_REQUIREMENTS = {
|
|
251
|
+
"default_prompt_risk_proportionate": "預設提示使用「按風險建立 fresh Reviewer」",
|
|
252
|
+
"default_prompt_simple_task": "簡單任務不會因預設提示而強制建立 Reviewer",
|
|
253
|
+
"normal_two_lanes": "兩條互不依賴 lane",
|
|
254
|
+
"auto_idle": "`producer_count=0`",
|
|
255
|
+
"no_subagent": "沒有 subagent 能力",
|
|
256
|
+
"cost_fallback": "平行成本不划算",
|
|
257
|
+
"read_only_write": "`read_only` lane 嘗試任何寫入",
|
|
258
|
+
"root_boundary": "project 內或其祖先、磁碟根、使用者根、系統根",
|
|
259
|
+
"link_boundary": "symlink、junction、reparse point、mount",
|
|
260
|
+
"lane_overlap": "與另一 lane 相等/互為祖先",
|
|
261
|
+
"partial_drift": "只淘汰相依候選",
|
|
262
|
+
"source_conflict": "不按票數",
|
|
263
|
+
"late_candidate": "候選遲到",
|
|
264
|
+
"producer_failure": "producer 失敗",
|
|
265
|
+
"artifact_tamper": "artifact hash tamper",
|
|
266
|
+
"role_impersonation": "producer 冒充 E/R",
|
|
267
|
+
"direct_to_e1": "直接送 E1",
|
|
268
|
+
"unmerged_scratch": "E1 採用未合流 scratch",
|
|
269
|
+
"project_write": "C/R/producer 寫 target project",
|
|
270
|
+
"stop_close": "`/CER-stop` 或 `/CER-close` 不等待 producer",
|
|
271
|
+
"no_lifecycle_identity": "不取得正式 title、cycle、ready、result、slash、lock、registry 或 run id",
|
|
272
|
+
"roadmap_boundary": "roadmap 的角色欄和 lifecycle 卡仍只有正式角色",
|
|
273
|
+
"auto_wait_threads_forbidden": "派工後自動使用 `wait_threads`/`read_thread` 當接收機制",
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
UNEXPECTED_FAILURE_REQUIREMENTS = {
|
|
277
|
+
"test_not_authority": "測試只產生證據,不增加修改權",
|
|
278
|
+
"allowlist_not_semantics": "不代表 E1 可改變該檔案內其他 owner、權威來源或受保護語意",
|
|
279
|
+
"gate_off": "不啟動本閘門",
|
|
280
|
+
"caused": "可在本批修正",
|
|
281
|
+
"preexisting": "只回報,不修",
|
|
282
|
+
"unknown_or_boundary": "停止進一步寫入",
|
|
283
|
+
"regression_boundary": "不會擴大 E1 的修補權",
|
|
284
|
+
"controller_only": "只有 C 可重凍結契約,並用新的 `batchId`/`payloadDigest` 派發新批次來擴大範圍",
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
DELIVERY_REQUIREMENTS = {
|
|
288
|
+
"post_dispatch_parked": "C 派工、建 task 或送訊後立即進入 `POST_DISPATCH_PARKED`",
|
|
289
|
+
"no_auto_wait": "不得自動使用 `wait_threads`、`read_thread` 或平台等價工具作等待、喚醒",
|
|
290
|
+
"no_auto_progress_read": "final 讀取、狀態探測或結果發現",
|
|
291
|
+
"read_exceptions": "只有兩個讀取例外:使用者在同一輪明示要求的一次性 thread 查證;或 C 已收到 direct-push 後,為驗證或裁決作一次有界讀回",
|
|
292
|
+
"no_push_no_progress": "沒有 direct-push 時,wait snapshot、完成狀態、commentary、摘要、child final",
|
|
293
|
+
"no_push_no_advance": "不能把 `pending`/`delivery_incomplete`",
|
|
294
|
+
"no_automatic_waiting": "禁止自動 waiting、反覆 waiting、polling、背景監聽",
|
|
295
|
+
"delivery_state_values": "`delivery_state` 記錄送達狀態,只用 `confirmed_delivered`、`not_delivered`、`delivery_unknown` 三值",
|
|
296
|
+
"confirmed_delivered_evidence": "`confirmed_delivered` 需要 target direct-push ack",
|
|
297
|
+
"send_success_not_delivery": "send 返回 success、title 變化、thread id 存在或 sender 自稱已送出都不足夠",
|
|
298
|
+
"delivery_unknown_retry": "`delivery_unknown` 只可按本節做一次有界讀回和同 `messageId` 受控重送",
|
|
299
|
+
"before_next_batch_delivery": "派下一批前,上一批結果必須同時有必要控制訊息的 `confirmed_delivered`",
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
DELIVERY_UAT_REQUIREMENTS = {
|
|
303
|
+
"post_dispatch_parked_uat": "派工後停在 `POST_DISPATCH_PARKED`",
|
|
304
|
+
"bounded_wakeup_wrapper_bad": "把等待包裝成有界喚醒",
|
|
305
|
+
"no_push_next_batch_bad": "未收到 direct-push 仍推進狀態或派下一批",
|
|
306
|
+
"send_success_no_target": "send tool 回 success 但 target 無 direct-push ack",
|
|
307
|
+
"wrong_batch_digest": "target ack 錯誤 `batchId`/`payloadDigest`",
|
|
308
|
+
"delivery_unknown_bad": "`delivery_unknown` 經一次有界檢查/受控重送後仍被當成 received",
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
TRUTH_SOURCE_INTAKE_REQUIREMENTS = {
|
|
312
|
+
"sole_owner": "真源攝取門檻屬於 Controller preflight 的唯一 owner",
|
|
313
|
+
"four_questions": "誰擁有;誰實際使用;如何生效;甚麼反例能推翻",
|
|
314
|
+
"owner_definition": "`誰擁有` 指使用者裁決、專案真源、規則、檔案或外部權威的來源錨點",
|
|
315
|
+
"consumer_definition": "`誰實際使用` 指 E1、R、交付物、安裝面、公開面、後續批次或使用者流程如何消費該條件",
|
|
316
|
+
"effect_definition": "`如何生效` 指它如何改變本批派工、交付內容、權限、驗收或成果判定",
|
|
317
|
+
"disproof_definition": "`甚麼反例能推翻` 指哪個讀回、測試、Reviewer 問題或反例會令本批不能算成功",
|
|
318
|
+
"missing_is_critical": "任一項答不到,或答案依賴未讀的必要真源,該條件就是 `關鍵缺失`",
|
|
319
|
+
"no_dispatch": "C 不得派正式實作批次,只能做必要唯讀診斷、收窄驗收範圍,或用 `🟡 使用者裁決` 停問",
|
|
320
|
+
"not_full_audit": "不得把此門檻擴成預設全文讀取、全 repo 審查或固定 Full Audit",
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
TRUTH_SOURCE_INTAKE_UAT_REQUIREMENTS = {
|
|
324
|
+
"four_questions_pass": "非簡單正式實作批次在派工前,C 能逐項回答真源攝取四問",
|
|
325
|
+
"missing_blocks": "C 答不到真源攝取四問任一項",
|
|
326
|
+
"missing_still_dispatches": "非簡單正式實作批次未回答誰擁有、誰實際使用、如何生效、甚麼反例能推翻,C 仍建立/復用 E1 或派實作批次",
|
|
327
|
+
"overwide_gate": "C 把真源攝取門檻擴成預設全文讀取、全 repo 審查、固定 Full Audit、第二份規則 owner 或固定表格流程",
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
CONTROLLER_CHALLENGE_UAT_REQUIREMENTS = {
|
|
331
|
+
"section": "## Controller 長任務挑戰情景",
|
|
332
|
+
"measurable_endpoint": "欠缺可量度或可讀回的終點",
|
|
333
|
+
"authority_boundary": "必要權威、允許邊界或反例證據不足時",
|
|
334
|
+
"adjacent_mainline": "合理但相鄰的要求、流程改善或替代交付",
|
|
335
|
+
"defensive_expansion": "不得成為防禦性擴建理由",
|
|
336
|
+
"changed_contract": "依賴舊條件的候選不可沿用舊接納身份",
|
|
337
|
+
"no_thrashing": "不得造成 ordinary/CER 震盪",
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
TRUTH_SOURCE_INTAKE_FORBIDDEN = {
|
|
341
|
+
"missing_four_questions_dispatch": "未回答誰擁有、誰實際使用、如何生效、甚麼反例能推翻時,C 仍可派正式實作批次",
|
|
342
|
+
"full_ingestion_required": "真源攝取門檻要求預設全文讀取、全 repo 審查或固定 Full Audit",
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
UNEXPECTED_FAILURE_UAT_MARKERS = {
|
|
346
|
+
"gate_off": "<!-- cer-uat-unexpected-failure:gate-off -->",
|
|
347
|
+
"caused": "<!-- cer-uat-unexpected-failure:caused -->",
|
|
348
|
+
"preexisting": "<!-- cer-uat-unexpected-failure:preexisting -->",
|
|
349
|
+
"unknown": "<!-- cer-uat-unexpected-failure:unknown -->",
|
|
350
|
+
"semantic_boundary": "<!-- cer-uat-unexpected-failure:semantic-boundary -->",
|
|
351
|
+
"acceptance_boundary": "<!-- cer-uat-unexpected-failure:acceptance-boundary -->",
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
UNEXPECTED_FAILURE_FORBIDDEN = {
|
|
355
|
+
"test_grants_authority": "測試失敗會增加 E1 的修改權",
|
|
356
|
+
"allowlist_grants_semantics": "檔案在 allowlist 內即授權 E1 改變該檔案所有語意",
|
|
357
|
+
"missing_baseline_guess": "沒有 baseline 時,E1 應猜測並修復",
|
|
358
|
+
"executor_expands_scope": "E1 可自行擴大範圍,不需要 C 重凍結新批次",
|
|
359
|
+
"controller_expands_without_new_identity": "C 可沿用舊 `batchId`/`payloadDigest` 擴大範圍",
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
SENDABLE_PACKET_REQUIREMENTS = {
|
|
363
|
+
"draft_sendable_split": "`draft_packet`",
|
|
364
|
+
"no_placeholders": "`sendable_packet` 不得保留 `<...>` 佔位符",
|
|
365
|
+
"truth_intake_summary": "Controller preflight 已通過的真源攝取四問摘要:誰擁有、誰實際使用、如何生效、甚麼反例能推翻",
|
|
366
|
+
"create_prompt_handshake_only": "新建 E1/R 的 `create_thread` 初始 prompt 不等於正式批次",
|
|
367
|
+
"internal_return_channel": "正式 direct-push 回傳通道是 CER 內部通訊,不屬於被禁止的 project/source-root 寫入或外部副作用",
|
|
368
|
+
"create_prompt_no_full_payload": "不得在 create prompt 放入完整 source corpus、候選工作內容或正式批次 payload",
|
|
369
|
+
"large_payload_once": "C 只在正式 `sendable_packet` 發送一次",
|
|
370
|
+
"large_payload_split": "過長或跨風險邊界的輸入按語義/風險切成多個正式批次",
|
|
371
|
+
"pre_dispatch_evidence": "長期、多批、高風險或非簡單正式實作批次的 `sendable_packet` 必須包含短小 `pre_dispatch_evidence`",
|
|
372
|
+
"pre_dispatch_not_new_owner": "它不是新真源、固定表格、背景監察或 Full Audit",
|
|
373
|
+
"pre_dispatch_fields": "內容至少列明:`outcome_anchor` 指向或摘要;本批改善的未完成條件與成功後可讀回成果差異;真源攝取四問摘要及來源錨點;已讀必要真源與仍缺真源的處置;本批工作線分類;若觸發 drift checkpoint,列其結論,否則說明未觸發理由",
|
|
374
|
+
"pre_dispatch_missing_blocks": "缺失、互相矛盾、依賴未讀必要真源,或只有「已判斷」但沒有可讀回摘要時,`sendable_packet` 不可送出",
|
|
375
|
+
"pre_dispatch_assignee_blocks": "E1/R 收到缺少必要 `pre_dispatch_evidence` 的正式批次時,只可 direct-push 零寫入 blocker(例如 `BATCH_BLOCKED_MISSING_PRE_DISPATCH_EVIDENCE`)並停止",
|
|
376
|
+
"concrete_bindings": "正式派工必須填入實際 `threadId` 或平台等價座標、`returnTarget`、`messageId`、`batchId`、`batchSeq`、`payloadDigest`,以及當前工具 schema/receipt 明示必需的路由座標",
|
|
377
|
+
"sessionid_not_threadid": "sessionId 不可代替 threadId 作正式派工座標",
|
|
378
|
+
"hostid_not_hard_required": "hostId 只在當前工具 schema 或 receipt 明示需要/提供時使用",
|
|
379
|
+
"no_hostid_inference": "不得由 `local`、title、sessionId、threadId 形狀或錯誤訊息推導 hostId",
|
|
380
|
+
"relative_identity_draft_only": "`同一 E1`/`上述 E1`/`下一個序號` 等相對說法只可作草稿",
|
|
381
|
+
"review_manifest": "R 派工必須填入實際 `candidateIdentity`、`candidateManifest` 及候選 delivery evidence",
|
|
382
|
+
"missing_blocks": "缺任一項即停在 `dispatch_blocked` 或 `decision_blocked`",
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
SENDABLE_PACKET_UAT_REQUIREMENTS = {
|
|
386
|
+
"placeholder_self_pass": "正式 `sendable_packet` 仍保留 `<...>` 佔位符",
|
|
387
|
+
"create_prompt_payload": "新建 E1/R create prompt 包含完整 source corpus、候選工作內容或正式批次",
|
|
388
|
+
"internal_return_channel_forbidden": "派工包同時要求 direct-push,又把正式 direct-push 回傳通道當成被禁止外部",
|
|
389
|
+
"double_large_payload": "同一完整大型輸入在 create prompt 和 formal `sendable_packet` 被重複發送",
|
|
390
|
+
"relative_identity": "正式派工用 `同一 E1`/`上述 E1`/`下一個序號` 等相對說法",
|
|
391
|
+
"hostid_hard_required": "Controller 仍硬性要求 `hostId`",
|
|
392
|
+
"hostid_inferred": "由 `local`、title、sessionId、threadId 形狀或錯誤訊息推導 hostId",
|
|
393
|
+
"sessionid_replaces_threadid": "正式派工以 sessionId 代替 threadId 作正式派工座標",
|
|
394
|
+
"review_manifest_missing": "R 派工缺實際 `candidateIdentity`、`candidateManifest` 或候選 delivery evidence",
|
|
395
|
+
"pre_dispatch_missing": "派工包缺 `pre_dispatch_evidence`",
|
|
396
|
+
"pre_dispatch_claim_only": "只寫「C 已判斷」但無可讀回摘要",
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
SENDABLE_PACKET_FORBIDDEN = {
|
|
400
|
+
"placeholder_allowed": "正式可送出的派工包可以保留 `<...>` 佔位符",
|
|
401
|
+
"create_prompt_full_payload": "create prompt 可包含完整 source corpus 或正式批次 payload",
|
|
402
|
+
"double_send_large_payload": "C 可在 create prompt 和正式 `sendable_packet` 重複發送同一完整大型輸入",
|
|
403
|
+
"relative_identity_allowed": "`同一 E1`/`上述 E1`/`下一個序號` 可作為正式派工身份",
|
|
404
|
+
"hostid_always_required": "正式派工一律必須填入 `hostId`,即使當前工具 schema 只要求 `threadId`",
|
|
405
|
+
"sessionid_infers_hostid": "可由 sessionId、title、`local` 或錯誤訊息推導 hostId 後繼續",
|
|
406
|
+
"sessionid_replaces_threadid": "sessionId 可代替 threadId 作正式派工座標",
|
|
407
|
+
"review_manifest_optional": "R 派工可以省略 `candidateManifest`",
|
|
408
|
+
"draft_pass": "`draft_packet` 可自評為可送出",
|
|
409
|
+
"pre_dispatch_optional": "長期、多批、高風險或非簡單正式實作批次不需要 `pre_dispatch_evidence`",
|
|
410
|
+
"assignee_fills_missing_pre_dispatch": "E1/R 可自行補完 C 的 pre-dispatch evidence 並繼續寫入",
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
MESSAGE_ID_BOUNDARY_REQUIREMENTS = (
|
|
414
|
+
"`messageId` 只是 CER 訊息層的識別、去重及追蹤欄位",
|
|
415
|
+
"不是 Codex 執行指令、App Server `method`、JSON-RPC request `id`、`threadId`、`sessionId`、idempotency key 或授權",
|
|
416
|
+
"未經實際工具呼叫及工具結果/可核實送達證據",
|
|
417
|
+
"只有 `messageId` 不算訊息已送達或工作已執行",
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
MESSAGE_ID_UAT_REQUIREMENTS = {
|
|
421
|
+
"identity_not_command": "只在 prompt、派工包、摘要或自稱回執中放入 `messageId`,就把它當成已建立 thread、開始 turn、呼叫工具、觸發寫入或授權",
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
MESSAGE_ID_FORBIDDEN = {
|
|
425
|
+
"messageid_starts_operation": "單獨 `messageId` 可以建立 thread、開始 turn 或呼叫工具",
|
|
426
|
+
"messageid_is_authority": "`messageId` 本身就是授權或 idempotency key",
|
|
427
|
+
"send_success_is_delivered": "send success 就是 `confirmed_delivered`",
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
LIVING_BRIEF_REQUIREMENTS = (
|
|
431
|
+
"C 維護一份活的任務簡報",
|
|
432
|
+
"活的任務簡報不是新 workflow,也不建立固定項目文件",
|
|
433
|
+
"已確認要求/排除、可安全推定、關鍵缺口、最新使用者回饋、本批凍結、下一個可觀察預覽或裁決點、與上一版相比改變了甚麼",
|
|
434
|
+
"C 只凍結下一個可安全執行批次",
|
|
435
|
+
"E1/R 派工使用最新活的任務簡報與本批凍結",
|
|
436
|
+
"E1 只獲授權執行本批凍結內容",
|
|
437
|
+
"R 依最新任務簡報、本批凍結、候選 identity 及 delivery evidence 驗收",
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
LIVING_BRIEF_ROADMAP_REQUIREMENTS = (
|
|
441
|
+
"顯示活的任務簡報、本批凍結和下一個可觀察停點",
|
|
442
|
+
"任何用戶可見的活的任務簡報都必須明示 `CER`",
|
|
443
|
+
"不得以「Codex 任務簡報」",
|
|
444
|
+
"CER 路線圖|活簡報",
|
|
445
|
+
"CER 活簡報:已確認=<...>|安全推定=<...>|待裁決=<...>",
|
|
446
|
+
"CER 本批凍結:<只本批會做>",
|
|
447
|
+
"CER 上次回饋/變更:<.../無>",
|
|
448
|
+
"活的任務簡報也只由最高可用權威來源",
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
LIVING_BRIEF_UAT_REQUIREMENTS = {
|
|
452
|
+
"fuzzy_start": "模糊但可開始的多批任務,C 建立活的任務簡報",
|
|
453
|
+
"no_full_spec_first": "不要求使用者先寫完整規格",
|
|
454
|
+
"not_project_context_prereq": "不把 `$project-context-workflow` 當成前置",
|
|
455
|
+
"feedback_delta": "使用者看過中間成果後改方向或補限制時,C 先更新活的任務簡報和路線圖差異",
|
|
456
|
+
"review_latest_brief": "R 驗收依最新任務簡報、本批凍結、候選 identity 及 delivery evidence",
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
LIVING_BRIEF_FORBIDDEN = {
|
|
460
|
+
"new_workflow": "活的任務簡報是一個獨立新 workflow",
|
|
461
|
+
"initial_prompt_full_freeze": "整輪初始 prompt 永遠是完整凍結規格",
|
|
462
|
+
"e1_unfrozen_future": "E1 可自行實作未凍結後續批次",
|
|
463
|
+
"r_initial_prompt_only": "R 只按最初 prompt 驗收",
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
OUTCOME_ANCHOR_REQUIREMENTS = (
|
|
467
|
+
"不可由後續批次自行改寫的 `outcome_anchor`",
|
|
468
|
+
"不可接受的替代成果",
|
|
469
|
+
"`mainline_outcome`、`diagnostic`、`mechanism_improvement` 或 `governance_self_improvement`",
|
|
470
|
+
"預期成果改善為零且不是必要條件的實作批次不得派出",
|
|
471
|
+
"活動不等於成果",
|
|
472
|
+
"只有 C 讀回並裁決某項使用者完成條件取得已接納差異",
|
|
473
|
+
"同一失敗類別按共同根因、使用者後果、受影響完成條件和方法判定",
|
|
474
|
+
"改名、換版本、換包裝",
|
|
475
|
+
"連續兩次未解決後,C 不得派第三個同類修正版",
|
|
476
|
+
"每批終態只可為已接納成果",
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
OUTCOME_ANCHOR_ROADMAP_REQUIREMENTS = (
|
|
480
|
+
"`outcome_anchor` 的已接納成果差異",
|
|
481
|
+
"不得以批次、task 或審閱",
|
|
482
|
+
"CER 成果錨:未完成=<完成條件>|已接納差異=<成果差異/無>",
|
|
483
|
+
"CER 工作線:<mainline_outcome/diagnostic/mechanism_improvement/governance_self_improvement>",
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
OUTCOME_ANCHOR_UAT_REQUIREMENTS = {
|
|
487
|
+
"anchor_fixed": "長期多批任務在首批前固定 `outcome_anchor`",
|
|
488
|
+
"zero_delta_rejected": "預期成果改善為零且不是必要條件的實作批次被拒絕",
|
|
489
|
+
"diagnostic_not_progress": "診斷批次可以執行並產生承接條件,但標為 `diagnostic`,不增加主線進度",
|
|
490
|
+
"technical_pass_not_progress": "技術檢查、格式、檔案一致或審閱通過,但 `outcome_anchor` 沒有已接納成果差異時,不標記為成功進度",
|
|
491
|
+
"third_retry_intercepted": "同一失敗類別連續兩次未解決後,第三次同類修正版被攔截",
|
|
492
|
+
"rename_same_retry": "改名、換版本、換包裝或同方法重派仍被識別為同類重試",
|
|
493
|
+
"reviewer_rejects_drift": "R 必須拒絕偏離原始成果、只有技術活動、反覆返工或用另一種交付形式代替使用者原要求的批次",
|
|
494
|
+
"mechanism_not_mainline": "`mechanism_improvement` 或 `governance_self_improvement` 不污染主線進度",
|
|
495
|
+
"adjacent_not_blocker": "相鄰改善失敗不會自動阻塞原任務",
|
|
496
|
+
"simple_lightweight": "簡單、單步、低風險且終點唯一的任務仍可用短摘要和 C 讀回驗收",
|
|
497
|
+
"completion_outcomes": "任務完成回報列已接納成果差異和未完成條件",
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
OUTCOME_ANCHOR_FORBIDDEN = {
|
|
501
|
+
"zero_delta_dispatch": "預期成果改善為零的實作批次可以派出",
|
|
502
|
+
"diagnostic_mainline": "診斷批次增加主線進度",
|
|
503
|
+
"third_retry_allowed": "第三個同類修正版可以繼續派出",
|
|
504
|
+
"activity_completion": "批次、task、Reviewer 或候選數量就是完成證據",
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
DRIFT_CHECKPOINT_REQUIREMENTS = {
|
|
508
|
+
"sole_owner": "長期任務防失焦檢查點屬於本節唯一 owner",
|
|
509
|
+
"no_new_monitor": "不另建監察角色、背景程序或固定表格",
|
|
510
|
+
"resume_trigger": "resume/上下文轉換",
|
|
511
|
+
"two_no_delta_trigger": "連續兩批沒有已接納成果差異",
|
|
512
|
+
"same_failure_trigger": "同類失敗第二次",
|
|
513
|
+
"adjacent_trigger": "E1/R 提出相鄰改向或替代交付",
|
|
514
|
+
"user_change_trigger": "使用者改方向或補限制",
|
|
515
|
+
"close_release_trigger": "close/release/重大交付前",
|
|
516
|
+
"next_condition": "下一批是否仍改善 `outcome_anchor` 的未完成條件",
|
|
517
|
+
"readable_delta": "成功後有甚麼可讀回成果差異",
|
|
518
|
+
"mainline_replacement": "是否正在取代主線成果",
|
|
519
|
+
"no_dispatch": "C 不得派正式實作批次",
|
|
520
|
+
"allowed_exits": "只可改做診斷、收窄驗收、停問使用者、終止路線",
|
|
521
|
+
"fresh_r_bounded": "風險足夠時建立 fresh R",
|
|
522
|
+
"not_progress": "checkpoint、活的任務簡報或路線圖更新不計作成果進度",
|
|
523
|
+
"no_monitoring": "不得觸發背景 monitoring、polling、自動 `wait_threads`、固定 R、固定 Full Audit",
|
|
524
|
+
"simple_exempt": "簡單、單步、低風險且終點唯一的任務",
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
DRIFT_CHECKPOINT_UAT_REQUIREMENTS = {
|
|
528
|
+
"generic_trigger": "長期、多批或容易受上下文污染的任務",
|
|
529
|
+
"two_no_delta": "連續兩批沒有已接納成果差異",
|
|
530
|
+
"same_failure": "同類失敗第二次",
|
|
531
|
+
"adjacent_change": "E1/R 提出相鄰改向或替代交付",
|
|
532
|
+
"next_condition": "下一批改善哪個 `outcome_anchor` 未完成條件",
|
|
533
|
+
"not_progress": "drift checkpoint、活的任務簡報或路線圖更新不計作成果進度",
|
|
534
|
+
"no_monitoring": "不觸發背景 monitoring、polling、自動 `wait_threads`、固定 R 或固定 Full Audit",
|
|
535
|
+
"missing_checkpoint_dispatch": "連續兩批沒有已接納成果差異,C 未做 drift checkpoint 仍派主線實作批次",
|
|
536
|
+
"adjacent_rewrites_mainline": "E1/R 提出相鄰改向、替代交付或範圍外 blocker 後,C 未分類是否取代主線成果便改寫下一批主線",
|
|
537
|
+
"checkpoint_as_progress": "drift checkpoint、活的任務簡報或路線圖更新被計作成果進度",
|
|
538
|
+
"checkpoint_triggers_monitoring": "drift checkpoint 觸發背景 monitoring、polling、自動 `wait_threads`、固定 R 或固定 Full Audit",
|
|
539
|
+
"simple_forced": "簡單、單步、低風險且終點唯一的任務被迫執行 drift checkpoint",
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
DRIFT_CHECKPOINT_FORBIDDEN = {
|
|
543
|
+
"background_monitor": "drift checkpoint 會啟動背景 monitoring",
|
|
544
|
+
"automatic_wait": "drift checkpoint 可自動使用 `wait_threads`",
|
|
545
|
+
"fixed_reviewer": "每次 drift checkpoint 都必須建立 R",
|
|
546
|
+
"fixed_full_audit": "每次 drift checkpoint 都觸發 Full Audit",
|
|
547
|
+
"progress_credit": "drift checkpoint 本身增加主線成果進度",
|
|
548
|
+
"simple_required": "簡單單步任務必須執行 drift checkpoint",
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
RESULT_DISPOSITION_REQUIREMENTS = {
|
|
552
|
+
"sole_owner": "結果處置門檻屬於本節唯一 owner",
|
|
553
|
+
"accepted_as": "`accepted_as` 為 `evidence_only`、`working_candidate`、`terminal_deliverable` 或 `authoritative_input`",
|
|
554
|
+
"bare_result": "裸 `RESULT_ACCEPTED` 只表示 C 已完成該批次裁決及通訊去重",
|
|
555
|
+
"unmet_persistence_fields": "`unmet_conditions`、`persistence_readback`",
|
|
556
|
+
"permitted_next_use_only": "`permitted_next_use` 是唯一下一步允許用途欄位",
|
|
557
|
+
"prior_result_use_enum": "`prior_result_use` 明確標為 `working_material` 或 `authority_input`",
|
|
558
|
+
"authority_fields": "若標為 `authority_input`,必須列出 `promotion_evidence` 與 `project_owner_anchor`",
|
|
559
|
+
"default_working_material": "候選、草稿、診斷、衍生輸出及純審閱結果預設只可作 `working_material`",
|
|
560
|
+
"authority_requires_owner": "要採納為 `authoritative_input`,C 必須有使用者明示或已讀目標專案既有 owner 的來源錨點",
|
|
561
|
+
"reviewer_split": "須按 `content_verdict`、`implementation_verdict`、`outcome_verdict`、`authority_promotion_verdict` 分層",
|
|
562
|
+
"reviewer_pass_limited": "內容或技術 PASS 不會自動形成 outcome PASS、authority promotion PASS 或主線進度",
|
|
563
|
+
"out_of_scope_not_pass": "`out_of_scope` 不是 PASS",
|
|
564
|
+
"review_scope_limited": "C 不得擴大 R 原本審閱範圍",
|
|
565
|
+
"terminal_candidate": "只有 `outcome_anchor` 本身要求草稿、候選或樣稿作終點時",
|
|
566
|
+
"persistence_blocks_next": "持久真源互相矛盾、尚未同步或 artifact 角色未能判定時,`next_dispatch` 必須是 `blocked`",
|
|
567
|
+
"missing_readback_blocks_authority": "`persistence_readback` 缺失、只說已保存但無 owner 讀回",
|
|
568
|
+
"unmet_conditions_blocks_authority": "`unmet_conditions` 尚未清零時,下一批只能消費為 `working_material`、診斷證據或保持 blocked",
|
|
569
|
+
"terminal_persistence_blocks_acceptance": "即使沒有下一批,C 亦不得把結果接納為 `terminal_deliverable`、報告進度或宣稱完成",
|
|
570
|
+
"terminal_artifact_set_consistency": "C 還須讀回每個被列為 `terminal_deliverable` 的最終狀態聲稱",
|
|
571
|
+
"closed_vocabulary": "`accepted_as`、`authority_effect`、`progress_effect` 及 `prior_result_use` 均為封閉詞彙",
|
|
572
|
+
"validate_before_persistence": "適當 writer 持久化前必須按本節合法值驗證",
|
|
573
|
+
"close_bundle_scope": "長期、多批、高風險或非簡單正式 CER 批次在關閉、接納為終端成果或交給下一批前",
|
|
574
|
+
"close_bundle_not_schema": "不是新 runtime owner、新 public command、KDL dependency 或平行 result-disposition schema",
|
|
575
|
+
"close_bundle_not_ordinary_goal": "普通 ordinary execution、Goal 草稿和低風險小批不強制使用",
|
|
576
|
+
"close_bundle_identity": "既有 `messageId`、`batchId`、`batchSeq`、`payloadDigest`",
|
|
577
|
+
"close_bundle_finish_line": "`pre_dispatch_evidence`/`outcome_anchor` 指向、本批未完成條件、成功後可讀回成果差異",
|
|
578
|
+
"close_bundle_buckets": "`acceptance_blockers`、`worker_regressions`、`adjacent_backlog`、`scope_change_requests`",
|
|
579
|
+
"close_bundle_identity_mismatch": "dispatch、result、ack 身份或 digest 不一致",
|
|
580
|
+
"close_bundle_delivery_blocks": "`delivery_state` 仍是 `delivery_unknown`/`not_delivered`",
|
|
581
|
+
"close_bundle_repair_buckets": "`acceptance_blockers` 和 `worker_regressions` 才可在 repair budget 未耗盡時導致有界修補",
|
|
582
|
+
"close_bundle_budget_no_reset": "ack 不得重置 attempt 或調高 max_attempts",
|
|
583
|
+
"close_bundle_adjacent_backlog": "`adjacent_backlog` 只可另列",
|
|
584
|
+
"close_bundle_scope_change": "`scope_change_requests` 只能 blocked 或停問使用者重定終點",
|
|
585
|
+
"close_bundle_close_clear": "`next_dispatch=close` 時不得殘留 acceptance blocker",
|
|
586
|
+
"close_bundle_pass_limited": "validator 或 closure PASS 只證明協調閉環一致",
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
RESULT_DISPOSITION_UAT_REQUIREMENTS = {
|
|
590
|
+
"content_pass_candidate": "Reviewer 通過候選內容時",
|
|
591
|
+
"derived_output_blocked": "`derived_output` 被下一批列作 `authority_input`",
|
|
592
|
+
"authority_input_missing_fields": "`prior_result_use: authority_input` 缺 `promotion_evidence` 或 `project_owner_anchor`",
|
|
593
|
+
"working_material_use_limits": "`prior_result_use: working_material` 只允許修改、比較、審閱或 refine,不得作決策權威",
|
|
594
|
+
"working_material_allowed": "`prior_result_use` 標為 `working_material`",
|
|
595
|
+
"technical_pass_limited": "Reviewer 技術 PASS 但 outcome FAIL",
|
|
596
|
+
"split_verdict_limited": "Reviewer 只提供 `content_verdict: pass` 或 `implementation_verdict: pass`",
|
|
597
|
+
"authority_out_of_scope": "`authority_promotion_verdict` 是 `out_of_scope`",
|
|
598
|
+
"truth_conflict_blocks": "Handoff、計劃、進度或其他目標專案真源",
|
|
599
|
+
"persistence_change_classes": "結果改變當前階段、artifact 角色、下一產品路線、權威來源、progress claim 或後續批次輸入之一",
|
|
600
|
+
"terminal_stale_state": "最後一批已產生正確交付物,但目標專案 current-state owner 仍寫着舊階段、沒有 terminal deliverable 或舊下一步時",
|
|
601
|
+
"terminal_artifact_set_conflict": "被列作 `terminal_deliverable` 的 `RUN_RESULT` 仍聲稱 persistence pending、未接納或舊階段時",
|
|
602
|
+
"accepted_as_synonym_rejected": "`accepted_as=terminal_outcome`",
|
|
603
|
+
"phase1_legal_disposition": "Phase 1 候選只完成非終端 checkpoint 時",
|
|
604
|
+
"progress_effect_synonym_rejected": "`progress_effect=accepted_outcome_delta_for_phase1_only`",
|
|
605
|
+
"draft_terminal_deliverable": "使用者終點本身就是草稿、候選或樣稿",
|
|
606
|
+
"missing_unmet_readback": "result disposition 缺 `unmet_conditions` 或 `persistence_readback`",
|
|
607
|
+
"next_allowed_use_rejected": "C 使用 `next_allowed_use` 代替唯一合法的 `permitted_next_use`",
|
|
608
|
+
"next_batch_source_named": "下一批沒有說明消費 accepted authority、working material、diagnostic evidence 或 clean baseline",
|
|
609
|
+
"close_bundle_scope": "長期、多批、高風險或非簡單正式 CER 批次關閉前",
|
|
610
|
+
"close_bundle_no_second_schema": "不建立第二套 result disposition schema",
|
|
611
|
+
"close_bundle_no_ordinary_goal": "不強制 ordinary execution、Goal 草稿或低風險小批使用",
|
|
612
|
+
"close_bundle_repair_budget": "ack 未重置 attempt/調高 max_attempts",
|
|
613
|
+
"close_bundle_adjacent_backlog": "`adjacent_backlog` 唯一有值",
|
|
614
|
+
"close_bundle_scope_change": "`scope_change_requests` 有值時",
|
|
615
|
+
"close_bundle_identity_delivery": "dispatch/result/ack 身份或 `payloadDigest` 不一致",
|
|
616
|
+
"close_bundle_pass_limited": "close-bundle validator PASS 只表示協調閉環一致",
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
RESULT_DISPOSITION_FORBIDDEN = {
|
|
620
|
+
"bare_result_promotes": "裸 `RESULT_ACCEPTED` 代表正式採用",
|
|
621
|
+
"candidate_auto_authority": "候選 PASS 自動成為權威輸入",
|
|
622
|
+
"technical_pass_outcome": "R 技術 PASS 就是 outcome PASS",
|
|
623
|
+
"authority_without_promotion_fields": "`authority_input` 可缺 `promotion_evidence` 或 `project_owner_anchor`",
|
|
624
|
+
"out_of_scope_pass": "`out_of_scope` 算 PASS",
|
|
625
|
+
"unpersisted_next_dispatch": "未持久化仍可派下一批",
|
|
626
|
+
"unpersisted_terminal_acceptance": "最後一批可在持久真源過期時直接接納為 `terminal_deliverable` 並宣稱完成",
|
|
627
|
+
"contradictory_terminal_artifact_accepted": "終點集合可包含仍聲稱持久化待完成的 artifact 並照常接納",
|
|
628
|
+
"missing_readback_authority": "缺 `persistence_readback` 仍可作權威輸入",
|
|
629
|
+
"next_allowed_use_substitutes": "`next_allowed_use` 可代替 `permitted_next_use`",
|
|
630
|
+
"unmet_conditions_dispatch": "未清零 `unmet_conditions` 仍可派下一批",
|
|
631
|
+
"close_bundle_forces_ordinary": "delegation close bundle 可強制 ordinary execution、Goal 草稿或低風險小批使用",
|
|
632
|
+
"close_bundle_synonym_schema": "close bundle 可另建 result-disposition 近義欄位並替代本節封閉詞彙",
|
|
633
|
+
"close_bundle_mismatch_close": "dispatch/result/ack 身份不一致時仍可 `RESULT_ACCEPTED`",
|
|
634
|
+
"close_bundle_adjacent_repair": "`adjacent_backlog` 可單獨觸發主線 repair",
|
|
635
|
+
"close_bundle_scope_repair": "`scope_change_requests` 可包裝成 `bounded_repair`",
|
|
636
|
+
"close_bundle_budget_reset": "ack 可重置 repair attempt 或調高 max_attempts",
|
|
637
|
+
"close_bundle_overclaim": "close-bundle validator PASS 可宣稱 outcome PASS、release-ready、npm-ready 或節省 token",
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def read_texts(root: Path) -> dict[str, str]:
|
|
642
|
+
texts: dict[str, str] = {}
|
|
643
|
+
for relative in EXPECTED_FILES:
|
|
644
|
+
path = root / Path(relative)
|
|
645
|
+
if path.is_file():
|
|
646
|
+
texts[relative] = path.read_text(encoding="utf-8-sig")
|
|
647
|
+
return texts
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def frontmatter_findings(skill_text: str) -> list[str]:
|
|
651
|
+
findings: list[str] = []
|
|
652
|
+
match = re.match(r"\A---\n([\s\S]*?)\n---\n", skill_text)
|
|
653
|
+
if not match:
|
|
654
|
+
return ["SKILL.md frontmatter is missing or malformed"]
|
|
655
|
+
keys: list[str] = []
|
|
656
|
+
for line in match.group(1).splitlines():
|
|
657
|
+
key_match = re.match(r"^([a-z_]+):", line)
|
|
658
|
+
if not key_match:
|
|
659
|
+
findings.append(f"SKILL.md frontmatter malformed line: {line}")
|
|
660
|
+
continue
|
|
661
|
+
keys.append(key_match.group(1))
|
|
662
|
+
if keys != ["name", "description"]:
|
|
663
|
+
findings.append(f"SKILL.md frontmatter keys must be name,description; actual={keys}")
|
|
664
|
+
if not re.search(r'^name:\s*cer-workflow\s*$', match.group(1), re.MULTILINE):
|
|
665
|
+
findings.append("SKILL.md name must be cer-workflow")
|
|
666
|
+
if "明確帶 CER" not in match.group(1) or "按需平行候選" not in match.group(1):
|
|
667
|
+
findings.append("SKILL.md description lacks explicit CER trigger or on-demand parallel capability")
|
|
668
|
+
return findings
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def openai_yaml_findings(text: str) -> list[str]:
|
|
672
|
+
findings: list[str] = []
|
|
673
|
+
required_lines = (
|
|
674
|
+
'interface:',
|
|
675
|
+
' display_name: "CER 工作法"',
|
|
676
|
+
' short_description: "',
|
|
677
|
+
' default_prompt: "',
|
|
678
|
+
'policy:',
|
|
679
|
+
' allow_implicit_invocation: false',
|
|
680
|
+
)
|
|
681
|
+
for required in required_lines:
|
|
682
|
+
if required not in text:
|
|
683
|
+
findings.append(f"agents/openai.yaml missing required shape: {required}")
|
|
684
|
+
if "$cer-workflow" not in text:
|
|
685
|
+
findings.append("agents/openai.yaml default_prompt must include $cer-workflow")
|
|
686
|
+
default_prompt_match = re.search(
|
|
687
|
+
r'^\s+default_prompt:\s*"([^"]+)"\s*$', text, re.MULTILINE
|
|
688
|
+
)
|
|
689
|
+
if default_prompt_match:
|
|
690
|
+
default_prompt = default_prompt_match.group(1)
|
|
691
|
+
# This is controlled package metadata, so exact equality is safer than
|
|
692
|
+
# an open-ended synonym blacklist.
|
|
693
|
+
if default_prompt != EXPECTED_DEFAULT_PROMPT:
|
|
694
|
+
findings.append(
|
|
695
|
+
"agents/openai.yaml default_prompt must exactly match the canonical "
|
|
696
|
+
"risk-proportionate prompt and must not force Reviewer for simple work"
|
|
697
|
+
)
|
|
698
|
+
if re.search(r"\b(?:producer|lane|scratch|hash)\b|平行候選生產者", text, re.IGNORECASE):
|
|
699
|
+
findings.append("agents/openai.yaml must not expose producer setup vocabulary")
|
|
700
|
+
if re.search(r"^\s*(?:icon_small|icon_large|brand_color|dependencies):", text, re.MULTILINE):
|
|
701
|
+
findings.append("agents/openai.yaml contains an unprovided icon, brand or dependency")
|
|
702
|
+
top_keys = re.findall(r"^([a-z_]+):\s*$", text, re.MULTILINE)
|
|
703
|
+
if top_keys != ["interface", "policy"]:
|
|
704
|
+
findings.append(f"agents/openai.yaml top-level keys mismatch: {top_keys}")
|
|
705
|
+
for line in text.splitlines():
|
|
706
|
+
if re.match(r"^\s+(?:display_name|short_description|default_prompt):", line):
|
|
707
|
+
if not re.search(r':\s*"[^"]*"\s*$', line):
|
|
708
|
+
findings.append(f"agents/openai.yaml interface value must be quoted: {line}")
|
|
709
|
+
short_match = re.search(r'^\s+short_description:\s*"([^"]+)"\s*$', text, re.MULTILINE)
|
|
710
|
+
if short_match and not 25 <= len(short_match.group(1)) <= 64:
|
|
711
|
+
findings.append("agents/openai.yaml short_description must be 25-64 characters")
|
|
712
|
+
return findings
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def normalized_contains(text: str, snippet: str) -> bool:
|
|
716
|
+
return re.sub(r"\s+", " ", snippet) in re.sub(r"\s+", " ", text)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def markdown_section(text: str, heading: str) -> str:
|
|
720
|
+
match = re.search(
|
|
721
|
+
rf"^{re.escape(heading)}[ \t]*\n([\s\S]*?)(?=^## |\Z)",
|
|
722
|
+
text,
|
|
723
|
+
flags=re.MULTILINE,
|
|
724
|
+
)
|
|
725
|
+
return match.group(1) if match else ""
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def command_table_row(text: str, command: str) -> str:
|
|
729
|
+
pattern = r"^\|\s*`" + re.escape(command) + r"(?:\s+[^`]*)?`\s*\|.*$"
|
|
730
|
+
match = re.search(pattern, text, flags=re.MULTILINE)
|
|
731
|
+
return match.group(0) if match else ""
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def assert_snippets_present(
|
|
735
|
+
text: str,
|
|
736
|
+
snippets: tuple[str, ...],
|
|
737
|
+
label: str,
|
|
738
|
+
findings: list[str],
|
|
739
|
+
) -> None:
|
|
740
|
+
for snippet in snippets:
|
|
741
|
+
if not normalized_contains(text, snippet):
|
|
742
|
+
findings.append(f"trigger matrix missing {label}: {snippet}")
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def trigger_matrix_findings(texts: dict[str, str]) -> list[str]:
|
|
746
|
+
findings: list[str] = []
|
|
747
|
+
skill = texts["SKILL.md"]
|
|
748
|
+
core = texts["references/core-runtime.md"]
|
|
749
|
+
uat = texts["references/uat.md"]
|
|
750
|
+
|
|
751
|
+
frontmatter = re.match(r"\A---\n([\s\S]*?)\n---\n", skill)
|
|
752
|
+
description = frontmatter.group(1) if frontmatter else ""
|
|
753
|
+
assert_snippets_present(
|
|
754
|
+
description,
|
|
755
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["frontmatter"],
|
|
756
|
+
"SKILL.md frontmatter trigger boundary",
|
|
757
|
+
findings,
|
|
758
|
+
)
|
|
759
|
+
for label, source in (
|
|
760
|
+
("SKILL.md /CER-auto row", command_table_row(skill, "/CER-auto")),
|
|
761
|
+
("core-runtime.md /CER-auto row", command_table_row(core, "/CER-auto")),
|
|
762
|
+
):
|
|
763
|
+
assert_snippets_present(
|
|
764
|
+
source,
|
|
765
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["auto_row"],
|
|
766
|
+
label,
|
|
767
|
+
findings,
|
|
768
|
+
)
|
|
769
|
+
for label, source in (
|
|
770
|
+
("SKILL.md /CER-start row", command_table_row(skill, "/CER-start")),
|
|
771
|
+
("core-runtime.md /CER-start row", command_table_row(core, "/CER-start")),
|
|
772
|
+
):
|
|
773
|
+
assert_snippets_present(
|
|
774
|
+
source,
|
|
775
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["start_row"],
|
|
776
|
+
label,
|
|
777
|
+
findings,
|
|
778
|
+
)
|
|
779
|
+
for label, source in (
|
|
780
|
+
("SKILL.md /CER-close row", command_table_row(skill, "/CER-close")),
|
|
781
|
+
("core-runtime.md /CER-close row", command_table_row(core, "/CER-close")),
|
|
782
|
+
):
|
|
783
|
+
assert_snippets_present(
|
|
784
|
+
source,
|
|
785
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["close_row"],
|
|
786
|
+
label,
|
|
787
|
+
findings,
|
|
788
|
+
)
|
|
789
|
+
assert_snippets_present(
|
|
790
|
+
markdown_section(skill, "## 操作指令"),
|
|
791
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["auto_help_template"],
|
|
792
|
+
"SKILL.md /CER-auto task template help",
|
|
793
|
+
findings,
|
|
794
|
+
)
|
|
795
|
+
assert_snippets_present(
|
|
796
|
+
markdown_section(core, "## 啟動"),
|
|
797
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["startup_owner"],
|
|
798
|
+
"core-runtime.md startup owner",
|
|
799
|
+
findings,
|
|
800
|
+
)
|
|
801
|
+
assert_snippets_present(
|
|
802
|
+
markdown_section(core, "## 停用 CER"),
|
|
803
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["stop_owner"],
|
|
804
|
+
"core-runtime.md stop owner",
|
|
805
|
+
findings,
|
|
806
|
+
)
|
|
807
|
+
install = markdown_section(uat, "## 安裝情景")
|
|
808
|
+
assert_snippets_present(
|
|
809
|
+
install,
|
|
810
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["uat_install_auto"],
|
|
811
|
+
"uat.md installation auto matrix",
|
|
812
|
+
findings,
|
|
813
|
+
)
|
|
814
|
+
assert_snippets_present(
|
|
815
|
+
install,
|
|
816
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["uat_install_start"],
|
|
817
|
+
"uat.md installation start matrix",
|
|
818
|
+
findings,
|
|
819
|
+
)
|
|
820
|
+
assert_snippets_present(
|
|
821
|
+
install,
|
|
822
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["uat_install_close"],
|
|
823
|
+
"uat.md installation close matrix",
|
|
824
|
+
findings,
|
|
825
|
+
)
|
|
826
|
+
assert_snippets_present(
|
|
827
|
+
markdown_section(uat, "## 失敗條件"),
|
|
828
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["uat_failure"],
|
|
829
|
+
"uat.md failure-condition matrix",
|
|
830
|
+
findings,
|
|
831
|
+
)
|
|
832
|
+
assert_snippets_present(
|
|
833
|
+
markdown_section(uat, "## 失敗條件"),
|
|
834
|
+
ZH_TRIGGER_MATRIX_EXPECTATIONS["uat_failure_auto"],
|
|
835
|
+
"uat.md auto failure-condition matrix",
|
|
836
|
+
findings,
|
|
837
|
+
)
|
|
838
|
+
return findings
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def link_findings(root: Path, texts: dict[str, str]) -> list[str]:
|
|
842
|
+
findings: list[str] = []
|
|
843
|
+
link_re = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
|
|
844
|
+
for relative, text in texts.items():
|
|
845
|
+
if not relative.endswith(".md"):
|
|
846
|
+
continue
|
|
847
|
+
source = PurePosixPath(relative)
|
|
848
|
+
for target in link_re.findall(text):
|
|
849
|
+
target_path = target.split("#", 1)[0]
|
|
850
|
+
if not target_path or "://" in target_path:
|
|
851
|
+
continue
|
|
852
|
+
resolved = (root / Path(str(source.parent / target_path))).resolve()
|
|
853
|
+
try:
|
|
854
|
+
resolved.relative_to(root.resolve())
|
|
855
|
+
except ValueError:
|
|
856
|
+
findings.append(f"relative link escapes skill root: {relative} -> {target}")
|
|
857
|
+
continue
|
|
858
|
+
if not resolved.is_file():
|
|
859
|
+
findings.append(f"relative link target missing: {relative} -> {target}")
|
|
860
|
+
return findings
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def validate_texts(root: Path, texts: dict[str, str]) -> list[str]:
|
|
864
|
+
findings: list[str] = []
|
|
865
|
+
missing = sorted(EXPECTED_FILES - set(texts))
|
|
866
|
+
if missing:
|
|
867
|
+
findings.append(f"required files missing: {missing}")
|
|
868
|
+
return findings
|
|
869
|
+
|
|
870
|
+
version = texts["VERSION"]
|
|
871
|
+
if not re.fullmatch(r"\d+\.\d+\.\d+\n?", version):
|
|
872
|
+
findings.append("VERSION must contain exactly one stable semver line")
|
|
873
|
+
for relative in TEXT_FILES:
|
|
874
|
+
matches = SEMVER_RE.findall(texts[relative])
|
|
875
|
+
if matches:
|
|
876
|
+
findings.append(f"concrete package semver outside VERSION: {relative}: {matches}")
|
|
877
|
+
|
|
878
|
+
findings.extend(frontmatter_findings(texts["SKILL.md"]))
|
|
879
|
+
findings.extend(openai_yaml_findings(texts["agents/openai.yaml"]))
|
|
880
|
+
findings.extend(link_findings(root, texts))
|
|
881
|
+
findings.extend(trigger_matrix_findings(texts))
|
|
882
|
+
|
|
883
|
+
skill_commands = {
|
|
884
|
+
match.group(1)
|
|
885
|
+
for match in re.finditer(r"^\|\s*`(/CER-[a-z]+)(?:\s+[^`]*)?`", texts["SKILL.md"], re.MULTILINE)
|
|
886
|
+
}
|
|
887
|
+
if skill_commands != FORMAL_COMMANDS:
|
|
888
|
+
findings.append(f"slash commands must remain exactly six: {sorted(skill_commands)}")
|
|
889
|
+
|
|
890
|
+
all_markdown = "\n".join(
|
|
891
|
+
texts[relative] for relative in sorted(texts) if relative.endswith(".md")
|
|
892
|
+
)
|
|
893
|
+
normalized_markdown = re.sub(r"\s+", " ", all_markdown)
|
|
894
|
+
if all_markdown.count(OWNER_MARKER) != 1:
|
|
895
|
+
findings.append("parallel producer owner marker must occur exactly once")
|
|
896
|
+
if OWNER_MARKER not in texts["references/parallel-producers.md"]:
|
|
897
|
+
findings.append("parallel producer owner marker is not in its sole owner")
|
|
898
|
+
if all_markdown.count(UNEXPECTED_FAILURE_OWNER_MARKER) != 1:
|
|
899
|
+
findings.append("unexpected-failure gate owner marker must occur exactly once")
|
|
900
|
+
if UNEXPECTED_FAILURE_OWNER_MARKER not in texts["references/core-runtime.md"]:
|
|
901
|
+
findings.append("unexpected-failure gate owner marker is not in core-runtime.md")
|
|
902
|
+
if all_markdown.count(TRUTH_SOURCE_INTAKE_OWNER_MARKER) != 1:
|
|
903
|
+
findings.append("truth-source intake owner marker must occur exactly once")
|
|
904
|
+
if TRUTH_SOURCE_INTAKE_OWNER_MARKER not in texts["references/core-runtime.md"]:
|
|
905
|
+
findings.append("truth-source intake owner marker is not in core-runtime.md")
|
|
906
|
+
if all_markdown.count(DRIFT_CHECKPOINT_OWNER_MARKER) != 1:
|
|
907
|
+
findings.append("drift checkpoint owner marker must occur exactly once")
|
|
908
|
+
if DRIFT_CHECKPOINT_OWNER_MARKER not in texts["references/core-runtime.md"]:
|
|
909
|
+
findings.append("drift checkpoint owner marker is not in core-runtime.md")
|
|
910
|
+
if all_markdown.count(RESULT_DISPOSITION_OWNER_MARKER) != 1:
|
|
911
|
+
findings.append("result disposition owner marker must occur exactly once")
|
|
912
|
+
if RESULT_DISPOSITION_OWNER_MARKER not in texts["references/core-runtime.md"]:
|
|
913
|
+
findings.append("result disposition owner marker is not in core-runtime.md")
|
|
914
|
+
if all_markdown.count(EXECUTION_PROFILE_OWNER_MARKER) != 1:
|
|
915
|
+
findings.append("execution profile owner marker must occur exactly once")
|
|
916
|
+
if EXECUTION_PROFILE_OWNER_MARKER not in texts["references/core-runtime.md"]:
|
|
917
|
+
findings.append("execution profile owner marker is not in core-runtime.md")
|
|
918
|
+
if all_markdown.count(PUBLIC_RUNTIME_LANGUAGE_OWNER_MARKER) != 1:
|
|
919
|
+
findings.append("public runtime language owner marker must occur exactly once")
|
|
920
|
+
if PUBLIC_RUNTIME_LANGUAGE_OWNER_MARKER not in texts["references/core-runtime.md"]:
|
|
921
|
+
findings.append("public runtime language owner marker is not in core-runtime.md")
|
|
922
|
+
|
|
923
|
+
owner = re.sub(r"\s+", " ", texts["references/parallel-producers.md"])
|
|
924
|
+
for label, required in OWNER_REQUIREMENTS.items():
|
|
925
|
+
if required not in owner:
|
|
926
|
+
findings.append(f"parallel producer owner missing {label}")
|
|
927
|
+
if "## 探索助手自動調度" in all_markdown:
|
|
928
|
+
findings.append("legacy exploration-helper owner section remains")
|
|
929
|
+
|
|
930
|
+
skill = texts["SKILL.md"]
|
|
931
|
+
core = texts["references/core-runtime.md"]
|
|
932
|
+
uat = re.sub(r"\s+", " ", texts["references/uat.md"])
|
|
933
|
+
roadmap = re.sub(r"\s+", " ", texts["references/roadmap.md"])
|
|
934
|
+
core_normalized = re.sub(r"\s+", " ", core)
|
|
935
|
+
execution_profile_match = re.search(
|
|
936
|
+
r"^## 執行強度閘門[ \t]*\n([\s\S]*?)(?=^## |\Z)",
|
|
937
|
+
core,
|
|
938
|
+
re.MULTILINE,
|
|
939
|
+
)
|
|
940
|
+
if not execution_profile_match:
|
|
941
|
+
findings.append("core-runtime.md lacks the execution profile gate owner section")
|
|
942
|
+
else:
|
|
943
|
+
execution_profile_owner = re.sub(
|
|
944
|
+
r"\s+", " ", execution_profile_match.group(1)
|
|
945
|
+
)
|
|
946
|
+
if EXECUTION_PROFILE_OWNER_MARKER not in execution_profile_owner:
|
|
947
|
+
findings.append("execution profile marker is outside its owner section")
|
|
948
|
+
for label, required in EXECUTION_PROFILE_REQUIREMENTS.items():
|
|
949
|
+
if required not in execution_profile_owner:
|
|
950
|
+
findings.append(f"execution profile owner missing {label}")
|
|
951
|
+
public_runtime_match = re.search(
|
|
952
|
+
r"^## 公開 runtime 語言邊界[ \t]*\n([\s\S]*?)(?=^## |\Z)",
|
|
953
|
+
core,
|
|
954
|
+
re.MULTILINE,
|
|
955
|
+
)
|
|
956
|
+
if not public_runtime_match:
|
|
957
|
+
findings.append("core-runtime.md lacks the public runtime language boundary owner section")
|
|
958
|
+
else:
|
|
959
|
+
public_runtime_owner = re.sub(r"\s+", " ", public_runtime_match.group(1))
|
|
960
|
+
if PUBLIC_RUNTIME_LANGUAGE_OWNER_MARKER not in public_runtime_owner:
|
|
961
|
+
findings.append("public runtime language marker is outside its owner section")
|
|
962
|
+
for label, required in PUBLIC_RUNTIME_LANGUAGE_REQUIREMENTS.items():
|
|
963
|
+
if required not in public_runtime_owner:
|
|
964
|
+
findings.append(f"public runtime language owner missing {label}")
|
|
965
|
+
preflight_match = re.search(
|
|
966
|
+
r"^## Controller preflight[ \t]*\n([\s\S]*?)(?=^## 啟動|\Z)",
|
|
967
|
+
core,
|
|
968
|
+
re.MULTILINE,
|
|
969
|
+
)
|
|
970
|
+
if not preflight_match:
|
|
971
|
+
findings.append("core-runtime.md lacks the Controller preflight owner section")
|
|
972
|
+
else:
|
|
973
|
+
preflight_owner = re.sub(r"\s+", " ", preflight_match.group(1))
|
|
974
|
+
if TRUTH_SOURCE_INTAKE_OWNER_MARKER not in preflight_owner:
|
|
975
|
+
findings.append("truth-source intake marker is outside Controller preflight")
|
|
976
|
+
for label, required in TRUTH_SOURCE_INTAKE_REQUIREMENTS.items():
|
|
977
|
+
if required not in preflight_owner:
|
|
978
|
+
findings.append(f"truth-source intake owner missing {label}")
|
|
979
|
+
unexpected_failure_match = re.search(
|
|
980
|
+
r"^## 執行閉環[ \t]*\n([\s\S]*?)(?=^## |\Z)", core, re.MULTILINE
|
|
981
|
+
)
|
|
982
|
+
if not unexpected_failure_match:
|
|
983
|
+
findings.append("core-runtime.md lacks the execution-loop owner section")
|
|
984
|
+
else:
|
|
985
|
+
unexpected_failure_owner = re.sub(
|
|
986
|
+
r"\s+", " ", unexpected_failure_match.group(1)
|
|
987
|
+
)
|
|
988
|
+
if UNEXPECTED_FAILURE_OWNER_MARKER not in unexpected_failure_owner:
|
|
989
|
+
findings.append("unexpected-failure gate marker is outside its owner section")
|
|
990
|
+
for label, required in UNEXPECTED_FAILURE_REQUIREMENTS.items():
|
|
991
|
+
if required not in unexpected_failure_owner:
|
|
992
|
+
findings.append(f"unexpected-failure gate owner missing {label}")
|
|
993
|
+
outcome_anchor_match = re.search(
|
|
994
|
+
r"^## 成果錨定與進展閘[ \t]*\n([\s\S]*?)(?=^## |\Z)",
|
|
995
|
+
core,
|
|
996
|
+
re.MULTILINE,
|
|
997
|
+
)
|
|
998
|
+
if not outcome_anchor_match:
|
|
999
|
+
findings.append("core-runtime.md lacks the outcome-anchor progress owner section")
|
|
1000
|
+
else:
|
|
1001
|
+
outcome_anchor_owner = re.sub(r"\s+", " ", outcome_anchor_match.group(1))
|
|
1002
|
+
if DRIFT_CHECKPOINT_OWNER_MARKER not in outcome_anchor_owner:
|
|
1003
|
+
findings.append("drift checkpoint marker is outside outcome-anchor progress section")
|
|
1004
|
+
if RESULT_DISPOSITION_OWNER_MARKER not in outcome_anchor_owner:
|
|
1005
|
+
findings.append("result disposition marker is outside outcome-anchor progress section")
|
|
1006
|
+
for label, required in DRIFT_CHECKPOINT_REQUIREMENTS.items():
|
|
1007
|
+
if required not in outcome_anchor_owner:
|
|
1008
|
+
findings.append(f"drift checkpoint owner missing {label}")
|
|
1009
|
+
for label, required in RESULT_DISPOSITION_REQUIREMENTS.items():
|
|
1010
|
+
if required not in outcome_anchor_owner:
|
|
1011
|
+
findings.append(f"result disposition owner missing {label}")
|
|
1012
|
+
self_contained_match = re.search(
|
|
1013
|
+
r"^## 自足派工[ \t]*\n([\s\S]*?)(?=^## |\Z)", core, re.MULTILINE
|
|
1014
|
+
)
|
|
1015
|
+
if not self_contained_match:
|
|
1016
|
+
findings.append("core-runtime.md lacks the self-contained dispatch section")
|
|
1017
|
+
else:
|
|
1018
|
+
self_contained_owner = re.sub(r"\s+", " ", self_contained_match.group(1))
|
|
1019
|
+
for label, required in SENDABLE_PACKET_REQUIREMENTS.items():
|
|
1020
|
+
if required not in self_contained_owner:
|
|
1021
|
+
findings.append(f"sendable-packet gate missing {label}")
|
|
1022
|
+
message_identity_match = re.search(
|
|
1023
|
+
r"^## 工具結果不明、角色對帳與批次去重[ \t]*\n([\s\S]*?)(?=^## |\Z)",
|
|
1024
|
+
core,
|
|
1025
|
+
re.MULTILINE,
|
|
1026
|
+
)
|
|
1027
|
+
if not message_identity_match:
|
|
1028
|
+
findings.append("core-runtime.md lacks the message-identity boundary section")
|
|
1029
|
+
else:
|
|
1030
|
+
message_identity_owner = re.sub(r"\s+", " ", message_identity_match.group(1))
|
|
1031
|
+
for index, required in enumerate(MESSAGE_ID_BOUNDARY_REQUIREMENTS):
|
|
1032
|
+
if required not in message_identity_owner:
|
|
1033
|
+
findings.append(f"message-identity boundary missing requirement_{index}")
|
|
1034
|
+
for label, forbidden in MESSAGE_ID_FORBIDDEN.items():
|
|
1035
|
+
if forbidden in message_identity_owner:
|
|
1036
|
+
findings.append(f"message-identity fixed contradiction present {label}")
|
|
1037
|
+
for index, required in enumerate(LIVING_BRIEF_REQUIREMENTS):
|
|
1038
|
+
if required not in core_normalized:
|
|
1039
|
+
findings.append(f"living-brief runtime missing requirement_{index}")
|
|
1040
|
+
for label, forbidden in LIVING_BRIEF_FORBIDDEN.items():
|
|
1041
|
+
if forbidden in core_normalized:
|
|
1042
|
+
findings.append(f"living-brief fixed contradiction present {label}")
|
|
1043
|
+
for label, forbidden in UNEXPECTED_FAILURE_FORBIDDEN.items():
|
|
1044
|
+
if forbidden in normalized_markdown:
|
|
1045
|
+
findings.append(f"unexpected-failure fixed contradiction present {label}")
|
|
1046
|
+
for label, required in DELIVERY_REQUIREMENTS.items():
|
|
1047
|
+
if required not in core_normalized:
|
|
1048
|
+
findings.append(f"delivery gate missing {label}")
|
|
1049
|
+
for label, required in DELIVERY_UAT_REQUIREMENTS.items():
|
|
1050
|
+
if required not in uat:
|
|
1051
|
+
findings.append(f"uat.md missing delivery counterexample {label}")
|
|
1052
|
+
for label, forbidden in SENDABLE_PACKET_FORBIDDEN.items():
|
|
1053
|
+
if forbidden in normalized_markdown:
|
|
1054
|
+
findings.append(f"sendable-packet fixed contradiction present {label}")
|
|
1055
|
+
for label, forbidden in TRUTH_SOURCE_INTAKE_FORBIDDEN.items():
|
|
1056
|
+
if forbidden in normalized_markdown:
|
|
1057
|
+
findings.append(f"truth-source intake fixed contradiction present {label}")
|
|
1058
|
+
for index, required in enumerate(OUTCOME_ANCHOR_REQUIREMENTS):
|
|
1059
|
+
if required not in core_normalized:
|
|
1060
|
+
findings.append(f"outcome-anchor runtime missing requirement_{index}")
|
|
1061
|
+
for label, forbidden in OUTCOME_ANCHOR_FORBIDDEN.items():
|
|
1062
|
+
if forbidden in normalized_markdown:
|
|
1063
|
+
findings.append(f"outcome-anchor fixed contradiction present {label}")
|
|
1064
|
+
for label, forbidden in DRIFT_CHECKPOINT_FORBIDDEN.items():
|
|
1065
|
+
if forbidden in normalized_markdown:
|
|
1066
|
+
findings.append(f"drift-checkpoint fixed contradiction present {label}")
|
|
1067
|
+
for label, forbidden in RESULT_DISPOSITION_FORBIDDEN.items():
|
|
1068
|
+
if forbidden in normalized_markdown:
|
|
1069
|
+
findings.append(f"result-disposition fixed contradiction present {label}")
|
|
1070
|
+
for label, forbidden in EXECUTION_PROFILE_FORBIDDEN.items():
|
|
1071
|
+
if forbidden in normalized_markdown:
|
|
1072
|
+
findings.append(f"execution-profile fixed contradiction present {label}")
|
|
1073
|
+
for label, forbidden in PUBLIC_RUNTIME_LANGUAGE_FORBIDDEN.items():
|
|
1074
|
+
if forbidden in normalized_markdown:
|
|
1075
|
+
findings.append(f"public runtime language fixed contradiction present {label}")
|
|
1076
|
+
if "[parallel-producers.md](references/parallel-producers.md)" not in skill:
|
|
1077
|
+
findings.append("SKILL.md lacks direct progressive-disclosure route")
|
|
1078
|
+
if "長任務防失焦檢查點只由" not in skill:
|
|
1079
|
+
findings.append("SKILL.md lacks drift-checkpoint owner pointer")
|
|
1080
|
+
if "`/CER-auto` 的路線選擇、重判及安全切換只由" not in skill:
|
|
1081
|
+
findings.append("SKILL.md lacks execution-profile owner pointer")
|
|
1082
|
+
if "按該 owner 的單次有界讀取要求載入" not in re.sub(r"\s+", " ", skill):
|
|
1083
|
+
findings.append("SKILL.md lacks the selector single-read owner pointer")
|
|
1084
|
+
if "[平行候選生產者](parallel-producers.md)" not in core:
|
|
1085
|
+
findings.append("core-runtime role summary lacks owner pointer")
|
|
1086
|
+
if "CER 正式角色只有 C、E1、R、E2" not in core:
|
|
1087
|
+
findings.append("core-runtime formal role boundary is incomplete")
|
|
1088
|
+
fifth_role_patterns = (
|
|
1089
|
+
r"CER\s*正式角色只有\s*C、E1、R、E2、",
|
|
1090
|
+
r"(?:producer|生產者|候選生產者|P)\s*(?:是|為|屬於)\s*(?:CER\s*)?正式角色",
|
|
1091
|
+
r"(?:第五|第\s*5)\s*(?:個)?\s*(?:CER\s*)?正式角色",
|
|
1092
|
+
)
|
|
1093
|
+
for pattern in fifth_role_patterns:
|
|
1094
|
+
if re.search(pattern, all_markdown, flags=re.IGNORECASE):
|
|
1095
|
+
findings.append("a fifth formal CER role must not be declared")
|
|
1096
|
+
break
|
|
1097
|
+
if "## 平行候選生產者反證情景" not in uat:
|
|
1098
|
+
findings.append("uat.md lacks bounded producer counterexamples")
|
|
1099
|
+
for label, required in UAT_REQUIREMENTS.items():
|
|
1100
|
+
if required not in uat:
|
|
1101
|
+
findings.append(f"uat.md missing producer counterexample {label}")
|
|
1102
|
+
for label, required in SENDABLE_PACKET_UAT_REQUIREMENTS.items():
|
|
1103
|
+
if required not in uat:
|
|
1104
|
+
findings.append(f"uat.md missing sendable-packet counterexample {label}")
|
|
1105
|
+
for label, required in MESSAGE_ID_UAT_REQUIREMENTS.items():
|
|
1106
|
+
if required not in uat:
|
|
1107
|
+
findings.append(f"uat.md missing message-identity counterexample {label}")
|
|
1108
|
+
for label, required in LIVING_BRIEF_UAT_REQUIREMENTS.items():
|
|
1109
|
+
if required not in uat:
|
|
1110
|
+
findings.append(f"uat.md missing living-brief counterexample {label}")
|
|
1111
|
+
for label, required in TRUTH_SOURCE_INTAKE_UAT_REQUIREMENTS.items():
|
|
1112
|
+
if required not in uat:
|
|
1113
|
+
findings.append(f"uat.md missing truth-source intake counterexample {label}")
|
|
1114
|
+
for label, required in CONTROLLER_CHALLENGE_UAT_REQUIREMENTS.items():
|
|
1115
|
+
if required not in texts["references/uat.md"]:
|
|
1116
|
+
findings.append(f"uat.md missing Controller long-task challenge {label}")
|
|
1117
|
+
if "## 成果錨定與進展情景" not in texts["references/uat.md"]:
|
|
1118
|
+
findings.append("uat.md lacks outcome-anchor progress scenarios")
|
|
1119
|
+
for label, required in OUTCOME_ANCHOR_UAT_REQUIREMENTS.items():
|
|
1120
|
+
if required not in uat:
|
|
1121
|
+
findings.append(f"uat.md missing outcome-anchor counterexample {label}")
|
|
1122
|
+
for label, required in DRIFT_CHECKPOINT_UAT_REQUIREMENTS.items():
|
|
1123
|
+
if required not in uat:
|
|
1124
|
+
findings.append(f"uat.md missing drift-checkpoint counterexample {label}")
|
|
1125
|
+
for label, required in RESULT_DISPOSITION_UAT_REQUIREMENTS.items():
|
|
1126
|
+
if required not in uat:
|
|
1127
|
+
findings.append(f"uat.md missing result-disposition counterexample {label}")
|
|
1128
|
+
if "## 自適應執行強度情景" not in texts["references/uat.md"]:
|
|
1129
|
+
findings.append("uat.md lacks adaptive execution profile scenarios")
|
|
1130
|
+
for label, required in EXECUTION_PROFILE_UAT_REQUIREMENTS.items():
|
|
1131
|
+
if required not in uat:
|
|
1132
|
+
findings.append(f"uat.md missing execution-profile scenario {label}")
|
|
1133
|
+
if "## 公開 runtime 語言邊界情景" not in texts["references/uat.md"]:
|
|
1134
|
+
findings.append("uat.md lacks public runtime language boundary scenarios")
|
|
1135
|
+
for label, required in PUBLIC_RUNTIME_LANGUAGE_UAT_REQUIREMENTS.items():
|
|
1136
|
+
if required not in uat:
|
|
1137
|
+
findings.append(f"uat.md missing public runtime language scenario {label}")
|
|
1138
|
+
unexpected_failure_uat_match = re.search(
|
|
1139
|
+
r"^## 未預期失敗與範圍例外情景[ \t]*\n([\s\S]*?)(?=^## |\Z)",
|
|
1140
|
+
texts["references/uat.md"],
|
|
1141
|
+
re.MULTILINE,
|
|
1142
|
+
)
|
|
1143
|
+
if not unexpected_failure_uat_match:
|
|
1144
|
+
findings.append("uat.md lacks unexpected-failure scope-exception scenarios")
|
|
1145
|
+
else:
|
|
1146
|
+
unexpected_failure_uat = unexpected_failure_uat_match.group(1)
|
|
1147
|
+
for label, marker in UNEXPECTED_FAILURE_UAT_MARKERS.items():
|
|
1148
|
+
if all_markdown.count(marker) != 1 or marker not in unexpected_failure_uat:
|
|
1149
|
+
findings.append(f"unexpected-failure UAT marker invalid {label}")
|
|
1150
|
+
if "平行候選生產者是 C 的內部按需能力,不加入角色欄" not in roadmap:
|
|
1151
|
+
findings.append("roadmap.md lacks display-only producer boundary")
|
|
1152
|
+
for index, required in enumerate(LIVING_BRIEF_ROADMAP_REQUIREMENTS):
|
|
1153
|
+
if required not in roadmap:
|
|
1154
|
+
findings.append(f"roadmap.md missing living-brief display requirement_{index}")
|
|
1155
|
+
for index, required in enumerate(OUTCOME_ANCHOR_ROADMAP_REQUIREMENTS):
|
|
1156
|
+
if required not in roadmap:
|
|
1157
|
+
findings.append(f"roadmap.md missing outcome-anchor display requirement_{index}")
|
|
1158
|
+
for relative in (
|
|
1159
|
+
"references/core-runtime.md",
|
|
1160
|
+
"references/roadmap.md",
|
|
1161
|
+
"references/uat.md",
|
|
1162
|
+
"references/parallel-producers.md",
|
|
1163
|
+
):
|
|
1164
|
+
if "## 目錄" not in texts[relative]:
|
|
1165
|
+
findings.append(f"long reference lacks concise table of contents: {relative}")
|
|
1166
|
+
if "{package_version}" not in roadmap or "{package_version}" not in uat:
|
|
1167
|
+
findings.append("card/UAT templates lack package_version placeholder")
|
|
1168
|
+
if "絕不可把佔位文字原樣顯示" not in roadmap:
|
|
1169
|
+
findings.append("roadmap lacks mandatory package_version substitution boundary")
|
|
1170
|
+
if re.search(r"(?:正式角色|角色欄).{0,40}(?:producer|生產者).{0,20}(?:加入|新增)", roadmap):
|
|
1171
|
+
findings.append("roadmap adds producer to formal role display")
|
|
1172
|
+
return findings
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
def validate(root: Path) -> list[str]:
|
|
1176
|
+
root = root.resolve()
|
|
1177
|
+
actual_files = {
|
|
1178
|
+
path.relative_to(root).as_posix()
|
|
1179
|
+
for path in root.rglob("*")
|
|
1180
|
+
if path.is_file()
|
|
1181
|
+
}
|
|
1182
|
+
unexpected = sorted(actual_files - EXPECTED_FILES)
|
|
1183
|
+
if unexpected:
|
|
1184
|
+
return [f"unexpected package files: {unexpected}"]
|
|
1185
|
+
findings = validate_texts(root, read_texts(root))
|
|
1186
|
+
router_bytes = len((root / "SKILL.md").read_bytes())
|
|
1187
|
+
if router_bytes > MAX_ROUTER_BYTES:
|
|
1188
|
+
findings.append(
|
|
1189
|
+
f"SKILL.md concise-router budget exceeded: {router_bytes} > {MAX_ROUTER_BYTES} bytes"
|
|
1190
|
+
)
|
|
1191
|
+
return findings
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
def mutation_matrix(root: Path) -> tuple[int, list[str]]:
|
|
1195
|
+
baseline = read_texts(root)
|
|
1196
|
+
failures: list[str] = []
|
|
1197
|
+
cases: list[tuple[str, dict[str, str]]] = []
|
|
1198
|
+
|
|
1199
|
+
def mutated(relative: str, old: str, new: str = "") -> dict[str, str]:
|
|
1200
|
+
candidate = copy.deepcopy(baseline)
|
|
1201
|
+
if old not in candidate[relative]:
|
|
1202
|
+
raise RuntimeError(f"self-test anchor missing: {relative}: {old}")
|
|
1203
|
+
candidate[relative] = candidate[relative].replace(old, new, 1)
|
|
1204
|
+
return candidate
|
|
1205
|
+
|
|
1206
|
+
def mutated_all(relative: str, old: str, new: str = "") -> dict[str, str]:
|
|
1207
|
+
candidate = copy.deepcopy(baseline)
|
|
1208
|
+
if old not in candidate[relative]:
|
|
1209
|
+
raise RuntimeError(f"self-test anchor missing: {relative}: {old}")
|
|
1210
|
+
candidate[relative] = candidate[relative].replace(old, new)
|
|
1211
|
+
return candidate
|
|
1212
|
+
|
|
1213
|
+
def mutated_fragment(relative: str, fragment: str) -> dict[str, str]:
|
|
1214
|
+
candidate = copy.deepcopy(baseline)
|
|
1215
|
+
pattern = re.escape(fragment).replace(r"\ ", r"\s+")
|
|
1216
|
+
changed, count = re.subn(pattern, "", candidate[relative])
|
|
1217
|
+
if count < 1:
|
|
1218
|
+
raise RuntimeError(f"self-test normalized anchor missing: {relative}: {fragment}")
|
|
1219
|
+
candidate[relative] = changed
|
|
1220
|
+
return candidate
|
|
1221
|
+
|
|
1222
|
+
cases.append(("version_invalid", mutated("VERSION", baseline["VERSION"], "version\n")))
|
|
1223
|
+
cases.append(
|
|
1224
|
+
(
|
|
1225
|
+
"semver_outside_version",
|
|
1226
|
+
mutated(
|
|
1227
|
+
"references/roadmap.md",
|
|
1228
|
+
"# 使用者停點與路線圖",
|
|
1229
|
+
"# 使用者停點與路線圖 " + ".".join(("9", "9", "9")),
|
|
1230
|
+
),
|
|
1231
|
+
)
|
|
1232
|
+
)
|
|
1233
|
+
cases.append(("frontmatter_extra", mutated("SKILL.md", "name: cer-workflow", "name: cer-workflow\nmetadata: bad")))
|
|
1234
|
+
cases.append(
|
|
1235
|
+
(
|
|
1236
|
+
"extra_slash_command",
|
|
1237
|
+
mutated(
|
|
1238
|
+
"SKILL.md",
|
|
1239
|
+
"| `/CER-help`",
|
|
1240
|
+
"| `/CER-producer` | `producer` | forbidden |\n| `/CER-help`",
|
|
1241
|
+
),
|
|
1242
|
+
)
|
|
1243
|
+
)
|
|
1244
|
+
cases.append(("owner_marker_duplicate", mutated("SKILL.md", "# CER 工作法", f"# CER 工作法\n{OWNER_MARKER}")))
|
|
1245
|
+
cases.append(("owner_marker_missing", mutated("references/parallel-producers.md", OWNER_MARKER)))
|
|
1246
|
+
cases.append(
|
|
1247
|
+
(
|
|
1248
|
+
"unexpected_failure_owner_marker_duplicate",
|
|
1249
|
+
mutated(
|
|
1250
|
+
"SKILL.md",
|
|
1251
|
+
"# CER 工作法",
|
|
1252
|
+
f"# CER 工作法\n{UNEXPECTED_FAILURE_OWNER_MARKER}",
|
|
1253
|
+
),
|
|
1254
|
+
)
|
|
1255
|
+
)
|
|
1256
|
+
cases.append(
|
|
1257
|
+
(
|
|
1258
|
+
"unexpected_failure_owner_marker_missing",
|
|
1259
|
+
mutated("references/core-runtime.md", UNEXPECTED_FAILURE_OWNER_MARKER),
|
|
1260
|
+
)
|
|
1261
|
+
)
|
|
1262
|
+
wrong_section_marker = mutated(
|
|
1263
|
+
"references/core-runtime.md", UNEXPECTED_FAILURE_OWNER_MARKER
|
|
1264
|
+
)
|
|
1265
|
+
wrong_section_marker["references/core-runtime.md"] = wrong_section_marker[
|
|
1266
|
+
"references/core-runtime.md"
|
|
1267
|
+
].replace(
|
|
1268
|
+
"## YAGNI 與停止",
|
|
1269
|
+
f"{UNEXPECTED_FAILURE_OWNER_MARKER}\n## YAGNI 與停止",
|
|
1270
|
+
1,
|
|
1271
|
+
)
|
|
1272
|
+
cases.append(("unexpected_failure_owner_marker_wrong_section", wrong_section_marker))
|
|
1273
|
+
cases.append(
|
|
1274
|
+
(
|
|
1275
|
+
"drift_checkpoint_owner_marker_duplicate",
|
|
1276
|
+
mutated(
|
|
1277
|
+
"SKILL.md",
|
|
1278
|
+
"# CER 工作法",
|
|
1279
|
+
f"# CER 工作法\n{DRIFT_CHECKPOINT_OWNER_MARKER}",
|
|
1280
|
+
),
|
|
1281
|
+
)
|
|
1282
|
+
)
|
|
1283
|
+
cases.append(
|
|
1284
|
+
(
|
|
1285
|
+
"drift_checkpoint_owner_marker_missing",
|
|
1286
|
+
mutated("references/core-runtime.md", DRIFT_CHECKPOINT_OWNER_MARKER),
|
|
1287
|
+
)
|
|
1288
|
+
)
|
|
1289
|
+
drift_wrong_section = mutated(
|
|
1290
|
+
"references/core-runtime.md", DRIFT_CHECKPOINT_OWNER_MARKER
|
|
1291
|
+
)
|
|
1292
|
+
drift_wrong_section["references/core-runtime.md"] = drift_wrong_section[
|
|
1293
|
+
"references/core-runtime.md"
|
|
1294
|
+
].replace(
|
|
1295
|
+
"## YAGNI 與停止",
|
|
1296
|
+
f"{DRIFT_CHECKPOINT_OWNER_MARKER}\n## YAGNI 與停止",
|
|
1297
|
+
1,
|
|
1298
|
+
)
|
|
1299
|
+
cases.append(("drift_checkpoint_owner_marker_wrong_section", drift_wrong_section))
|
|
1300
|
+
cases.append(
|
|
1301
|
+
(
|
|
1302
|
+
"result_disposition_owner_marker_duplicate",
|
|
1303
|
+
mutated(
|
|
1304
|
+
"SKILL.md",
|
|
1305
|
+
"# CER 工作法",
|
|
1306
|
+
f"# CER 工作法\n{RESULT_DISPOSITION_OWNER_MARKER}",
|
|
1307
|
+
),
|
|
1308
|
+
)
|
|
1309
|
+
)
|
|
1310
|
+
cases.append(
|
|
1311
|
+
(
|
|
1312
|
+
"result_disposition_owner_marker_missing",
|
|
1313
|
+
mutated("references/core-runtime.md", RESULT_DISPOSITION_OWNER_MARKER),
|
|
1314
|
+
)
|
|
1315
|
+
)
|
|
1316
|
+
result_disposition_wrong_section = mutated(
|
|
1317
|
+
"references/core-runtime.md", RESULT_DISPOSITION_OWNER_MARKER
|
|
1318
|
+
)
|
|
1319
|
+
result_disposition_wrong_section["references/core-runtime.md"] = result_disposition_wrong_section[
|
|
1320
|
+
"references/core-runtime.md"
|
|
1321
|
+
].replace(
|
|
1322
|
+
"## 自足派工",
|
|
1323
|
+
f"{RESULT_DISPOSITION_OWNER_MARKER}\n## 自足派工",
|
|
1324
|
+
1,
|
|
1325
|
+
)
|
|
1326
|
+
cases.append(("result_disposition_owner_marker_wrong_section", result_disposition_wrong_section))
|
|
1327
|
+
cases.append(
|
|
1328
|
+
(
|
|
1329
|
+
"execution_profile_owner_marker_duplicate",
|
|
1330
|
+
mutated(
|
|
1331
|
+
"SKILL.md",
|
|
1332
|
+
"# CER 工作法",
|
|
1333
|
+
f"# CER 工作法\n{EXECUTION_PROFILE_OWNER_MARKER}",
|
|
1334
|
+
),
|
|
1335
|
+
)
|
|
1336
|
+
)
|
|
1337
|
+
cases.append(
|
|
1338
|
+
(
|
|
1339
|
+
"execution_profile_owner_marker_missing",
|
|
1340
|
+
mutated("references/core-runtime.md", EXECUTION_PROFILE_OWNER_MARKER),
|
|
1341
|
+
)
|
|
1342
|
+
)
|
|
1343
|
+
execution_profile_wrong_section = mutated(
|
|
1344
|
+
"references/core-runtime.md", EXECUTION_PROFILE_OWNER_MARKER
|
|
1345
|
+
)
|
|
1346
|
+
execution_profile_wrong_section["references/core-runtime.md"] = execution_profile_wrong_section[
|
|
1347
|
+
"references/core-runtime.md"
|
|
1348
|
+
].replace(
|
|
1349
|
+
"## YAGNI 與停止",
|
|
1350
|
+
f"{EXECUTION_PROFILE_OWNER_MARKER}\n## YAGNI 與停止",
|
|
1351
|
+
1,
|
|
1352
|
+
)
|
|
1353
|
+
cases.append(("execution_profile_owner_marker_wrong_section", execution_profile_wrong_section))
|
|
1354
|
+
cases.append(
|
|
1355
|
+
(
|
|
1356
|
+
"implicit_invocation_true",
|
|
1357
|
+
mutated("agents/openai.yaml", "allow_implicit_invocation: false", "allow_implicit_invocation: true"),
|
|
1358
|
+
)
|
|
1359
|
+
)
|
|
1360
|
+
cases.append(("default_prompt_missing_skill", mutated("agents/openai.yaml", "$cer-workflow", "CER")))
|
|
1361
|
+
cases.append(
|
|
1362
|
+
(
|
|
1363
|
+
"default_prompt_unconditional_reviewer",
|
|
1364
|
+
mutated(
|
|
1365
|
+
"agents/openai.yaml",
|
|
1366
|
+
"以唯一 writer 執行這項工作;按風險建立 fresh Reviewer",
|
|
1367
|
+
"以唯一 writer 及 fresh Reviewer 執行這項工作",
|
|
1368
|
+
),
|
|
1369
|
+
)
|
|
1370
|
+
)
|
|
1371
|
+
for label, counterexample in REVIEWER_PROPORTIONALITY_COUNTEREXAMPLES.items():
|
|
1372
|
+
cases.append(
|
|
1373
|
+
(
|
|
1374
|
+
f"default_prompt_{label}",
|
|
1375
|
+
mutated(
|
|
1376
|
+
"agents/openai.yaml",
|
|
1377
|
+
"無需額外設定",
|
|
1378
|
+
f"無需額外設定;{counterexample}",
|
|
1379
|
+
),
|
|
1380
|
+
)
|
|
1381
|
+
)
|
|
1382
|
+
cases.append(
|
|
1383
|
+
(
|
|
1384
|
+
"user_prompt_exposes_setup",
|
|
1385
|
+
mutated(
|
|
1386
|
+
"agents/openai.yaml",
|
|
1387
|
+
"無需額外設定",
|
|
1388
|
+
"請設定 producer lane、scratch root 及 hash",
|
|
1389
|
+
),
|
|
1390
|
+
)
|
|
1391
|
+
)
|
|
1392
|
+
cases.append(
|
|
1393
|
+
(
|
|
1394
|
+
"trigger_frontmatter_plain_close_reversed",
|
|
1395
|
+
mutated(
|
|
1396
|
+
"SKILL.md",
|
|
1397
|
+
"單獨「開工/收工」不是 CER 觸發",
|
|
1398
|
+
"單獨「開工/收工」會觸發 CER close",
|
|
1399
|
+
),
|
|
1400
|
+
)
|
|
1401
|
+
)
|
|
1402
|
+
cases.append(
|
|
1403
|
+
(
|
|
1404
|
+
"trigger_skill_auto_row_reversed",
|
|
1405
|
+
mutated(
|
|
1406
|
+
"SKILL.md",
|
|
1407
|
+
"路線裁決前不成立 C",
|
|
1408
|
+
"路線裁決前已成立 C",
|
|
1409
|
+
),
|
|
1410
|
+
)
|
|
1411
|
+
)
|
|
1412
|
+
cases.append(
|
|
1413
|
+
(
|
|
1414
|
+
"trigger_core_auto_row_reversed",
|
|
1415
|
+
mutated(
|
|
1416
|
+
"references/core-runtime.md",
|
|
1417
|
+
"路線裁決前不成立 C",
|
|
1418
|
+
"路線裁決前已成立 C",
|
|
1419
|
+
),
|
|
1420
|
+
)
|
|
1421
|
+
)
|
|
1422
|
+
cases.append(
|
|
1423
|
+
(
|
|
1424
|
+
"trigger_uat_install_auto_reversed",
|
|
1425
|
+
mutated(
|
|
1426
|
+
"references/uat.md",
|
|
1427
|
+
"路線裁決前不成立 C",
|
|
1428
|
+
"路線裁決前已成立 C",
|
|
1429
|
+
),
|
|
1430
|
+
)
|
|
1431
|
+
)
|
|
1432
|
+
cases.append(
|
|
1433
|
+
(
|
|
1434
|
+
"trigger_skill_start_row_reversed",
|
|
1435
|
+
mutated(
|
|
1436
|
+
"SKILL.md",
|
|
1437
|
+
"單獨 `開工` 不啟動 CER",
|
|
1438
|
+
"單獨 `開工` 啟動 CER",
|
|
1439
|
+
),
|
|
1440
|
+
)
|
|
1441
|
+
)
|
|
1442
|
+
cases.append(
|
|
1443
|
+
(
|
|
1444
|
+
"trigger_skill_close_row_reversed",
|
|
1445
|
+
mutated(
|
|
1446
|
+
"SKILL.md",
|
|
1447
|
+
"單獨 `收工` 不觸發 CER close",
|
|
1448
|
+
"單獨 `收工` 觸發 CER close",
|
|
1449
|
+
),
|
|
1450
|
+
)
|
|
1451
|
+
)
|
|
1452
|
+
cases.append(
|
|
1453
|
+
(
|
|
1454
|
+
"trigger_core_start_row_reversed",
|
|
1455
|
+
mutated(
|
|
1456
|
+
"references/core-runtime.md",
|
|
1457
|
+
"單獨 `開工` 不啟動 CER",
|
|
1458
|
+
"單獨 `開工` 啟動 CER",
|
|
1459
|
+
),
|
|
1460
|
+
)
|
|
1461
|
+
)
|
|
1462
|
+
cases.append(
|
|
1463
|
+
(
|
|
1464
|
+
"trigger_core_close_row_reversed",
|
|
1465
|
+
mutated(
|
|
1466
|
+
"references/core-runtime.md",
|
|
1467
|
+
"單獨 `收工` 不觸發 CER close",
|
|
1468
|
+
"單獨 `收工` 觸發 CER close",
|
|
1469
|
+
),
|
|
1470
|
+
)
|
|
1471
|
+
)
|
|
1472
|
+
cases.append(
|
|
1473
|
+
(
|
|
1474
|
+
"trigger_core_startup_owner_reversed",
|
|
1475
|
+
mutated(
|
|
1476
|
+
"references/core-runtime.md",
|
|
1477
|
+
"單獨 `開工` 屬於目標 workspace 既有治理,不是 CER trigger",
|
|
1478
|
+
"單獨 `開工` 屬於 CER trigger",
|
|
1479
|
+
),
|
|
1480
|
+
)
|
|
1481
|
+
)
|
|
1482
|
+
cases.append(
|
|
1483
|
+
(
|
|
1484
|
+
"trigger_core_stop_owner_reversed",
|
|
1485
|
+
mutated(
|
|
1486
|
+
"references/core-runtime.md",
|
|
1487
|
+
"單獨 `收工` 屬於目標 workspace 既有治理,不映射為 CER stop 或 close",
|
|
1488
|
+
"單獨 `收工` 屬於 CER 指令,映射為 CER stop 或 close",
|
|
1489
|
+
),
|
|
1490
|
+
)
|
|
1491
|
+
)
|
|
1492
|
+
cases.append(
|
|
1493
|
+
(
|
|
1494
|
+
"trigger_uat_install_start_reversed",
|
|
1495
|
+
mutated(
|
|
1496
|
+
"references/uat.md",
|
|
1497
|
+
"單獨 `開工` 不觸發 CER",
|
|
1498
|
+
"單獨 `開工` 觸發 CER",
|
|
1499
|
+
),
|
|
1500
|
+
)
|
|
1501
|
+
)
|
|
1502
|
+
cases.append(
|
|
1503
|
+
(
|
|
1504
|
+
"trigger_uat_install_close_reversed",
|
|
1505
|
+
mutated(
|
|
1506
|
+
"references/uat.md",
|
|
1507
|
+
"單獨 `收工` 不觸發 CER close,也不映射為 `/CER-stop`",
|
|
1508
|
+
"單獨 `收工` 觸發 CER close,並映射為 `/CER-stop`",
|
|
1509
|
+
),
|
|
1510
|
+
)
|
|
1511
|
+
)
|
|
1512
|
+
cases.append(
|
|
1513
|
+
(
|
|
1514
|
+
"trigger_uat_failure_condition_lost",
|
|
1515
|
+
mutated(
|
|
1516
|
+
"references/uat.md",
|
|
1517
|
+
"單獨 `開工` 啟動 CER,或單獨 `收工` 觸發 CER close/stop",
|
|
1518
|
+
"單獨 `開工` 不啟動 CER,且單獨 `收工` 不觸發 CER close/stop",
|
|
1519
|
+
),
|
|
1520
|
+
)
|
|
1521
|
+
)
|
|
1522
|
+
cases.append(
|
|
1523
|
+
(
|
|
1524
|
+
"unprovided_dependency",
|
|
1525
|
+
mutated("agents/openai.yaml", "policy:", "dependencies:\n tools: []\npolicy:"),
|
|
1526
|
+
)
|
|
1527
|
+
)
|
|
1528
|
+
cases.append(
|
|
1529
|
+
(
|
|
1530
|
+
"missing_progressive_link",
|
|
1531
|
+
mutated_all(
|
|
1532
|
+
"SKILL.md",
|
|
1533
|
+
"[parallel-producers.md](references/parallel-producers.md)",
|
|
1534
|
+
"parallel producers",
|
|
1535
|
+
),
|
|
1536
|
+
)
|
|
1537
|
+
)
|
|
1538
|
+
for label, fragment in OWNER_REQUIREMENTS.items():
|
|
1539
|
+
cases.append(
|
|
1540
|
+
(
|
|
1541
|
+
f"owner_missing_{label}",
|
|
1542
|
+
mutated_fragment("references/parallel-producers.md", fragment),
|
|
1543
|
+
)
|
|
1544
|
+
)
|
|
1545
|
+
for label, fragment in UAT_REQUIREMENTS.items():
|
|
1546
|
+
cases.append(
|
|
1547
|
+
(f"uat_missing_{label}", mutated_fragment("references/uat.md", fragment))
|
|
1548
|
+
)
|
|
1549
|
+
for label, fragment in UNEXPECTED_FAILURE_REQUIREMENTS.items():
|
|
1550
|
+
cases.append(
|
|
1551
|
+
(
|
|
1552
|
+
f"unexpected_failure_owner_missing_{label}",
|
|
1553
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1554
|
+
)
|
|
1555
|
+
)
|
|
1556
|
+
for label, fragment in DELIVERY_REQUIREMENTS.items():
|
|
1557
|
+
cases.append(
|
|
1558
|
+
(
|
|
1559
|
+
f"delivery_gate_missing_{label}",
|
|
1560
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1561
|
+
)
|
|
1562
|
+
)
|
|
1563
|
+
for label, fragment in DELIVERY_UAT_REQUIREMENTS.items():
|
|
1564
|
+
cases.append(
|
|
1565
|
+
(
|
|
1566
|
+
f"delivery_uat_missing_{label}",
|
|
1567
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1568
|
+
)
|
|
1569
|
+
)
|
|
1570
|
+
for label, fragment in TRUTH_SOURCE_INTAKE_REQUIREMENTS.items():
|
|
1571
|
+
cases.append(
|
|
1572
|
+
(
|
|
1573
|
+
f"truth_source_intake_owner_missing_{label}",
|
|
1574
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1575
|
+
)
|
|
1576
|
+
)
|
|
1577
|
+
for label, fragment in DRIFT_CHECKPOINT_REQUIREMENTS.items():
|
|
1578
|
+
cases.append(
|
|
1579
|
+
(
|
|
1580
|
+
f"drift_checkpoint_owner_missing_{label}",
|
|
1581
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1582
|
+
)
|
|
1583
|
+
)
|
|
1584
|
+
for label, fragment in RESULT_DISPOSITION_REQUIREMENTS.items():
|
|
1585
|
+
cases.append(
|
|
1586
|
+
(
|
|
1587
|
+
f"result_disposition_owner_missing_{label}",
|
|
1588
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1589
|
+
)
|
|
1590
|
+
)
|
|
1591
|
+
for label, fragment in EXECUTION_PROFILE_REQUIREMENTS.items():
|
|
1592
|
+
cases.append(
|
|
1593
|
+
(
|
|
1594
|
+
f"execution_profile_owner_missing_{label}",
|
|
1595
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1596
|
+
)
|
|
1597
|
+
)
|
|
1598
|
+
for label, fragment in PUBLIC_RUNTIME_LANGUAGE_REQUIREMENTS.items():
|
|
1599
|
+
cases.append(
|
|
1600
|
+
(
|
|
1601
|
+
f"public_runtime_language_owner_missing_{label}",
|
|
1602
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1603
|
+
)
|
|
1604
|
+
)
|
|
1605
|
+
for label, fragment in SENDABLE_PACKET_REQUIREMENTS.items():
|
|
1606
|
+
cases.append(
|
|
1607
|
+
(
|
|
1608
|
+
f"sendable_packet_owner_missing_{label}",
|
|
1609
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1610
|
+
)
|
|
1611
|
+
)
|
|
1612
|
+
for index, fragment in enumerate(MESSAGE_ID_BOUNDARY_REQUIREMENTS):
|
|
1613
|
+
cases.append(
|
|
1614
|
+
(
|
|
1615
|
+
f"message_id_owner_missing_{index}",
|
|
1616
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1617
|
+
)
|
|
1618
|
+
)
|
|
1619
|
+
for index, fragment in enumerate(LIVING_BRIEF_REQUIREMENTS):
|
|
1620
|
+
cases.append(
|
|
1621
|
+
(
|
|
1622
|
+
f"living_brief_runtime_missing_{index}",
|
|
1623
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1624
|
+
)
|
|
1625
|
+
)
|
|
1626
|
+
for index, fragment in enumerate(LIVING_BRIEF_ROADMAP_REQUIREMENTS):
|
|
1627
|
+
cases.append(
|
|
1628
|
+
(
|
|
1629
|
+
f"living_brief_roadmap_missing_{index}",
|
|
1630
|
+
mutated_fragment("references/roadmap.md", fragment),
|
|
1631
|
+
)
|
|
1632
|
+
)
|
|
1633
|
+
for index, fragment in enumerate(OUTCOME_ANCHOR_REQUIREMENTS):
|
|
1634
|
+
cases.append(
|
|
1635
|
+
(
|
|
1636
|
+
f"outcome_anchor_runtime_missing_{index}",
|
|
1637
|
+
mutated_fragment("references/core-runtime.md", fragment),
|
|
1638
|
+
)
|
|
1639
|
+
)
|
|
1640
|
+
for index, fragment in enumerate(OUTCOME_ANCHOR_ROADMAP_REQUIREMENTS):
|
|
1641
|
+
cases.append(
|
|
1642
|
+
(
|
|
1643
|
+
f"outcome_anchor_roadmap_missing_{index}",
|
|
1644
|
+
mutated_fragment("references/roadmap.md", fragment),
|
|
1645
|
+
)
|
|
1646
|
+
)
|
|
1647
|
+
for label, fragment in SENDABLE_PACKET_UAT_REQUIREMENTS.items():
|
|
1648
|
+
cases.append(
|
|
1649
|
+
(
|
|
1650
|
+
f"sendable_packet_uat_missing_{label}",
|
|
1651
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1652
|
+
)
|
|
1653
|
+
)
|
|
1654
|
+
for label, fragment in MESSAGE_ID_UAT_REQUIREMENTS.items():
|
|
1655
|
+
cases.append(
|
|
1656
|
+
(
|
|
1657
|
+
f"message_id_uat_missing_{label}",
|
|
1658
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1659
|
+
)
|
|
1660
|
+
)
|
|
1661
|
+
for label, fragment in LIVING_BRIEF_UAT_REQUIREMENTS.items():
|
|
1662
|
+
cases.append(
|
|
1663
|
+
(
|
|
1664
|
+
f"living_brief_uat_missing_{label}",
|
|
1665
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1666
|
+
)
|
|
1667
|
+
)
|
|
1668
|
+
for label, fragment in TRUTH_SOURCE_INTAKE_UAT_REQUIREMENTS.items():
|
|
1669
|
+
cases.append(
|
|
1670
|
+
(
|
|
1671
|
+
f"truth_source_intake_uat_missing_{label}",
|
|
1672
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1673
|
+
)
|
|
1674
|
+
)
|
|
1675
|
+
for label, fragment in CONTROLLER_CHALLENGE_UAT_REQUIREMENTS.items():
|
|
1676
|
+
cases.append(
|
|
1677
|
+
(
|
|
1678
|
+
f"controller_challenge_uat_missing_{label}",
|
|
1679
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1680
|
+
)
|
|
1681
|
+
)
|
|
1682
|
+
for label, fragment in OUTCOME_ANCHOR_UAT_REQUIREMENTS.items():
|
|
1683
|
+
cases.append(
|
|
1684
|
+
(
|
|
1685
|
+
f"outcome_anchor_uat_missing_{label}",
|
|
1686
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1687
|
+
)
|
|
1688
|
+
)
|
|
1689
|
+
for label, fragment in DRIFT_CHECKPOINT_UAT_REQUIREMENTS.items():
|
|
1690
|
+
cases.append(
|
|
1691
|
+
(
|
|
1692
|
+
f"drift_checkpoint_uat_missing_{label}",
|
|
1693
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1694
|
+
)
|
|
1695
|
+
)
|
|
1696
|
+
for label, fragment in RESULT_DISPOSITION_UAT_REQUIREMENTS.items():
|
|
1697
|
+
cases.append(
|
|
1698
|
+
(
|
|
1699
|
+
f"result_disposition_uat_missing_{label}",
|
|
1700
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1701
|
+
)
|
|
1702
|
+
)
|
|
1703
|
+
for label, fragment in EXECUTION_PROFILE_UAT_REQUIREMENTS.items():
|
|
1704
|
+
cases.append(
|
|
1705
|
+
(
|
|
1706
|
+
f"execution_profile_uat_missing_{label}",
|
|
1707
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1708
|
+
)
|
|
1709
|
+
)
|
|
1710
|
+
for label, fragment in PUBLIC_RUNTIME_LANGUAGE_UAT_REQUIREMENTS.items():
|
|
1711
|
+
cases.append(
|
|
1712
|
+
(
|
|
1713
|
+
f"public_runtime_language_uat_missing_{label}",
|
|
1714
|
+
mutated_fragment("references/uat.md", fragment),
|
|
1715
|
+
)
|
|
1716
|
+
)
|
|
1717
|
+
for label, marker in UNEXPECTED_FAILURE_UAT_MARKERS.items():
|
|
1718
|
+
cases.append(
|
|
1719
|
+
(
|
|
1720
|
+
f"unexpected_failure_uat_marker_missing_{label}",
|
|
1721
|
+
mutated("references/uat.md", marker),
|
|
1722
|
+
)
|
|
1723
|
+
)
|
|
1724
|
+
for label, contradiction in UNEXPECTED_FAILURE_FORBIDDEN.items():
|
|
1725
|
+
cases.append(
|
|
1726
|
+
(
|
|
1727
|
+
f"unexpected_failure_contradiction_{label}",
|
|
1728
|
+
mutated(
|
|
1729
|
+
"references/core-runtime.md",
|
|
1730
|
+
"## 執行閉環",
|
|
1731
|
+
f"## 執行閉環\n\n{contradiction}。",
|
|
1732
|
+
),
|
|
1733
|
+
)
|
|
1734
|
+
)
|
|
1735
|
+
for label, contradiction in SENDABLE_PACKET_FORBIDDEN.items():
|
|
1736
|
+
cases.append(
|
|
1737
|
+
(
|
|
1738
|
+
f"sendable_packet_contradiction_{label}",
|
|
1739
|
+
mutated(
|
|
1740
|
+
"references/core-runtime.md",
|
|
1741
|
+
"## 自足派工",
|
|
1742
|
+
f"## 自足派工\n\n{contradiction}。",
|
|
1743
|
+
),
|
|
1744
|
+
)
|
|
1745
|
+
)
|
|
1746
|
+
for label, contradiction in MESSAGE_ID_FORBIDDEN.items():
|
|
1747
|
+
cases.append(
|
|
1748
|
+
(
|
|
1749
|
+
f"message_id_contradiction_{label}",
|
|
1750
|
+
mutated(
|
|
1751
|
+
"references/core-runtime.md",
|
|
1752
|
+
"## 工具結果不明、角色對帳與批次去重",
|
|
1753
|
+
f"## 工具結果不明、角色對帳與批次去重\n\n{contradiction}。",
|
|
1754
|
+
),
|
|
1755
|
+
)
|
|
1756
|
+
)
|
|
1757
|
+
for label, contradiction in LIVING_BRIEF_FORBIDDEN.items():
|
|
1758
|
+
cases.append(
|
|
1759
|
+
(
|
|
1760
|
+
f"living_brief_contradiction_{label}",
|
|
1761
|
+
mutated(
|
|
1762
|
+
"references/core-runtime.md",
|
|
1763
|
+
"## Controller preflight",
|
|
1764
|
+
f"## Controller preflight\n\n{contradiction}。",
|
|
1765
|
+
),
|
|
1766
|
+
)
|
|
1767
|
+
)
|
|
1768
|
+
for label, contradiction in TRUTH_SOURCE_INTAKE_FORBIDDEN.items():
|
|
1769
|
+
cases.append(
|
|
1770
|
+
(
|
|
1771
|
+
f"truth_source_intake_contradiction_{label}",
|
|
1772
|
+
mutated(
|
|
1773
|
+
"references/core-runtime.md",
|
|
1774
|
+
"## Controller preflight",
|
|
1775
|
+
f"## Controller preflight\n\n{contradiction}。",
|
|
1776
|
+
),
|
|
1777
|
+
)
|
|
1778
|
+
)
|
|
1779
|
+
for label, contradiction in OUTCOME_ANCHOR_FORBIDDEN.items():
|
|
1780
|
+
cases.append(
|
|
1781
|
+
(
|
|
1782
|
+
f"outcome_anchor_contradiction_{label}",
|
|
1783
|
+
mutated(
|
|
1784
|
+
"references/core-runtime.md",
|
|
1785
|
+
"## 成果錨定與進展閘",
|
|
1786
|
+
f"## 成果錨定與進展閘\n\n{contradiction}。",
|
|
1787
|
+
),
|
|
1788
|
+
)
|
|
1789
|
+
)
|
|
1790
|
+
for label, contradiction in DRIFT_CHECKPOINT_FORBIDDEN.items():
|
|
1791
|
+
cases.append(
|
|
1792
|
+
(
|
|
1793
|
+
f"drift_checkpoint_contradiction_{label}",
|
|
1794
|
+
mutated(
|
|
1795
|
+
"references/core-runtime.md",
|
|
1796
|
+
"## 成果錨定與進展閘",
|
|
1797
|
+
f"## 成果錨定與進展閘\n\n{contradiction}。",
|
|
1798
|
+
),
|
|
1799
|
+
)
|
|
1800
|
+
)
|
|
1801
|
+
for label, contradiction in RESULT_DISPOSITION_FORBIDDEN.items():
|
|
1802
|
+
cases.append(
|
|
1803
|
+
(
|
|
1804
|
+
f"result_disposition_contradiction_{label}",
|
|
1805
|
+
mutated(
|
|
1806
|
+
"references/core-runtime.md",
|
|
1807
|
+
"## 成果錨定與進展閘",
|
|
1808
|
+
f"## 成果錨定與進展閘\n\n{contradiction}。",
|
|
1809
|
+
),
|
|
1810
|
+
)
|
|
1811
|
+
)
|
|
1812
|
+
for label, contradiction in EXECUTION_PROFILE_FORBIDDEN.items():
|
|
1813
|
+
cases.append(
|
|
1814
|
+
(
|
|
1815
|
+
f"execution_profile_contradiction_{label}",
|
|
1816
|
+
mutated(
|
|
1817
|
+
"references/core-runtime.md",
|
|
1818
|
+
"## 執行強度閘門",
|
|
1819
|
+
f"## 執行強度閘門\n\n{contradiction}。",
|
|
1820
|
+
),
|
|
1821
|
+
)
|
|
1822
|
+
)
|
|
1823
|
+
for label, contradiction in PUBLIC_RUNTIME_LANGUAGE_FORBIDDEN.items():
|
|
1824
|
+
cases.append(
|
|
1825
|
+
(
|
|
1826
|
+
f"public_runtime_language_contradiction_{label}",
|
|
1827
|
+
mutated(
|
|
1828
|
+
"references/core-runtime.md",
|
|
1829
|
+
"## 公開 runtime 語言邊界",
|
|
1830
|
+
f"## 公開 runtime 語言邊界\n\n{contradiction}。",
|
|
1831
|
+
),
|
|
1832
|
+
)
|
|
1833
|
+
)
|
|
1834
|
+
cases.append(
|
|
1835
|
+
(
|
|
1836
|
+
"roadmap_adds_producer_role",
|
|
1837
|
+
mutated(
|
|
1838
|
+
"references/roadmap.md",
|
|
1839
|
+
"不加入角色欄、生命週期卡",
|
|
1840
|
+
"加入角色欄、生命週期卡",
|
|
1841
|
+
),
|
|
1842
|
+
)
|
|
1843
|
+
)
|
|
1844
|
+
cases.append(
|
|
1845
|
+
(
|
|
1846
|
+
"fifth_formal_role",
|
|
1847
|
+
mutated(
|
|
1848
|
+
"references/core-runtime.md",
|
|
1849
|
+
"CER 正式角色只有 C、E1、R、E2",
|
|
1850
|
+
"CER 正式角色只有 C、E1、R、E2、P",
|
|
1851
|
+
),
|
|
1852
|
+
)
|
|
1853
|
+
)
|
|
1854
|
+
|
|
1855
|
+
for name, candidate in cases:
|
|
1856
|
+
if not validate_texts(root, candidate):
|
|
1857
|
+
failures.append(name)
|
|
1858
|
+
return len(cases), failures
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
def main() -> int:
|
|
1862
|
+
parser = argparse.ArgumentParser(description="Validate the CER skill package")
|
|
1863
|
+
parser.add_argument("root", nargs="?", default=".", help="CER skill root")
|
|
1864
|
+
parser.add_argument("--self-test", action="store_true", help="run in-memory mutation matrix")
|
|
1865
|
+
args = parser.parse_args()
|
|
1866
|
+
root = Path(args.root).resolve()
|
|
1867
|
+
findings = validate(root)
|
|
1868
|
+
if findings:
|
|
1869
|
+
for finding in findings:
|
|
1870
|
+
print(f"FAIL: {finding}")
|
|
1871
|
+
print(f"status: failed ({len(findings)} findings)")
|
|
1872
|
+
return 1
|
|
1873
|
+
print("status: passed")
|
|
1874
|
+
print(f"version: {(root / 'VERSION').read_text(encoding='utf-8-sig').strip()}")
|
|
1875
|
+
print(f"files: {len(EXPECTED_FILES)}")
|
|
1876
|
+
if args.self_test:
|
|
1877
|
+
count, failures = mutation_matrix(root)
|
|
1878
|
+
print(f"mutation_cases: {count}")
|
|
1879
|
+
if failures:
|
|
1880
|
+
print(f"FAIL: mutation false-green: {failures}")
|
|
1881
|
+
return 1
|
|
1882
|
+
print("mutation_status: passed")
|
|
1883
|
+
return 0
|
|
1884
|
+
|
|
1885
|
+
|
|
1886
|
+
if __name__ == "__main__":
|
|
1887
|
+
sys.exit(main())
|