@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.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/assets/manifest.json +30 -0
- package/assets/skills/opentest/SKILL.md +60 -0
- package/assets/skills/opentest/references/acceptance-evidence.md +22 -0
- package/assets/skills/opentest/references/codex-harness-coverage-heuristics.md +64 -0
- package/assets/skills/opentest/references/command-routing.md +9 -0
- package/assets/skills/opentest/references/lifecycle.md +16 -0
- package/assets/skills/opentest/references/matrix-format.md +16 -0
- package/assets/skills/opentest/references/quality-gate.md +21 -0
- package/assets/skills/opentest/scripts/opentest-detect.sh +56 -0
- package/assets/skills/opentest/scripts/opentest-guard.sh +189 -0
- package/assets/skills/opentest/scripts/opentest-state.sh +277 -0
- package/assets/skills/opentest/templates/acceptance-template.md +23 -0
- package/assets/skills/opentest/templates/archive-layout.md +14 -0
- package/assets/skills/opentest/templates/matrix-template.md +5 -0
- package/assets/skills/opentest/templates/plan-template.md +18 -0
- package/assets/skills/opentest/templates/report-template.md +23 -0
- package/assets/skills/opentest-accept/SKILL.md +23 -0
- package/assets/skills/opentest-archive/SKILL.md +8 -0
- package/assets/skills/opentest-author/SKILL.md +23 -0
- package/assets/skills/opentest-heal/SKILL.md +8 -0
- package/assets/skills/opentest-plan/SKILL.md +23 -0
- package/assets/skills/opentest-run/SKILL.md +25 -0
- package/assets/skills/opentest-verify/SKILL.md +16 -0
- package/bin/opentest.js +122 -0
- package/package.json +36 -0
- package/scripts/prepublish-check.js +58 -0
- 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,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,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,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,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`。
|
package/bin/opentest.js
ADDED
|
@@ -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
|
+
}
|