@pzy560117/opentest 0.1.13 → 0.1.14

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.13",
2
+ "version": "0.1.14",
3
3
  "languages": [
4
4
  {
5
5
  "id": "en",
@@ -70,6 +70,7 @@
70
70
  "opentest/references/matrix-format.md",
71
71
  "opentest/references/opentest-driven-development.md",
72
72
  "opentest/references/quality-gate.md",
73
+ "opentest/references/rules-precedence.md",
73
74
  "opentest/references/test-asset-layout.md",
74
75
  "opentest/references/test-surfaces.md",
75
76
  "opentest/references/web-browser-testing.md",
@@ -5,13 +5,14 @@ description: "OpenTest router."
5
5
 
6
6
  # OpenTest
7
7
 
8
- OpenTest is the single user entry. The AI routes phases and surface adapters.
8
+ OpenTest is the single user entry for phase and adapter routing.
9
9
 
10
10
  ## Required references
11
11
 
12
12
  - `opentest/references/command-routing.md`
13
13
  - `opentest/references/lifecycle.md`
14
14
  - `opentest/references/complete-testing-workflow.md`
15
+ - `opentest/references/rules-precedence.md`
15
16
  - `opentest/references/test-surfaces.md`
16
17
 
17
18
  ## Bootstrap
@@ -30,5 +31,5 @@ Stop if scripts are missing.
30
31
  1. If `.opentest.yaml` is missing, run `bash "$OPENTEST_STATE" init`.
31
32
  2. Run `bash "$OPENTEST_STATE" check` and `bash "$OPENTEST_DETECT" summary`.
32
33
  3. Dispatch by `phase` to `opentest-plan`, `opentest-author`, `opentest-run`, `opentest-accept`, `opentest-verify`, `opentest-heal`, or `opentest-archive`.
33
- 4. `plan/run/accept` choose adapters from matrix surface: `web-browser` -> `opentest-web-browser`, `android-app` -> `opentest-android-app`, `desktop-gui` -> `opentest-desktop-gui`, `api` -> `opentest-api`.
34
+ 4. `plan/run/accept` choose adapters from matrix surface: `web-browser`, `android-app`, `desktop-gui`, or `api`.
34
35
  5. Ask only when surface cannot be inferred. Use installed skill language.
@@ -0,0 +1,16 @@
1
+ # Rules Precedence
2
+
3
+ OpenTest is a testing lifecycle skill. When a project invokes OpenTest or has an active `.opentest.yaml`, the selected execution surface defines the required evidence chain.
4
+
5
+ Project or global rules that say not to create test-framework code by default are restraint rules for ad hoc acceptance. They prevent unnecessary scaffolding before a testing workflow is selected. They do not disable OpenTest evidence contracts.
6
+
7
+ Once a matrix row selects an execution surface, its framework route is part of required evidence:
8
+
9
+ - `android-app`: `pytest` is the user-facing orchestration entry. `android-midscene-pytest`, ADB, screenshots, logcat, and Midscene reports are evidence contracts, not optional scaffolding.
10
+ - `web-browser`: `instant-acceptance` can use MCP or Playwright CLI; `durable-regression` uses the project E2E framework or `@playwright/test`.
11
+ - `api`: project API commands are preferred; without them, use `pytest` with HTTP/schema/fixture evidence.
12
+ - `desktop-gui`: use project GUI automation or `@midscene/computer` when visual desktop automation is required.
13
+
14
+ Do not set `test_framework: none` to satisfy a generic framework-restraint rule while the matrix requires a framework-backed execution surface. If a project rule explicitly forbids the required framework, record the conflict as `blocked` or ask for a decision. Do not silently downgrade required evidence.
15
+
16
+ `test_framework: none` is valid only when the matrix has no framework-backed execution surface and no required evidence that depends on pytest, Playwright, Midscene, or a project test harness.
@@ -52,6 +52,39 @@ check_result() {
52
52
  fi
53
53
  }
54
54
 
55
+ matrix_path() {
56
+ yaml_field matrix
57
+ }
58
+
59
+ matrix_has() {
60
+ local pattern="$1"
61
+ local matrix
62
+ matrix=$(matrix_path)
63
+ file_nonempty "$matrix" && grep -Eqi "$pattern" "$matrix"
64
+ }
65
+
66
+ framework_matches_matrix() {
67
+ local framework
68
+ framework=$(yaml_field test_framework)
69
+ framework="${framework:-pytest}"
70
+
71
+ if matrix_has 'android-app|opentest-android-app|android-midscene-pytest'; then
72
+ if [ "$framework" != "pytest" ]; then
73
+ red " [FAIL] android-app execution surface requires test_framework=pytest"
74
+ return 1
75
+ fi
76
+ fi
77
+
78
+ if [ "$framework" = "none" ]; then
79
+ if matrix_has 'python[[:space:]]+-m[[:space:]]+pytest|pytest report|pytest 报告|@playwright/test|npx[[:space:]]+playwright[[:space:]]+test|durable-regression|midscene|opentest-api|opentest-desktop-gui'; then
80
+ red " [FAIL] test_framework=none conflicts with framework-backed matrix evidence"
81
+ return 1
82
+ fi
83
+ fi
84
+
85
+ return 0
86
+ }
87
+
55
88
  result_is_archivable() {
56
89
  local result
57
90
  result=$(yaml_field verification_result)
@@ -61,6 +94,7 @@ result_is_archivable() {
61
94
  guard_plan() {
62
95
  check_path "plan exists" "$(yaml_field plan)"
63
96
  check_path "matrix exists" "$(yaml_field matrix)"
97
+ check_result "test_framework matches matrix execution surfaces" framework_matches_matrix
64
98
  }
65
99
 
66
100
  guard_author() {
@@ -73,10 +107,12 @@ guard_author() {
73
107
  else
74
108
  check_path "matrix exists for recorded gaps" "$matrix"
75
109
  fi
110
+ check_result "test_framework matches matrix execution surfaces" framework_matches_matrix
76
111
  }
77
112
 
78
113
  guard_run() {
79
114
  check_path "run report exists" "$(yaml_field run_report)"
115
+ check_result "test_framework matches matrix execution surfaces" framework_matches_matrix
80
116
  }
81
117
 
82
118
  guard_accept() {
@@ -89,6 +125,7 @@ guard_accept() {
89
125
  else
90
126
  check_path "run report exists for recorded no-acceptance path" "$run_report"
91
127
  fi
128
+ check_result "test_framework matches matrix execution surfaces" framework_matches_matrix
92
129
  }
93
130
 
94
131
  guard_verify() {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: opentest-accept
3
- description: "OpenTest phase 4: execute acceptance and write back evidence."
3
+ description: "OpenTest phase 4: execute acceptance and evidence."
4
4
  ---
5
5
 
6
6
  # OpenTest Accept
@@ -11,6 +11,7 @@ Write PASS, FAIL, or blocked evidence to cases and matrix.
11
11
 
12
12
  - `opentest/references/acceptance-evidence.md`
13
13
  - `opentest/references/complete-testing-workflow.md`
14
+ - `opentest/references/rules-precedence.md`
14
15
  - `opentest/references/test-surfaces.md`
15
16
  - `opentest/references/android-app-testing.md`
16
17
  - `opentest/references/web-browser-testing.md`
@@ -25,10 +26,10 @@ Write PASS, FAIL, or blocked evidence to cases and matrix.
25
26
 
26
27
  1. Read the matrix, fixtures, and `docs/opentest/acceptance/`.
27
28
  2. Select the acceptance tool from the matrix execution surface.
28
- 3. For `web-browser`, use `opentest-web-browser`: Playwright MCP/CLI, `@playwright/test` for durable regression, Midscene only for visual assist.
29
- 4. For `android-app`, use `opentest-android-app` and `android-midscene-pytest`: `python -m pytest tests_py -v`, ADB smoke, Midscene HTML, logcat, and `midscene_run`; block missing prerequisites.
30
- 5. For `desktop-gui`, use `opentest-desktop-gui`: project GUI automation or `@midscene/computer`, screenshot/recording, metadata, and read-back.
31
- 6. For `api`, use `opentest-api`: project API command or `pytest` with `httpx`/`requests`, schema checks, fixtures, read-after-write, and cleanup/teardown.
29
+ 3. `web-browser`: Playwright MCP/CLI; `@playwright/test` for durable regression; Midscene only for visual assist.
30
+ 4. `android-app`: `opentest-android-app` plus `android-midscene-pytest`, `python -m pytest tests_py -v`, ADB smoke, Midscene HTML, logcat, and `midscene_run`; block missing prerequisites.
31
+ 5. `desktop-gui`: `opentest-desktop-gui`, project GUI automation or `@midscene/computer`, screenshot/recording, metadata, and read-back.
32
+ 6. `api`: `opentest-api`, project API command or `pytest` with `httpx`/`requests`, schema, fixtures, read-after-write, and cleanup/teardown.
32
33
  7. For CRUD/data changes, execute the full chain from the workflow reference.
33
34
  8. Record feedback location/shape, artifacts, blocked evidence, and matching ACC IDs.
34
35
  9. Update acceptance records and run `bash "$OPENTEST_GUARD" accept --apply`.
@@ -11,6 +11,7 @@ Turn the matrix into executable tests, fixtures, seed/teardown notes, and accept
11
11
 
12
12
  - `opentest/references/opentest-driven-development.md`
13
13
  - `opentest/references/complete-testing-workflow.md`
14
+ - `opentest/references/rules-precedence.md`
14
15
  - `opentest/references/test-asset-layout.md`
15
16
  - `opentest/templates/fixtures-template.md`
16
17
  - `opentest/templates/acceptance-template.md`
@@ -19,7 +20,7 @@ Turn the matrix into executable tests, fixtures, seed/teardown notes, and accept
19
20
 
20
21
  1. Read `matrix` and `fixtures` from `.opentest.yaml`.
21
22
  2. Preserve each row's requirement source and expected behavior. Do not rewrite acceptance cases around current implementation names, component internals, or existing test files.
22
- 3. Place assets in the fixed layout from `test-asset-layout.md`; default to pytest under `tests/` when no project framework exists. Missing implementation means evidence stays pending.
23
+ 3. Place assets in the fixed layout from `test-asset-layout.md`; default to pytest under `tests/` when no project framework exists, and do not convert required framework evidence to `none`. Missing implementation means evidence stays pending.
23
24
  4. Create/update fixtures, seed, teardown, users, roles, entities, files/images, and assertion surfaces.
24
25
  5. For CRUD/data changes, author the full acceptance flow: create -> list -> detail -> update -> read back -> delete -> confirm absence -> teardown.
25
26
  6. Record any gap/blocker with reason and risk.
@@ -1,17 +1,16 @@
1
1
  ---
2
2
  name: opentest-plan
3
- description: "OpenTest phase 1: create test strategy and acceptance matrix."
3
+ description: "OpenTest phase 1: create strategy and matrix."
4
4
  ---
5
5
 
6
6
  # OpenTest Plan
7
7
 
8
- Create plan, matrix, and fixtures before implementation.
9
-
10
8
  ## Required references
11
9
 
12
10
  - `opentest/references/codex-harness-coverage-heuristics.md`
13
11
  - `opentest/references/matrix-format.md`
14
12
  - `opentest/references/complete-testing-workflow.md`
13
+ - `opentest/references/rules-precedence.md`
15
14
  - `opentest/references/test-asset-layout.md`
16
15
  - `opentest/references/test-surfaces.md`
17
16
  - `opentest/references/android-app-testing.md`
@@ -21,14 +20,15 @@ Create plan, matrix, and fixtures before implementation.
21
20
 
22
21
  ## Steps
23
22
 
24
- 1. Read rules, requirements/diff, commands, and detection output.
25
- 2. Treat requirements and risks as sources; inspect current code only for project facts.
26
- 3. Apply CRUD baseline and test data rules for data-writing/API/form/file/stateful changes.
27
- 4. Classify execution surface and evidence layer separately: `web-browser`, `android-app`, `desktop-gui`, or `api`; use `opentest-android-app`, web modes, `opentest-desktop-gui`, and `opentest-api` where applicable.
28
- 5. Produce a requirement-first matrix with source, behavior, surface, mode, evidence, command, gap/blocker, status, and fixed layout.
29
- 6. Write `.opentest.yaml` fields: `plan`, `matrix`, `fixtures`.
30
- 7. Update handoff if present, then run `bash "$OPENTEST_GUARD" plan --apply`.
23
+ 1. Read rules, req/diff, commands, detect output, and `rules-precedence.md`.
24
+ 2. Treat requirements and risks as sources; use current code only for project facts.
25
+ 3. Apply CRUD baseline and test data for data/API/form/file/state changes.
26
+ 4. Classify execution surface and evidence layer separately: `web-browser`, `android-app`, `desktop-gui`, or `api`.
27
+ 5. If rules restrain frameworks, decide whether OpenTest's surface is the explicit framework requirement; conflicts are blocker, not `test_framework: none`.
28
+ 6. Produce a requirement-first matrix with source, behavior, surface, mode, evidence, command, gap, status, and layout.
29
+ 7. Write `.opentest.yaml`: `plan`, `matrix`, `fixtures`, and surface-consistent `test_framework`.
30
+ 8. Update handoff if present, then run `bash "$OPENTEST_GUARD" plan --apply`.
31
31
 
32
32
  ## Gate
33
33
 
34
- Every behavior, failure path, boundary, and risk needs evidence or gap/blocker. Every row cites a source and includes surface plus evidence layer; web rows include acceptance mode. Do not use unit/component/integration/contract/smoke as the execution surface. Do not drop or narrow acceptance because current code has no matching file.
34
+ Every behavior, failure path, boundary, and risk needs evidence or gap/blocker. Every row cites source, surface, and evidence layer; web rows include mode. `test_framework` must match routes; `android-app` uses `pytest`, never `none`. Do not use unit/component/integration/contract/smoke as surface. Do not drop or narrow acceptance because current code has no matching file.
@@ -11,6 +11,7 @@ Run matrix-driven commands and write reports under `docs/opentest/runs/`.
11
11
 
12
12
  - `opentest/references/command-routing.md`
13
13
  - `opentest/references/complete-testing-workflow.md`
14
+ - `opentest/references/rules-precedence.md`
14
15
  - `opentest/references/test-asset-layout.md`
15
16
  - `opentest/references/test-surfaces.md`
16
17
  - `opentest/references/android-app-testing.md`
@@ -12,6 +12,7 @@ OpenTest 把需求转成测试证据生命周期。用户只触发 OpenTest;AI
12
12
  - `opentest/references/command-routing.md`
13
13
  - `opentest/references/lifecycle.md`
14
14
  - `opentest/references/complete-testing-workflow.md`
15
+ - `opentest/references/rules-precedence.md`
15
16
  - `opentest/references/test-surfaces.md`
16
17
 
17
18
  ## 启动
@@ -0,0 +1,16 @@
1
+ # 规则优先级
2
+
3
+ OpenTest 是测试生命周期 skill。项目触发 OpenTest,或存在活跃的 `.opentest.yaml` 时,已选执行面决定必需证据链。
4
+
5
+ 全局或项目规则里的“不默认创建测试框架代码”是临时验收的克制规则。它用于避免在未选择测试流程前过早铺脚手架,不用于关闭 OpenTest 的证据契约。
6
+
7
+ 矩阵行一旦选择执行面,对应框架路线就是必需证据的一部分:
8
+
9
+ - `android-app`:`pytest` 是面向用户的编排入口。`android-midscene-pytest`、ADB、截图、logcat 和 Midscene 报告是证据契约,不是可选脚手架。
10
+ - `web-browser`:`instant-acceptance` 可以用 MCP 或 Playwright CLI;`durable-regression` 使用项目 E2E 框架或 `@playwright/test`。
11
+ - `api`:优先项目 API 命令;没有项目命令时,用 `pytest` 搭配 HTTP、schema 和 fixture 证据。
12
+ - `desktop-gui`:优先项目 GUI 自动化;需要视觉桌面自动化时使用 `@midscene/computer`。
13
+
14
+ 不得为了满足泛化的框架克制规则,把 `test_framework` 设为 `none`,同时又在矩阵里要求框架支撑的执行面。如果项目规则明确禁止必需框架,记录为 `blocked` 或请求裁决;不要静默降级必需证据。
15
+
16
+ 只有当矩阵没有框架支撑的执行面,也没有依赖 pytest、Playwright、Midscene 或项目测试 harness 的必需证据时,`test_framework: none` 才是有效声明。
@@ -11,6 +11,7 @@ description: "OpenTest 阶段 4:执行自然语言验收、MCP 验收或真实
11
11
 
12
12
  - `opentest/references/acceptance-evidence.md`
13
13
  - `opentest/references/complete-testing-workflow.md`
14
+ - `opentest/references/rules-precedence.md`
14
15
  - `opentest/references/test-surfaces.md`
15
16
  - `opentest/references/android-app-testing.md`
16
17
  - `opentest/references/web-browser-testing.md`
@@ -11,6 +11,7 @@ description: "OpenTest 阶段 2:根据矩阵补齐测试资产、fixtures 和
11
11
 
12
12
  - `opentest/references/opentest-driven-development.md`
13
13
  - `opentest/references/complete-testing-workflow.md`
14
+ - `opentest/references/rules-precedence.md`
14
15
  - `opentest/references/test-asset-layout.md`
15
16
  - `opentest/templates/fixtures-template.md`
16
17
  - `opentest/templates/acceptance-template.md`
@@ -19,7 +20,7 @@ description: "OpenTest 阶段 2:根据矩阵补齐测试资产、fixtures 和
19
20
 
20
21
  1. 读取 `.opentest.yaml` 的 `matrix` 和 `fixtures`。
21
22
  2. 保留每条矩阵行的需求来源和期望行为。不要围绕当前实现命名、组件内部结构或已有测试文件重写验收用例。
22
- 3. 资产必须放入 `test-asset-layout.md` 的固定目录;没有项目框架时默认使用 pytest + `tests/`。实现缺失只表示证据 pending。
23
+ 3. 资产必须放入 `test-asset-layout.md` 的固定目录;没有项目框架时默认使用 pytest + `tests/`,不得把必需框架证据降级为 `none`。实现缺失只表示证据 pending。
23
24
  4. 创建/更新 fixtures、seed、teardown、用户、角色、实体、文件/图片和断言界面。
24
25
  5. CRUD/数据变更必须补全链路:新增 -> 列表 -> 详情 -> 修改 -> 回读 -> 删除 -> 确认消失 -> 清理。
25
26
  6. 记录 gap/blocker 的原因和风险。
@@ -12,6 +12,7 @@ description: "OpenTest 阶段 1:分析变更、风险和项目事实,生成
12
12
  - `opentest/references/codex-harness-coverage-heuristics.md`
13
13
  - `opentest/references/matrix-format.md`
14
14
  - `opentest/references/complete-testing-workflow.md`
15
+ - `opentest/references/rules-precedence.md`
15
16
  - `opentest/references/test-asset-layout.md`
16
17
  - `opentest/references/test-surfaces.md`
17
18
  - `opentest/references/android-app-testing.md`
@@ -21,14 +22,15 @@ description: "OpenTest 阶段 1:分析变更、风险和项目事实,生成
21
22
 
22
23
  ## 步骤
23
24
 
24
- 1. 读取规则、需求/设计/请求/diff、现有命令和 detect 输出。
25
+ 1. 读取规则、需求/设计/请求/diff、现有命令、detect 输出和 `rules-precedence.md`。
25
26
  2. 把需求、流程、业务规则和风险当成验收来源;读取当前代码只用于发现项目事实,例如命令、路由、框架、fixtures 和 helper。
26
27
  3. 数据写入、API、表单、文件或有状态流程默认套用 CRUD 基线和测试数据要求。
27
28
  4. 分开判定执行面和证据层级。执行面只能是 `web-browser`、`android-app`、`desktop-gui` 或 `api`;Android 行用 `opentest-android-app`,Web 行必须写验收模式,原生桌面行用 `opentest-desktop-gui`,API 行用 `opentest-api`。
28
- 5. 生成需求先行矩阵,包含来源、行为、执行面、验收模式、证据层级、命令/工具、证据、缺口/阻塞、状态和固定资产目录。
29
- 6. 写入 `.opentest.yaml` 的 `plan`、`matrix`、`fixtures`。
30
- 7. 如存在 handoff,同步 plan/matrix/fixtures 路径,然后运行 `bash "$OPENTEST_GUARD" plan --apply`。
29
+ 5. 若项目规则克制测试框架,判断 OpenTest 已选执行面是否构成明确框架要求;冲突写为 blocker,不得静默设为 `test_framework: none`。
30
+ 6. 生成需求先行矩阵,包含来源、行为、执行面、验收模式、证据层级、命令/工具、证据、缺口/阻塞、状态和固定资产目录。
31
+ 7. 写入 `.opentest.yaml` 的 `plan`、`matrix`、`fixtures` 和与执行面一致的 `test_framework`。
32
+ 8. 如存在 handoff,同步 plan/matrix/fixtures 路径,然后运行 `bash "$OPENTEST_GUARD" plan --apply`。
31
33
 
32
34
  ## 质量门
33
35
 
34
- 每个行为、失败路径、边界和风险面都要有证据或 gap/blocker。每行必须引用需求来源,并包含执行面和证据层级;Web 行还要有验收模式。不得把 unit/component/integration/contract/smoke 当执行面。不得因为当前代码没有对应函数、组件、API 或测试文件就删除或缩小验收。CRUD 基线和测试数据默认必需;不适用时在矩阵写明原因。
36
+ 每个行为、失败路径、边界和风险面都要有证据或 gap/blocker。每行必须引用需求来源,并包含执行面和证据层级;Web 行还要有验收模式。`test_framework` 必须匹配执行面路线;`android-app` 使用 `pytest`,不得是 `none`。不得把 unit/component/integration/contract/smoke 当执行面。不得因为当前代码没有对应函数、组件、API 或测试文件就删除或缩小验收。CRUD 基线和测试数据默认必需;不适用时在矩阵写明原因。
@@ -11,6 +11,7 @@ description: "OpenTest 阶段 3:按 targeted、fast、full、ci-like 或 pre-p
11
11
 
12
12
  - `opentest/references/command-routing.md`
13
13
  - `opentest/references/complete-testing-workflow.md`
14
+ - `opentest/references/rules-precedence.md`
14
15
  - `opentest/references/test-asset-layout.md`
15
16
  - `opentest/references/test-surfaces.md`
16
17
  - `opentest/references/android-app-testing.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pzy560117/opentest",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "OpenTest quality evidence lifecycle skills for Codex",
5
5
  "keywords": [
6
6
  "opentest",
@@ -216,10 +216,31 @@ function assertRequirementFirstAcceptanceContracts() {
216
216
  assert(chineseMatrix.includes('需求来源') && chineseMatrix.includes('REQ-001'), '[REQFIRST] Chinese matrix template must include requirement source examples');
217
217
  assert(englishPlan.includes('current code only for project facts') && englishPlan.includes('Do not drop or narrow acceptance'), '[REQFIRST] English plan skill must prevent implementation-derived acceptance');
218
218
  assert(chinesePlan.includes('读取当前代码只用于发现项目事实') && chinesePlan.includes('不得因为当前代码没有对应函数'), '[REQFIRST] Chinese plan skill must prevent implementation-derived acceptance');
219
+ assert(englishPlan.includes('rules-precedence.md') && englishPlan.includes('test_framework') && englishPlan.includes('android-app') && englishPlan.includes('never `none`'), '[REQFIRST] English plan skill must gate framework precedence');
220
+ assert(chinesePlan.includes('rules-precedence.md') && chinesePlan.includes('test_framework') && chinesePlan.includes('android-app') && chinesePlan.includes('不得是 `none`'), '[REQFIRST] Chinese plan skill must gate framework precedence');
219
221
  assert(englishAuthor.includes('Missing implementation means evidence stays pending'), '[REQFIRST] English author skill must keep requirements when implementation is missing');
220
222
  assert(chineseAuthor.includes('实现缺失只表示证据 pending'), '[REQFIRST] Chinese author skill must keep requirements when implementation is missing');
221
223
  }
222
224
 
225
+ function assertRulesPrecedenceContracts() {
226
+ const englishRules = readRequiredText('assets/skills/opentest/references/rules-precedence.md', '[PRECEDENCE] missing English rules-precedence');
227
+ const chineseRules = readRequiredText('assets/skills-zh/opentest/references/rules-precedence.md', '[PRECEDENCE] missing Chinese rules-precedence');
228
+ const guardScript = readFileSync('assets/skills/opentest/scripts/opentest-guard.sh', 'utf8');
229
+ const englishAuthor = readFileSync('assets/skills/opentest-author/SKILL.md', 'utf8');
230
+ const chineseAuthor = readFileSync('assets/skills-zh/opentest-author/SKILL.md', 'utf8');
231
+ const englishRun = readFileSync('assets/skills/opentest-run/SKILL.md', 'utf8');
232
+ const chineseRun = readFileSync('assets/skills-zh/opentest-run/SKILL.md', 'utf8');
233
+ const englishAccept = readFileSync('assets/skills/opentest-accept/SKILL.md', 'utf8');
234
+ const chineseAccept = readFileSync('assets/skills-zh/opentest-accept/SKILL.md', 'utf8');
235
+
236
+ assert(englishRules.includes('test_framework: none') && englishRules.includes('android-app') && englishRules.includes('blocked'), '[PRECEDENCE] English rules must define none conflict handling');
237
+ assert(chineseRules.includes('test_framework') && chineseRules.includes('android-app') && chineseRules.includes('blocked'), '[PRECEDENCE] Chinese rules must define none conflict handling');
238
+ assert(guardScript.includes('framework_matches_matrix') && guardScript.includes('android-app execution surface requires test_framework=pytest') && guardScript.includes('test_framework=none conflicts'), '[PRECEDENCE] guard must block framework/matrix conflicts');
239
+ assert(englishAuthor.includes('rules-precedence.md') && chineseAuthor.includes('rules-precedence.md'), '[PRECEDENCE] author skills must load rules precedence');
240
+ assert(englishRun.includes('rules-precedence.md') && chineseRun.includes('rules-precedence.md'), '[PRECEDENCE] run skills must load rules precedence');
241
+ assert(englishAccept.includes('rules-precedence.md') && chineseAccept.includes('rules-precedence.md'), '[PRECEDENCE] accept skills must load rules precedence');
242
+ }
243
+
223
244
  function assertTestSurfaceContracts() {
224
245
  const manifestText = readFileSync('assets/manifest.json', 'utf8');
225
246
  const englishRouter = readFileSync('assets/skills/opentest/SKILL.md', 'utf8');
@@ -247,8 +268,11 @@ function assertTestSurfaceContracts() {
247
268
  const surfaces = ['web-browser', 'android-app', 'desktop-gui', 'api'];
248
269
 
249
270
  assert(manifestText.includes('opentest/references/test-surfaces.md'), '[SURFACE] manifest must ship test-surfaces reference');
271
+ assert(manifestText.includes('opentest/references/rules-precedence.md'), '[SURFACE] manifest must ship rules-precedence reference');
250
272
  assert(englishRouter.includes('single user entry') && englishRouter.includes('matrix surface') && englishRouter.includes('Ask only when') && englishRouter.includes('surface cannot be inferred'), '[SURFACE] English router must keep single-entry automatic adapter routing');
251
273
  assert(chineseRouter.includes('用户只触发 OpenTest') && chineseRouter.includes('按矩阵执行面自动选择 adapter') && chineseRouter.includes('不要让用户手动选择 adapter'), '[SURFACE] Chinese router must keep single-entry automatic adapter routing');
274
+ assert(englishRouter.includes('rules-precedence.md'), '[SURFACE] English router must load rules precedence');
275
+ assert(chineseRouter.includes('rules-precedence.md'), '[SURFACE] Chinese router must load rules precedence');
252
276
 
253
277
  for (const surface of surfaces) {
254
278
  assert(englishSurfaces.includes(surface), `[SURFACE] English test-surfaces missing ${surface}`);
@@ -925,6 +949,7 @@ assertManifestStructure();
925
949
  assertPrepublishGateCoversManifestAssets();
926
950
  assertDefaultPytestContracts();
927
951
  assertRequirementFirstAcceptanceContracts();
952
+ assertRulesPrecedenceContracts();
928
953
  assertTestSurfaceContracts();
929
954
  assertAndroidAppContracts();
930
955
  assertWebBrowserContracts();