@pzy560117/opentest 0.1.0

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.
Files changed (29) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/assets/manifest.json +30 -0
  4. package/assets/skills/opentest/SKILL.md +60 -0
  5. package/assets/skills/opentest/references/acceptance-evidence.md +22 -0
  6. package/assets/skills/opentest/references/codex-harness-coverage-heuristics.md +64 -0
  7. package/assets/skills/opentest/references/command-routing.md +9 -0
  8. package/assets/skills/opentest/references/lifecycle.md +16 -0
  9. package/assets/skills/opentest/references/matrix-format.md +16 -0
  10. package/assets/skills/opentest/references/quality-gate.md +21 -0
  11. package/assets/skills/opentest/scripts/opentest-detect.sh +56 -0
  12. package/assets/skills/opentest/scripts/opentest-guard.sh +189 -0
  13. package/assets/skills/opentest/scripts/opentest-state.sh +277 -0
  14. package/assets/skills/opentest/templates/acceptance-template.md +23 -0
  15. package/assets/skills/opentest/templates/archive-layout.md +14 -0
  16. package/assets/skills/opentest/templates/matrix-template.md +5 -0
  17. package/assets/skills/opentest/templates/plan-template.md +18 -0
  18. package/assets/skills/opentest/templates/report-template.md +23 -0
  19. package/assets/skills/opentest-accept/SKILL.md +23 -0
  20. package/assets/skills/opentest-archive/SKILL.md +8 -0
  21. package/assets/skills/opentest-author/SKILL.md +23 -0
  22. package/assets/skills/opentest-heal/SKILL.md +8 -0
  23. package/assets/skills/opentest-plan/SKILL.md +23 -0
  24. package/assets/skills/opentest-run/SKILL.md +25 -0
  25. package/assets/skills/opentest-verify/SKILL.md +16 -0
  26. package/bin/opentest.js +122 -0
  27. package/package.json +36 -0
  28. package/scripts/prepublish-check.js +58 -0
  29. package/scripts/smoke-test.js +64 -0
@@ -0,0 +1,277 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ STATE_FILE="${OPENTEST_STATE_FILE:-.opentest.yaml}"
5
+
6
+ red() { printf '\033[31m%s\033[0m\n' "$1" >&2; }
7
+ green() { printf '\033[32m%s\033[0m\n' "$1" >&2; }
8
+
9
+ usage() {
10
+ red "Usage: opentest-state.sh <init|get|set|transition|check>"
11
+ exit 1
12
+ }
13
+
14
+ today() {
15
+ date +%Y-%m-%d
16
+ }
17
+
18
+ strip_wrapping_quotes() {
19
+ local value="${1-}"
20
+ case "$value" in
21
+ \"*\") printf '%s\n' "${value:1:${#value}-2}" ;;
22
+ \'*\') printf '%s\n' "${value:1:${#value}-2}" ;;
23
+ *) printf '%s\n' "$value" ;;
24
+ esac
25
+ }
26
+
27
+ yaml_field() {
28
+ local field="$1"
29
+ [ -f "$STATE_FILE" ] || return 0
30
+ local raw
31
+ raw=$(awk -v key="$field" '
32
+ $1 == key ":" {
33
+ sub(/^[^:]+:[[:space:]]*/, "", $0)
34
+ print
35
+ exit
36
+ }
37
+ ' "$STATE_FILE")
38
+ strip_wrapping_quotes "$raw"
39
+ }
40
+
41
+ replace_yaml_field() {
42
+ local field="$1"
43
+ local value="$2"
44
+ local tmp_file
45
+ tmp_file=$(mktemp)
46
+ awk -v key="$field" -v value="$value" '
47
+ BEGIN { replaced = 0 }
48
+ $1 == key ":" {
49
+ print key ": " value
50
+ replaced = 1
51
+ next
52
+ }
53
+ { print }
54
+ END {
55
+ if (replaced == 0) {
56
+ print key ": " value
57
+ }
58
+ }
59
+ ' "$STATE_FILE" > "$tmp_file"
60
+ mv "$tmp_file" "$STATE_FILE"
61
+ }
62
+
63
+ require_state_file() {
64
+ if [ ! -f "$STATE_FILE" ]; then
65
+ red "ERROR: $STATE_FILE not found"
66
+ exit 1
67
+ fi
68
+ }
69
+
70
+ validate_known_field() {
71
+ case "$1" in
72
+ workflow|phase|plan|matrix|acceptance|run_mode|run_report|verification_result|verification_report|archived|created_at|updated_at)
73
+ return 0
74
+ ;;
75
+ *)
76
+ red "ERROR: unknown field '$1'"
77
+ exit 1
78
+ ;;
79
+ esac
80
+ }
81
+
82
+ validate_enum() {
83
+ local value="$1"
84
+ shift
85
+ local item
86
+ for item in "$@"; do
87
+ if [ "$value" = "$item" ]; then
88
+ return 0
89
+ fi
90
+ done
91
+ red "ERROR: invalid value '$value'. Valid values: $*"
92
+ exit 1
93
+ }
94
+
95
+ set_field() {
96
+ local field="$1"
97
+ local value="$2"
98
+ replace_yaml_field "$field" "$value"
99
+ }
100
+
101
+ cmd_init() {
102
+ if [ -f "$STATE_FILE" ]; then
103
+ red "ERROR: $STATE_FILE already exists"
104
+ exit 1
105
+ fi
106
+ local stamp
107
+ stamp=$(today)
108
+ cat > "$STATE_FILE" <<EOF
109
+ workflow: standard
110
+ phase: plan
111
+ plan: null
112
+ matrix: null
113
+ acceptance: null
114
+ run_mode: targeted
115
+ run_report: null
116
+ verification_result: pending
117
+ verification_report: null
118
+ archived: false
119
+ created_at: $stamp
120
+ updated_at: $stamp
121
+ EOF
122
+ green "Initialized $STATE_FILE"
123
+ }
124
+
125
+ cmd_get() {
126
+ [ $# -eq 1 ] || usage
127
+ require_state_file
128
+ validate_known_field "$1"
129
+ yaml_field "$1"
130
+ }
131
+
132
+ cmd_set() {
133
+ [ $# -eq 2 ] || usage
134
+ require_state_file
135
+
136
+ local field="$1"
137
+ local value="$2"
138
+
139
+ validate_known_field "$field"
140
+ case "$field" in
141
+ phase)
142
+ validate_enum "$value" plan author run accept verify heal archive
143
+ ;;
144
+ run_mode)
145
+ validate_enum "$value" targeted fast full ci-like
146
+ ;;
147
+ verification_result)
148
+ validate_enum "$value" pending pass fail risk-accepted
149
+ ;;
150
+ archived)
151
+ validate_enum "$value" true false
152
+ ;;
153
+ esac
154
+
155
+ set_field "$field" "$value"
156
+ if [ "$field" != "updated_at" ]; then
157
+ set_field updated_at "$(today)"
158
+ fi
159
+ green "[SET] $field=$value"
160
+ }
161
+
162
+ require_phase() {
163
+ local expected="$1"
164
+ local actual
165
+ actual=$(yaml_field phase)
166
+ if [ "$actual" != "$expected" ]; then
167
+ red "ERROR: expected phase '$expected', got '${actual:-null}'"
168
+ exit 1
169
+ fi
170
+ }
171
+
172
+ cmd_transition() {
173
+ [ $# -eq 1 ] || usage
174
+ require_state_file
175
+
176
+ local event="$1"
177
+ validate_enum "$event" \
178
+ plan-complete \
179
+ author-complete \
180
+ run-complete \
181
+ accept-complete \
182
+ verify-pass \
183
+ verify-risk-accepted \
184
+ verify-fail \
185
+ heal-complete \
186
+ archived
187
+
188
+ case "$event" in
189
+ plan-complete)
190
+ require_phase plan
191
+ cmd_set phase author
192
+ ;;
193
+ author-complete)
194
+ require_phase author
195
+ cmd_set phase run
196
+ ;;
197
+ run-complete)
198
+ require_phase run
199
+ cmd_set phase accept
200
+ ;;
201
+ accept-complete)
202
+ require_phase accept
203
+ cmd_set phase verify
204
+ ;;
205
+ verify-pass)
206
+ require_phase verify
207
+ cmd_set verification_result pass
208
+ cmd_set phase archive
209
+ ;;
210
+ verify-risk-accepted)
211
+ require_phase verify
212
+ cmd_set verification_result risk-accepted
213
+ cmd_set phase archive
214
+ ;;
215
+ verify-fail)
216
+ require_phase verify
217
+ cmd_set verification_result fail
218
+ cmd_set phase heal
219
+ ;;
220
+ heal-complete)
221
+ require_phase heal
222
+ cmd_set phase run
223
+ ;;
224
+ archived)
225
+ require_phase archive
226
+ cmd_set archived true
227
+ ;;
228
+ esac
229
+ green "[TRANSITION] $event"
230
+ }
231
+
232
+ cmd_check() {
233
+ require_state_file
234
+
235
+ local workflow phase run_mode verification_result archived
236
+ workflow=$(yaml_field workflow)
237
+ phase=$(yaml_field phase)
238
+ run_mode=$(yaml_field run_mode)
239
+ verification_result=$(yaml_field verification_result)
240
+ archived=$(yaml_field archived)
241
+
242
+ validate_enum "$workflow" standard
243
+ validate_enum "$phase" plan author run accept verify heal archive
244
+ validate_enum "$run_mode" targeted fast full ci-like
245
+ validate_enum "$verification_result" pending pass fail risk-accepted
246
+ validate_enum "$archived" true false
247
+
248
+ green "[PASS] $STATE_FILE exists and phase=$phase"
249
+ }
250
+
251
+ case "${1:-}" in
252
+ init)
253
+ shift
254
+ [ $# -eq 0 ] || usage
255
+ cmd_init
256
+ ;;
257
+ get)
258
+ shift
259
+ cmd_get "$@"
260
+ ;;
261
+ set)
262
+ shift
263
+ cmd_set "$@"
264
+ ;;
265
+ transition)
266
+ shift
267
+ cmd_transition "$@"
268
+ ;;
269
+ check)
270
+ shift
271
+ [ $# -eq 0 ] || usage
272
+ cmd_check
273
+ ;;
274
+ *)
275
+ usage
276
+ ;;
277
+ esac
@@ -0,0 +1,23 @@
1
+ # OpenTest 验收用例
2
+
3
+ ## ACC-001
4
+
5
+ - intent:
6
+ - context:
7
+ - actor:
8
+ - execution surface:
9
+ - status: pending
10
+
11
+ ### Steps
12
+
13
+ 1.
14
+
15
+ ### Expected Outcome
16
+
17
+ -
18
+
19
+ ### Evidence
20
+
21
+ - status:
22
+ - notes:
23
+ - artifacts:
@@ -0,0 +1,14 @@
1
+ # OpenTest Archive Layout
2
+
3
+ Archive completed evidence under:
4
+
5
+ `docs/opentest/archive/YYYY-MM-DD-<topic>/`
6
+
7
+ Include:
8
+
9
+ - plan
10
+ - matrix
11
+ - acceptance evidence
12
+ - run report
13
+ - verification report
14
+ - screenshots or logs when available
@@ -0,0 +1,5 @@
1
+ # Acceptance-to-Test Matrix
2
+
3
+ | ID | 意图 | 风险 | 必需证据 | 状态 |
4
+ | --- | --- | --- | --- | --- |
5
+ | ACC-001 | | low | targeted review | pending |
@@ -0,0 +1,18 @@
1
+ # OpenTest 测试策略
2
+
3
+ ## 变更摘要
4
+
5
+ - 变更类型:
6
+ - 风险等级:
7
+ - 影响面:
8
+
9
+ ## 适用覆盖面
10
+
11
+ - 必需:
12
+ - 不适用:
13
+ - gap:
14
+
15
+ ## 证据计划
16
+
17
+ | 证据 | 命令或执行面 | 产物路径 | 状态 |
18
+ | --- | --- | --- | --- |
@@ -0,0 +1,23 @@
1
+ # OpenTest 验证报告
2
+
3
+ ## Summary
4
+
5
+ - result:
6
+ - run mode:
7
+ - verified at:
8
+
9
+ ## Commands
10
+
11
+ | command | exit code | result | log |
12
+ | --- | --- | --- | --- |
13
+
14
+ ## Acceptance Evidence
15
+
16
+ | ID | result | evidence | notes |
17
+ | --- | --- | --- | --- |
18
+
19
+ ## Quality Gate
20
+
21
+ - blocking:
22
+ - risk accepted:
23
+ - non-blocking gaps:
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: opentest-accept
3
+ description: "OpenTest 阶段 4:执行自然语言验收、MCP 验收或真实链路验收,并回写证据。"
4
+ ---
5
+
6
+ # OpenTest Accept
7
+
8
+ ## 目标
9
+
10
+ 执行必需验收项,并把 PASS、FAIL 或 blocked evidence 回写到验收用例和矩阵。
11
+
12
+ ## 步骤
13
+
14
+ 1. 读取矩阵和 `docs/opentest/acceptance/`。
15
+ 2. 对前端交互,优先用 Chrome DevTools MCP 执行真实页面验收。
16
+ 3. 对 API 或后台链路,使用项目已有命令或直接 API 检查。
17
+ 4. 工具、环境或前置数据不可用时,记录 blocked evidence。
18
+ 5. 更新验收记录。
19
+ 6. 运行 `bash "$OPENTEST_GUARD" accept --apply`。
20
+
21
+ ## Existing Skill Routing
22
+
23
+ 前端交互验收优先使用自然语言用例加 Chrome DevTools MCP。执行前先确认用例覆盖的是本次适用维度;执行后把 PASS、FAIL 或 blocked 写回对应 ACC ID。
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: opentest-archive
3
+ description: "OpenTest 阶段 6:归档已通过验证的质量证据并关闭状态。"
4
+ ---
5
+
6
+ # OpenTest Archive
7
+
8
+ 验证通过后,将 plan、matrix、acceptance、runs、reports 和可用截图或日志复制到 `docs/opentest/archive/`,运行 `bash "$OPENTEST_GUARD" archive --apply`。验证未通过时拒绝完成归档。
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: opentest-author
3
+ description: "OpenTest 阶段 2:根据矩阵补齐测试资产和自然语言验收用例。"
4
+ ---
5
+
6
+ # OpenTest Author
7
+
8
+ ## 目标
9
+
10
+ 根据 acceptance-to-test matrix 创建或更新测试资产。
11
+
12
+ ## 步骤
13
+
14
+ 1. 读取 `.opentest.yaml` 中的 `matrix`。
15
+ 2. 对代码级证据,按项目已有测试框架创建或更新测试文件。
16
+ 3. 对真实链路证据,写入 `docs/opentest/acceptance/` 自然语言验收用例。
17
+ 4. 对不适用或当前无法补齐的证据,记录原因和风险。
18
+ 5. 写入 `.opentest.yaml` 的 `acceptance` 字段。
19
+ 6. 运行 `bash "$OPENTEST_GUARD" author --apply`。
20
+
21
+ ## Existing Skill Routing
22
+
23
+ 当矩阵要求代码级测试证据时,优先加载现有 TDD guidance。若项目规则要求特定测试框架,遵循项目规则。若当前任务更适合真实验收而不是新增测试框架代码,记录原因并交给 `opentest-accept`。
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: opentest-heal
3
+ description: "OpenTest 恢复阶段:修复测试资产问题,不掩盖产品行为失败。"
4
+ ---
5
+
6
+ # OpenTest Heal
7
+
8
+ 只处理 selector、等待策略、过期 label、过期自然语言步骤等测试资产问题。若失败说明产品行为不满足验收,停止 heal 并记录需要回到 author、run 或 verify。
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: opentest-plan
3
+ description: "OpenTest 阶段 1:分析变更、风险和项目事实,生成测试策略与 acceptance-to-test matrix。"
4
+ ---
5
+
6
+ # OpenTest Plan
7
+
8
+ ## 目标
9
+
10
+ 生成 `docs/opentest/plans/` 下的测试策略和 `docs/opentest/matrices/` 下的矩阵。
11
+
12
+ ## 步骤
13
+
14
+ 1. 读取项目规则、需求、diff、现有测试命令和 `opentest/references/codex-harness-coverage-heuristics.md`。
15
+ 2. 判断变更类型、风险等级和适用覆盖面。
16
+ 3. 生成窄矩阵,至少包含 `ID`、`意图`、`风险`、`必需证据`、`状态`。
17
+ 4. 对适用但未覆盖的证据面标记 `gap`。
18
+ 5. 写入 `.opentest.yaml` 的 `plan` 和 `matrix` 字段。
19
+ 6. 运行 `bash "$OPENTEST_GUARD" plan --apply`。
20
+
21
+ ## 约束
22
+
23
+ 不要把 Codex Harness 全量 checklist 展开成固定必需项。低风险变更保留轻量覆盖摘要,高风险闭环再展开详细验收维度。
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: opentest-run
3
+ description: "OpenTest 阶段 3:按 targeted、fast、full 或 ci-like 模式运行项目测试命令。"
4
+ ---
5
+
6
+ # OpenTest Run
7
+
8
+ ## 目标
9
+
10
+ 执行项目已有验证命令并写入运行报告。
11
+
12
+ ## 模式
13
+
14
+ - `targeted`:只运行与矩阵相关的测试命令。
15
+ - `fast`:运行快速反馈命令,例如 type、lint、unit。
16
+ - `full`:运行项目完整测试命令。
17
+ - `ci-like`:尽量复现 CI 验证顺序。
18
+
19
+ ## 步骤
20
+
21
+ 1. 读取 `.opentest.yaml` 的 `run_mode` 和矩阵。
22
+ 2. 优先使用项目显式命令。
23
+ 3. 记录命令、退出码、摘要和日志路径到 `docs/opentest/runs/`。
24
+ 4. 写入 `.opentest.yaml` 的 `run_report` 字段。
25
+ 5. 运行 `bash "$OPENTEST_GUARD" run --apply`。
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: opentest-verify
3
+ description: "OpenTest 阶段 5:应用质量门并生成验证报告。"
4
+ ---
5
+
6
+ # OpenTest Verify
7
+
8
+ ## 目标
9
+
10
+ 应用质量门,输出结构化验证报告,并把结果写回状态文件。
11
+
12
+ ## Verification Order
13
+
14
+ 验证报告按 build、type、lint、test、security、diff 组织。若项目没有某类命令,记录为 missing command 或 not applicable,不把未知状态写成 pass。
15
+
16
+ 按 build、type、lint、test、security、diff 的顺序组织证据。必需测试、必需验收、构建、类型或 lint 失败时标记 `fail`。非必需缺口可记录为 `risk-accepted`。写入 `docs/opentest/reports/` 和 `.opentest.yaml` 的 `verification_report`、`verification_result`。
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cp, mkdir, readdir, rm, stat } from 'fs/promises';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const packageRoot = path.resolve(__dirname, '..');
10
+ const skillsSource = path.join(packageRoot, 'assets', 'skills');
11
+
12
+ function usage(exitCode = 0) {
13
+ const text = `OpenTest skill installer
14
+
15
+ Usage:
16
+ opentest install [--global] [--force] [--project <path>]
17
+ opentest list
18
+
19
+ Options:
20
+ --global Install to ~/.codex/skills instead of ./ .codex/skills
21
+ --project PATH Install to PATH/.codex/skills
22
+ --force Overwrite existing opentest skill directories
23
+ `;
24
+ (exitCode === 0 ? console.log : console.error)(text.trim());
25
+ process.exit(exitCode);
26
+ }
27
+
28
+ function parseArgs(argv) {
29
+ const [command, ...rest] = argv;
30
+ const options = { command, global: false, force: false, project: process.cwd() };
31
+
32
+ for (let i = 0; i < rest.length; i += 1) {
33
+ const arg = rest[i];
34
+ if (arg === '--global') {
35
+ options.global = true;
36
+ } else if (arg === '--force') {
37
+ options.force = true;
38
+ } else if (arg === '--project') {
39
+ const value = rest[i + 1];
40
+ if (!value) usage(1);
41
+ options.project = path.resolve(value);
42
+ i += 1;
43
+ } else {
44
+ usage(1);
45
+ }
46
+ }
47
+
48
+ return options;
49
+ }
50
+
51
+ function homeDir() {
52
+ const home = process.env.HOME || process.env.USERPROFILE;
53
+ if (!home) {
54
+ throw new Error('Cannot resolve home directory. Set HOME or USERPROFILE.');
55
+ }
56
+ return home;
57
+ }
58
+
59
+ function targetSkillsDir(options) {
60
+ if (options.global) {
61
+ return path.join(homeDir(), '.codex', 'skills');
62
+ }
63
+ return path.join(options.project, '.codex', 'skills');
64
+ }
65
+
66
+ async function listSkills() {
67
+ const entries = await readdir(skillsSource, { withFileTypes: true });
68
+ for (const entry of entries.filter((item) => item.isDirectory()).map((item) => item.name).sort()) {
69
+ console.log(entry);
70
+ }
71
+ }
72
+
73
+ async function install(options) {
74
+ const target = targetSkillsDir(options);
75
+ await mkdir(target, { recursive: true });
76
+
77
+ const entries = await readdir(skillsSource, { withFileTypes: true });
78
+ const skillDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith('opentest'));
79
+
80
+ for (const entry of skillDirs) {
81
+ const src = path.join(skillsSource, entry.name);
82
+ const dest = path.join(target, entry.name);
83
+ const exists = await stat(dest).then(() => true, () => false);
84
+
85
+ if (exists && !options.force) {
86
+ throw new Error(`${dest} already exists. Re-run with --force to overwrite.`);
87
+ }
88
+
89
+ if (exists) {
90
+ await rm(dest, { recursive: true, force: true });
91
+ }
92
+ await cp(src, dest, { recursive: true });
93
+ }
94
+
95
+ console.log(`Installed ${skillDirs.length} OpenTest skills to ${target}`);
96
+ console.log('Restart Codex or open a new session to pick up newly installed skills.');
97
+ }
98
+
99
+ async function main() {
100
+ const options = parseArgs(process.argv.slice(2));
101
+
102
+ if (!options.command || options.command === '--help' || options.command === '-h') {
103
+ usage(0);
104
+ }
105
+
106
+ if (options.command === 'list') {
107
+ await listSkills();
108
+ return;
109
+ }
110
+
111
+ if (options.command === 'install') {
112
+ await install(options);
113
+ return;
114
+ }
115
+
116
+ usage(1);
117
+ }
118
+
119
+ main().catch((error) => {
120
+ console.error(`ERROR: ${error.message}`);
121
+ process.exit(1);
122
+ });
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@pzy560117/opentest",
3
+ "version": "0.1.0",
4
+ "description": "OpenTest quality evidence lifecycle skills for Codex",
5
+ "keywords": [
6
+ "opentest",
7
+ "codex",
8
+ "skills",
9
+ "testing",
10
+ "workflow"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "benym",
14
+ "type": "module",
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "bin": {
19
+ "opentest": "bin/opentest.js"
20
+ },
21
+ "files": [
22
+ "assets",
23
+ "bin",
24
+ "scripts",
25
+ "README.md",
26
+ "LICENSE",
27
+ "!scripts/**/*.test.js"
28
+ ],
29
+ "scripts": {
30
+ "test": "node scripts/smoke-test.js",
31
+ "prepublishOnly": "node scripts/prepublish-check.js && npm test"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }