@elvis1513/auto-coding-skill 1.0.0 → 1.0.2
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/README.md +30 -22
- package/cli/assets/skill/SKILL.md +22 -9
- package/cli/assets/skill/data/templates/ENGINEERING.md +37 -40
- package/cli/assets/skill/data/templates/docs/reviews/_TEMPLATE-REVIEW.md +4 -5
- package/cli/assets/skill/data/templates/docs/tasks/summaries/_TEMPLATE-TASK-SUMMARY.md +1 -1
- package/cli/assets/skill/scripts/ap.py +120 -160
- package/cli/src/index.js +0 -17
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ Engineering workflow skill for:
|
|
|
7
7
|
|
|
8
8
|
This skill targets Go backend + frontend monorepo projects that rely on Jenkins for build and deployment. The optimized default flow is lightweight locally, then Jenkins-first and target-environment-first for real verification.
|
|
9
9
|
|
|
10
|
+
`docs/ENGINEERING.md` is intentionally Git-tracked. The environment fields kept in that file are mandatory, must be filled with real values, and are committed as part of project maintenance. Unused environment items should be removed instead of being kept as placeholders.
|
|
11
|
+
|
|
10
12
|
## Install
|
|
11
13
|
|
|
12
14
|
```bash
|
|
@@ -33,7 +35,7 @@ npm install -g git+https://github.com/elvis1513/auto-coding-skill.git
|
|
|
33
35
|
- Synced reusable workflow improvements from a production project back into this skill.
|
|
34
36
|
- Moved repo-side helper entrypoint to `docs/tools/autopipeline`.
|
|
35
37
|
- Tightened regression matrix rules: rows start as `TODO`, and `PASS` requires real execution evidence.
|
|
36
|
-
- Added Jenkins API verification flow with credentials sourced from `docs/ENGINEERING.md
|
|
38
|
+
- Added Jenkins API verification flow with credentials sourced from `docs/ENGINEERING.md`.
|
|
37
39
|
|
|
38
40
|
## Optimized Standard Flow
|
|
39
41
|
|
|
@@ -51,12 +53,11 @@ npm install -g git+https://github.com/elvis1513/auto-coding-skill.git
|
|
|
51
53
|
3. 开发实现
|
|
52
54
|
- 只修改本次任务必要文件,不做无关重构。
|
|
53
55
|
4. 本地轻量校验
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
- lint / typecheck
|
|
57
|
-
- API 文档检查
|
|
58
|
-
- Jenkinsfile / 脚本语法检查
|
|
56
|
+
- 优先执行一个项目自定义快速门禁命令
|
|
57
|
+
- 若未配置,再执行 quick test / test / build 中最先配置的一项
|
|
59
58
|
- `git diff --check`
|
|
59
|
+
- API 文档检查
|
|
60
|
+
- Jenkins 配置检查
|
|
60
61
|
5. 立即提交推送
|
|
61
62
|
- 本地轻量校验通过后,commit + push,触发 Jenkins。
|
|
62
63
|
6. Jenkins 验证
|
|
@@ -73,7 +74,7 @@ npm install -g git+https://github.com/elvis1513/auto-coding-skill.git
|
|
|
73
74
|
默认不做:
|
|
74
75
|
- 本地 Docker Compose 启动
|
|
75
76
|
- 本地 Docker build
|
|
76
|
-
- 本地完整
|
|
77
|
+
- 本地完整 regression
|
|
77
78
|
- 每个小改动强制 `check-matrix`
|
|
78
79
|
- 每个小改动强制生成 summary
|
|
79
80
|
- 未真实执行就要求 regression matrix 全 `PASS`
|
|
@@ -81,7 +82,7 @@ npm install -g git+https://github.com/elvis1513/auto-coding-skill.git
|
|
|
81
82
|
|
|
82
83
|
按需保留:
|
|
83
84
|
- `runtime-up` / `runtime-down`
|
|
84
|
-
- 本地 health
|
|
85
|
+
- 本地 health
|
|
85
86
|
- `check-matrix`
|
|
86
87
|
- `gen-summary`
|
|
87
88
|
- deployment runbook / deployment record
|
|
@@ -108,6 +109,7 @@ python3 .claude/skills/auto-coding-skill/scripts/ap.py --repo . install
|
|
|
108
109
|
- `docs/ENGINEERING.md` frontmatter
|
|
109
110
|
|
|
110
111
|
This frontmatter is the only manual config source.
|
|
112
|
+
It must be committed to Git. Do not add it to `.gitignore`.
|
|
111
113
|
|
|
112
114
|
重点字段:
|
|
113
115
|
- `commands.*`
|
|
@@ -117,17 +119,28 @@ This frontmatter is the only manual config source.
|
|
|
117
119
|
|
|
118
120
|
默认必填:
|
|
119
121
|
- `project.name`
|
|
120
|
-
- `commands.build`
|
|
121
|
-
- `commands.quick_test` 或 `commands.test`
|
|
122
|
-
- `commands.lint` 或 `commands.typecheck`
|
|
122
|
+
- `commands.light_gate` 或 `commands.quick_test` 或 `commands.test` 或 `commands.build`
|
|
123
123
|
- `target_env.name`
|
|
124
|
+
- `target_env.frontend_base_url`
|
|
125
|
+
- `target_env.frontend_username`
|
|
126
|
+
- `target_env.frontend_password`
|
|
127
|
+
- `target_env.backend_base_url`
|
|
128
|
+
- `target_env.backend_username`
|
|
129
|
+
- `target_env.backend_password`
|
|
130
|
+
- `target_env.backend_root_username`
|
|
131
|
+
- `target_env.backend_root_password`
|
|
124
132
|
- `target_env.health_base_url`
|
|
125
133
|
- `target_env.health_path`
|
|
134
|
+
- `jenkins.base_url`
|
|
135
|
+
- `jenkins.ui_username`
|
|
136
|
+
- `jenkins.ui_password`
|
|
137
|
+
- `jenkins.api_user`
|
|
138
|
+
- `jenkins.api_password`
|
|
126
139
|
- `jenkins.trigger_branch`
|
|
127
140
|
- `jenkins.image_repository`
|
|
128
141
|
- `jenkins.image_tag_strategy`
|
|
129
142
|
- `jenkins.deploy_env`
|
|
130
|
-
- `jenkins.job_url`
|
|
143
|
+
- `jenkins.job_url`
|
|
131
144
|
|
|
132
145
|
4. Start AI development by constraints:
|
|
133
146
|
|
|
@@ -185,7 +198,10 @@ This frontmatter is the only manual config source.
|
|
|
185
198
|
- Purpose: single source of project config + workflow rules.
|
|
186
199
|
- How to record:
|
|
187
200
|
- Fill YAML frontmatter once.
|
|
188
|
-
- Keep target env
|
|
201
|
+
- Keep target env front/backend usernames and passwords, Jenkins UI/API usernames and passwords, commands, docs paths here only.
|
|
202
|
+
- Target environment also includes backend server root username/password.
|
|
203
|
+
- This file is expected to be committed to Git and maintained in plaintext for this workflow.
|
|
204
|
+
- Remaining environment keys are all mandatory; blank values, TODO-like placeholders, and incorrect URL/path formats are treated as blocking errors by `doctor`.
|
|
189
205
|
- Do not duplicate config elsewhere.
|
|
190
206
|
|
|
191
207
|
### 2) docs/tasks/taskbook.md
|
|
@@ -275,12 +291,6 @@ python3 docs/tools/autopipeline/ap.py commit-push <TASK_ID> \
|
|
|
275
291
|
Available on-demand commands:
|
|
276
292
|
|
|
277
293
|
```bash
|
|
278
|
-
python3 docs/tools/autopipeline/ap.py run build
|
|
279
|
-
python3 docs/tools/autopipeline/ap.py run test
|
|
280
|
-
python3 docs/tools/autopipeline/ap.py run quick_test
|
|
281
|
-
python3 docs/tools/autopipeline/ap.py run lint
|
|
282
|
-
python3 docs/tools/autopipeline/ap.py run typecheck
|
|
283
|
-
python3 docs/tools/autopipeline/ap.py run script_syntax
|
|
284
294
|
python3 docs/tools/autopipeline/ap.py doctor
|
|
285
295
|
python3 docs/tools/autopipeline/ap.py verify-api-docs
|
|
286
296
|
python3 docs/tools/autopipeline/ap.py verify-jenkins
|
|
@@ -290,8 +300,6 @@ python3 docs/tools/autopipeline/ap.py verify-jenkins-build --job-url <job-url> -
|
|
|
290
300
|
python3 docs/tools/autopipeline/ap.py verify-jenkins-build --multibranch-root-job <root-job> --branch-name <branch> --build-number <number>
|
|
291
301
|
python3 docs/tools/autopipeline/ap.py runtime-up
|
|
292
302
|
python3 docs/tools/autopipeline/ap.py wait-health --scope runtime
|
|
293
|
-
python3 docs/tools/autopipeline/ap.py run smoke
|
|
294
|
-
python3 docs/tools/autopipeline/ap.py run regression
|
|
295
303
|
python3 docs/tools/autopipeline/ap.py runtime-down
|
|
296
304
|
python3 docs/tools/autopipeline/ap.py check-matrix
|
|
297
305
|
python3 docs/tools/autopipeline/ap.py gen-summary <TASK_ID>
|
|
@@ -316,7 +324,7 @@ python3 docs/tools/autopipeline/ap.py gen-summary <TASK_ID>
|
|
|
316
324
|
- `doctor`
|
|
317
325
|
- Checks whether the default lightweight workflow is actually configured instead of silently skipping gates.
|
|
318
326
|
- `light-gate`
|
|
319
|
-
- Now
|
|
327
|
+
- Now prefers one curated fast gate command instead of serially running every expensive check.
|
|
320
328
|
- `verify-target`
|
|
321
329
|
- Performs real target-environment verification beyond health checks when you provide key backend/frontend paths.
|
|
322
330
|
- `commit-push --record-closure`
|
|
@@ -7,6 +7,8 @@ description: Use for a lightweight Jenkins-first engineering workflow in Claude/
|
|
|
7
7
|
|
|
8
8
|
This skill is for Go backend + frontend monorepo projects that rely on Jenkins to build and deploy after push. It supports both Claude and Codex. The default workflow is lightweight locally, then uses Jenkins and the real target environment as the authoritative verification path.
|
|
9
9
|
|
|
10
|
+
`docs/ENGINEERING.md` is intentionally Git-tracked in this workflow. The remaining environment fields in that file are mandatory, must be filled with real values, and are committed as part of the project baseline. Unused environment keys should be removed from the template instead of being left as placeholders.
|
|
11
|
+
|
|
10
12
|
Prefer already available MCP servers, installed skills, plugins, and app connectors over ad-hoc manual work whenever they can complete the task reliably.
|
|
11
13
|
|
|
12
14
|
Default to multi-agent execution when the client supports it. Break work into independent design, research, implementation, validation, and documentation subtasks so Claude/Codex can run them in parallel whenever that reduces cycle time without weakening control of the main task.
|
|
@@ -71,20 +73,32 @@ This contains all manual fields:
|
|
|
71
73
|
- `docs.*`
|
|
72
74
|
|
|
73
75
|
Do not duplicate config in other md/yaml files.
|
|
76
|
+
Do not hide `docs/ENGINEERING.md` in `.gitignore`.
|
|
74
77
|
|
|
75
78
|
Minimum required config for the default flow:
|
|
76
79
|
- `project.name`
|
|
77
|
-
- `commands.build`
|
|
78
|
-
- `commands.quick_test` or `commands.test`
|
|
79
|
-
- `commands.lint` or `commands.typecheck`
|
|
80
|
+
- `commands.light_gate` or `commands.quick_test` or `commands.test` or `commands.build`
|
|
80
81
|
- `target_env.name`
|
|
82
|
+
- `target_env.frontend_base_url`
|
|
83
|
+
- `target_env.frontend_username`
|
|
84
|
+
- `target_env.frontend_password`
|
|
85
|
+
- `target_env.backend_base_url`
|
|
86
|
+
- `target_env.backend_username`
|
|
87
|
+
- `target_env.backend_password`
|
|
88
|
+
- `target_env.backend_root_username`
|
|
89
|
+
- `target_env.backend_root_password`
|
|
81
90
|
- `target_env.health_base_url`
|
|
82
91
|
- `target_env.health_path`
|
|
92
|
+
- `jenkins.base_url`
|
|
93
|
+
- `jenkins.ui_username`
|
|
94
|
+
- `jenkins.ui_password`
|
|
95
|
+
- `jenkins.api_user`
|
|
96
|
+
- `jenkins.api_password`
|
|
83
97
|
- `jenkins.trigger_branch`
|
|
84
98
|
- `jenkins.image_repository`
|
|
85
99
|
- `jenkins.image_tag_strategy`
|
|
86
100
|
- `jenkins.deploy_env`
|
|
87
|
-
- `jenkins.job_url`
|
|
101
|
+
- `jenkins.job_url`
|
|
88
102
|
|
|
89
103
|
## Branch policy
|
|
90
104
|
|
|
@@ -124,8 +138,6 @@ On-demand commands:
|
|
|
124
138
|
```bash
|
|
125
139
|
python3 docs/tools/autopipeline/ap.py runtime-up
|
|
126
140
|
python3 docs/tools/autopipeline/ap.py wait-health --scope runtime
|
|
127
|
-
python3 docs/tools/autopipeline/ap.py run smoke
|
|
128
|
-
python3 docs/tools/autopipeline/ap.py run regression
|
|
129
141
|
python3 docs/tools/autopipeline/ap.py runtime-down
|
|
130
142
|
python3 docs/tools/autopipeline/ap.py check-matrix
|
|
131
143
|
python3 docs/tools/autopipeline/ap.py gen-summary <TASK_ID>
|
|
@@ -133,9 +145,10 @@ python3 docs/tools/autopipeline/ap.py gen-summary <TASK_ID>
|
|
|
133
145
|
|
|
134
146
|
## Quality policy
|
|
135
147
|
|
|
136
|
-
- Default local gate is lightweight
|
|
137
|
-
- `doctor` should be used early to catch missing config before the first implementation loop.
|
|
138
|
-
- `light-gate` now fails if
|
|
148
|
+
- Default local gate is lightweight and time-bounded: prefer one curated project command via `commands.light_gate`, then run only diff/API/Jenkins checks.
|
|
149
|
+
- `doctor` should be used early to catch missing or invalid config before the first implementation loop.
|
|
150
|
+
- `light-gate` now fails if no usable fast gate command is configured.
|
|
151
|
+
- `doctor`, `light-gate`, and `commit-push` all fail when required environment fields are missing, placeholder-like, or syntactically invalid.
|
|
139
152
|
- Do not require local Docker Compose or full local regression for every small change.
|
|
140
153
|
- Jenkins and target environment verification are more valuable than repeated local simulation of deploy-only problems.
|
|
141
154
|
- `verify-target` should be used for real target-environment API/page checks when the task touches user-visible or deploy-sensitive behavior.
|
|
@@ -10,26 +10,17 @@ project:
|
|
|
10
10
|
jenkinsfile: "Jenkinsfile"
|
|
11
11
|
|
|
12
12
|
commands:
|
|
13
|
+
light_gate: ""
|
|
13
14
|
build: ""
|
|
14
15
|
test: ""
|
|
15
16
|
quick_test: ""
|
|
16
17
|
lint: ""
|
|
17
18
|
typecheck: ""
|
|
18
19
|
format: ""
|
|
19
|
-
script_syntax: ""
|
|
20
|
-
diff_check: ""
|
|
21
|
-
docker_build: ""
|
|
22
|
-
compose_up: ""
|
|
23
|
-
compose_down: ""
|
|
24
|
-
smoke: ""
|
|
25
|
-
regression: ""
|
|
26
20
|
|
|
27
21
|
runtime:
|
|
28
22
|
docker_compose_file: ""
|
|
29
23
|
docker_service: ""
|
|
30
|
-
container_name: ""
|
|
31
|
-
image: ""
|
|
32
|
-
app_port: ""
|
|
33
24
|
health_base_url: ""
|
|
34
25
|
health_path: ""
|
|
35
26
|
env_file: ""
|
|
@@ -43,30 +34,23 @@ target_env:
|
|
|
43
34
|
backend_base_url: ""
|
|
44
35
|
backend_username: ""
|
|
45
36
|
backend_password: ""
|
|
37
|
+
backend_root_username: ""
|
|
38
|
+
backend_root_password: ""
|
|
46
39
|
health_base_url: ""
|
|
47
40
|
health_path: ""
|
|
48
|
-
verify_notes: ""
|
|
49
41
|
|
|
50
42
|
jenkins:
|
|
51
43
|
base_url: ""
|
|
52
44
|
ui_username: ""
|
|
53
45
|
ui_password: ""
|
|
54
|
-
crumb_url: ""
|
|
55
|
-
job_name: ""
|
|
56
46
|
job_url: ""
|
|
57
|
-
multibranch_root_job: ""
|
|
58
|
-
branch_name: ""
|
|
59
47
|
trigger_branch: ""
|
|
60
48
|
image_repository: ""
|
|
61
49
|
image_tag_strategy: ""
|
|
62
50
|
deploy_env: ""
|
|
63
51
|
deploy_timeout_sec: 1800
|
|
64
52
|
api_user: ""
|
|
65
|
-
api_token: ""
|
|
66
53
|
api_password: ""
|
|
67
|
-
api_user_env: "JENKINS_USER"
|
|
68
|
-
api_token_env: "JENKINS_TOKEN"
|
|
69
|
-
api_password_env: "JENKINS_PASSWORD"
|
|
70
54
|
|
|
71
55
|
docs:
|
|
72
56
|
taskbook: "docs/tasks/taskbook.md"
|
|
@@ -88,7 +72,7 @@ docs:
|
|
|
88
72
|
默认原则:
|
|
89
73
|
- 默认不要求本地 Docker Compose 启动。
|
|
90
74
|
- 默认不要求本地 Docker build。
|
|
91
|
-
- 默认不要求本地完整
|
|
75
|
+
- 默认不要求本地完整 regression。
|
|
92
76
|
- 默认不要求每个小改动生成长 summary。
|
|
93
77
|
- 默认不要求 regression matrix 全 PASS。
|
|
94
78
|
- 默认不要求 deployment record。
|
|
@@ -97,40 +81,53 @@ docs:
|
|
|
97
81
|
补充规则:
|
|
98
82
|
- 每次任务闭环后,必须清理临时文件、临时目录、日志、截图、构建缓存等非必要产物;仅明确需要保留的本地诊断目录允许保留。
|
|
99
83
|
- 所有手工填写信息,只维护在本文件 frontmatter 中,其他文档不得重复配置。
|
|
84
|
+
- `docs/ENGINEERING.md` 必须提交到 Git 管理,不允许写入 `.gitignore`。
|
|
85
|
+
- 本 workflow 明确允许在 `docs/ENGINEERING.md` 中明文维护平台账号、密码,并随 Git 一起版本化。
|
|
86
|
+
- 未参与默认流程的环境项不要保留占位;模板中未保留的字段视为已清理,不再额外配置。
|
|
100
87
|
|
|
101
88
|
---
|
|
102
89
|
|
|
103
90
|
## 0. 配置填写(必须)
|
|
104
91
|
|
|
105
92
|
先填写 `docs/ENGINEERING.md` frontmatter 中的所有空值。重点包括:
|
|
106
|
-
- `commands
|
|
107
|
-
- `target_env
|
|
108
|
-
- `jenkins.*`:Jenkins UI/API
|
|
93
|
+
- `commands.light_gate`:推荐配置一个项目级快速门禁命令,作为默认本地校验入口
|
|
94
|
+
- `target_env.*`:目标环境前端 / 后端地址、用户名、密码,必须全部填写且真实可用
|
|
95
|
+
- `jenkins.*`:Jenkins UI/API 用户名、密码、Job、分支、镜像、部署环境,必须全部填写且真实可用
|
|
109
96
|
|
|
110
97
|
字段说明:
|
|
111
98
|
- `target_env.backend_username` / `target_env.backend_password`:目标环境后台账号
|
|
112
|
-
- `target_env.
|
|
99
|
+
- `target_env.backend_root_username` / `target_env.backend_root_password`:目标环境后台服务器 root 账号
|
|
100
|
+
- `target_env.frontend_username` / `target_env.frontend_password`:目标环境前端登录账号
|
|
113
101
|
- `jenkins.ui_username` / `jenkins.ui_password`:Jenkins 页面登录账号
|
|
114
|
-
- `jenkins.api_user`
|
|
102
|
+
- `jenkins.api_user` / `jenkins.api_password`:Jenkins API 用户名 / 密码
|
|
115
103
|
|
|
116
104
|
默认必填:
|
|
117
105
|
- `project.name`
|
|
118
|
-
- `commands.build`
|
|
119
|
-
- `commands.quick_test` 或 `commands.test`
|
|
120
|
-
- `commands.lint` 或 `commands.typecheck`
|
|
106
|
+
- `commands.light_gate` 或 `commands.quick_test` 或 `commands.test` 或 `commands.build`
|
|
121
107
|
- `target_env.name`
|
|
108
|
+
- `target_env.frontend_base_url`
|
|
109
|
+
- `target_env.frontend_username`
|
|
110
|
+
- `target_env.frontend_password`
|
|
111
|
+
- `target_env.backend_base_url`
|
|
112
|
+
- `target_env.backend_username`
|
|
113
|
+
- `target_env.backend_password`
|
|
114
|
+
- `target_env.backend_root_username`
|
|
115
|
+
- `target_env.backend_root_password`
|
|
122
116
|
- `target_env.health_base_url`
|
|
123
117
|
- `target_env.health_path`
|
|
118
|
+
- `jenkins.ui_username`
|
|
119
|
+
- `jenkins.ui_password`
|
|
120
|
+
- `jenkins.api_user`
|
|
121
|
+
- `jenkins.api_password`
|
|
124
122
|
- `jenkins.trigger_branch`
|
|
125
123
|
- `jenkins.image_repository`
|
|
126
124
|
- `jenkins.image_tag_strategy`
|
|
127
125
|
- `jenkins.deploy_env`
|
|
128
|
-
- `jenkins.job_url`
|
|
126
|
+
- `jenkins.job_url`
|
|
129
127
|
|
|
130
128
|
按需填写:
|
|
131
129
|
- `runtime.*`:仅在本地运行诊断时使用
|
|
132
|
-
- `commands.
|
|
133
|
-
- `target_env.frontend_*` / `target_env.backend_*`:仅在需要额外页面/API验证或受保护资源校验时使用
|
|
130
|
+
- `commands.build` / `commands.test` / `commands.quick_test` / `commands.lint` / `commands.typecheck` / `commands.format`:按项目实际情况保留
|
|
134
131
|
|
|
135
132
|
---
|
|
136
133
|
|
|
@@ -194,13 +191,12 @@ docs:
|
|
|
194
191
|
只修改本次任务必要文件,不做无关重构。
|
|
195
192
|
|
|
196
193
|
4. 本地轻量校验
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
- lint / typecheck
|
|
201
|
-
- API 文档检查
|
|
202
|
-
- Jenkinsfile / 脚本语法检查
|
|
194
|
+
默认只跑最少必要检查:
|
|
195
|
+
- 优先执行 `commands.light_gate`
|
|
196
|
+
- 若未配置,则执行 `quick_test` / `test` / `build` 中最先配置的一项
|
|
203
197
|
- `git diff --check`
|
|
198
|
+
- API 文档检查
|
|
199
|
+
- Jenkins 配置检查
|
|
204
200
|
|
|
205
201
|
5. 立即提交推送
|
|
206
202
|
轻量校验通过后,commit + push,触发 Jenkins。
|
|
@@ -217,6 +213,9 @@ docs:
|
|
|
217
213
|
9. 闭环记录
|
|
218
214
|
每个任务必须留下轻量闭环记录:任务 ID、提交号、Jenkins Build URL、目标环境验证结果、是否通过、遗留问题。
|
|
219
215
|
|
|
216
|
+
10. 配置入库
|
|
217
|
+
`docs/ENGINEERING.md` 中保留下来的环境信息、前端/后端账号、Jenkins 账号与密码必须 100% 填写、正确填写,并提交 Git 作为项目权威配置持续维护。
|
|
218
|
+
|
|
220
219
|
---
|
|
221
220
|
|
|
222
221
|
## 3. 高风险变更(必须补强验证)
|
|
@@ -244,8 +243,6 @@ docs:
|
|
|
244
243
|
- `runtime-up`
|
|
245
244
|
- `runtime-down`
|
|
246
245
|
- 本地 health check
|
|
247
|
-
- 本地 `smoke`
|
|
248
|
-
- 本地 `regression`
|
|
249
246
|
- `check-matrix`
|
|
250
247
|
- `gen-summary`
|
|
251
248
|
|
|
@@ -265,7 +262,7 @@ docs:
|
|
|
265
262
|
|
|
266
263
|
说明:
|
|
267
264
|
- `doctor`:检查默认流程必填项和常见配置错误。
|
|
268
|
-
- `light-gate
|
|
265
|
+
- `light-gate`:默认轻量门禁,优先执行项目自定义快速门禁命令。
|
|
269
266
|
- `verify-target`:目标环境健康检查 + 按需关键 API / 页面验证。
|
|
270
267
|
- `record-closure`:默认轻量闭环记录。
|
|
271
268
|
- `check-matrix`、`gen-summary`、`runtime-up/down`:保留为按需工具。
|
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
> 仅在任务需要独立 review 记录时使用;不是每个小改动的默认强制文档。
|
|
4
4
|
|
|
5
5
|
## 1. 静态分析结果
|
|
6
|
-
- Command:
|
|
6
|
+
- Command:light_gate / diff_check / verify_api_docs / verify_jenkins
|
|
7
7
|
- Summary:
|
|
8
8
|
- Issues:
|
|
9
9
|
|
|
10
10
|
## 2. 本地轻量校验
|
|
11
|
-
- build:
|
|
12
|
-
- test or quick_test:
|
|
11
|
+
- light_gate or quick_test/test/build:
|
|
13
12
|
- api docs:
|
|
14
|
-
-
|
|
13
|
+
- jenkins:
|
|
15
14
|
|
|
16
15
|
## 3. 按需本地运行验证(如果有)
|
|
17
|
-
- runtime-up / health
|
|
16
|
+
- runtime-up / health:
|
|
18
17
|
|
|
19
18
|
## 4. Jenkins 与目标环境
|
|
20
19
|
- Jenkins readiness:
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- 兼容性影响:
|
|
20
20
|
|
|
21
21
|
## 3. 质量证据
|
|
22
|
-
- 本地轻量校验:
|
|
22
|
+
- 本地轻量校验:light_gate or quick_test/test/build / api docs / jenkins / diff-check
|
|
23
23
|
- Jenkins Build:
|
|
24
24
|
- 目标环境验证:
|
|
25
25
|
- 闭环记录:`docs/tasks/closure-log.md`
|
|
@@ -8,7 +8,6 @@ import argparse
|
|
|
8
8
|
import base64
|
|
9
9
|
import datetime as _dt
|
|
10
10
|
import json
|
|
11
|
-
import os
|
|
12
11
|
import time
|
|
13
12
|
import urllib.parse
|
|
14
13
|
import urllib.error
|
|
@@ -20,6 +19,7 @@ from core import APError, ensure_git_repo, copy_tree, run, load_yaml, find_confi
|
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
_JENKINS_CRUMB_CACHE: dict[str, dict[str, str]] = {}
|
|
22
|
+
_INVALID_PLACEHOLDERS = {"N/A", "TODO", "TBD", "CHANGEME", "CHANGE_ME", "FILL_ME", "FILL-ME", "PLACEHOLDER", "XXX"}
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def _skill_root() -> Path:
|
|
@@ -60,27 +60,21 @@ def _run_configured_command(repo: Path, cfg: dict, name: str) -> bool:
|
|
|
60
60
|
|
|
61
61
|
def _jenkins_basic_auth_headers(cfg: dict) -> dict:
|
|
62
62
|
jenkins_cfg = (cfg.get("jenkins") or {})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
or os.getenv(password_env)
|
|
75
|
-
or os.getenv("JENKINS_TOKEN")
|
|
76
|
-
or os.getenv("JENKINS_PASSWORD")
|
|
77
|
-
)
|
|
78
|
-
if not user or not token:
|
|
63
|
+
user_candidates = [
|
|
64
|
+
jenkins_cfg.get("api_user"),
|
|
65
|
+
jenkins_cfg.get("ui_username"),
|
|
66
|
+
]
|
|
67
|
+
secret_candidates = [
|
|
68
|
+
jenkins_cfg.get("api_password"),
|
|
69
|
+
jenkins_cfg.get("ui_password"),
|
|
70
|
+
]
|
|
71
|
+
user = next((_text(v) for v in user_candidates if _is_explicit_fill(v)), "")
|
|
72
|
+
secret = next((_text(v) for v in secret_candidates if _is_explicit_fill(v)), "")
|
|
73
|
+
if not user or not secret:
|
|
79
74
|
raise APError(
|
|
80
|
-
"Missing Jenkins API credentials. Fill jenkins.api_user
|
|
81
|
-
f"in docs/ENGINEERING.md, or set env vars {user_env}, {token_env}, {password_env}."
|
|
75
|
+
"Missing Jenkins API credentials. Fill jenkins.api_user and jenkins.api_password in docs/ENGINEERING.md."
|
|
82
76
|
)
|
|
83
|
-
raw = f"{user}:{
|
|
77
|
+
raw = f"{user}:{secret}".encode("utf-8")
|
|
84
78
|
auth = base64.b64encode(raw).decode("ascii")
|
|
85
79
|
return {"Authorization": f"Basic {auth}"}
|
|
86
80
|
|
|
@@ -122,10 +116,6 @@ def _jenkins_root_url(cfg: dict, job_url: str = "") -> str:
|
|
|
122
116
|
|
|
123
117
|
|
|
124
118
|
def _jenkins_crumb_api_url(cfg: dict, job_url: str = "") -> str:
|
|
125
|
-
jenkins_cfg = (cfg.get("jenkins") or {})
|
|
126
|
-
explicit = str(jenkins_cfg.get("crumb_url") or "").strip()
|
|
127
|
-
if explicit:
|
|
128
|
-
return explicit
|
|
129
119
|
root = _jenkins_root_url(cfg, job_url=job_url)
|
|
130
120
|
if not root:
|
|
131
121
|
return ""
|
|
@@ -203,12 +193,12 @@ def _jenkins_api_get_json(url: str, cfg: dict, timeout_s: int = 15, allow_404: b
|
|
|
203
193
|
except Exception as retry_exc:
|
|
204
194
|
raise APError(f"Jenkins API request failed after crumb retry: {url}\n{retry_exc}") from retry_exc
|
|
205
195
|
else:
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"Fill jenkins.base_url
|
|
211
|
-
|
|
196
|
+
raise APError(
|
|
197
|
+
f"Jenkins API request failed: {url}\n"
|
|
198
|
+
f"HTTP 403\n{body or '(empty response body)'}\n"
|
|
199
|
+
"Jenkins may require crumb/CSRF handling, but no crumb issuer endpoint was available. "
|
|
200
|
+
"Fill jenkins.base_url in docs/ENGINEERING.md if needed."
|
|
201
|
+
) from exc
|
|
212
202
|
else:
|
|
213
203
|
raise APError(
|
|
214
204
|
f"Jenkins API request failed: {url}\n"
|
|
@@ -296,15 +286,12 @@ def _resolve_jenkins_job_url(cfg: dict, job_name: str = "", job_url: str = "") -
|
|
|
296
286
|
jenkins_cfg = (cfg.get("jenkins") or {})
|
|
297
287
|
explicit_url = str(job_url or "").strip()
|
|
298
288
|
requested_name = str(job_name or "").strip()
|
|
299
|
-
configured_name = str(jenkins_cfg.get("job_name") or "").strip()
|
|
300
289
|
configured_url = str(jenkins_cfg.get("job_url") or "").strip()
|
|
301
290
|
base_url = str(jenkins_cfg.get("base_url") or "").strip()
|
|
302
291
|
|
|
303
292
|
if explicit_url:
|
|
304
293
|
return explicit_url.rstrip("/")
|
|
305
294
|
if requested_name:
|
|
306
|
-
if configured_name and requested_name == configured_name and configured_url:
|
|
307
|
-
return configured_url.rstrip("/")
|
|
308
295
|
if base_url:
|
|
309
296
|
return _jenkins_job_url_from_name(base_url, requested_name)
|
|
310
297
|
raise APError(
|
|
@@ -313,11 +300,9 @@ def _resolve_jenkins_job_url(cfg: dict, job_name: str = "", job_url: str = "") -
|
|
|
313
300
|
)
|
|
314
301
|
if configured_url:
|
|
315
302
|
return configured_url.rstrip("/")
|
|
316
|
-
if configured_name and base_url:
|
|
317
|
-
return _jenkins_job_url_from_name(base_url, configured_name)
|
|
318
303
|
raise APError(
|
|
319
|
-
"Missing Jenkins job location. Fill jenkins.job_url
|
|
320
|
-
"
|
|
304
|
+
"Missing Jenkins job location. Fill jenkins.job_url in docs/ENGINEERING.md, "
|
|
305
|
+
"or pass --job-url / --job-name explicitly."
|
|
321
306
|
)
|
|
322
307
|
|
|
323
308
|
|
|
@@ -331,17 +316,16 @@ def _resolve_jenkins_job_candidates(
|
|
|
331
316
|
branch_name: str = "",
|
|
332
317
|
) -> List[str]:
|
|
333
318
|
jenkins_cfg = (cfg.get("jenkins") or {})
|
|
334
|
-
effective_branch = str(branch_name or
|
|
319
|
+
effective_branch = str(branch_name or "").strip()
|
|
335
320
|
if not effective_branch:
|
|
336
321
|
inferred_branch = _resolve_git_branch_name(repo, git_ref or "HEAD")
|
|
337
322
|
if inferred_branch:
|
|
338
323
|
effective_branch = inferred_branch
|
|
339
324
|
|
|
340
|
-
effective_root = str(multibranch_root_job or
|
|
325
|
+
effective_root = str(multibranch_root_job or "").strip()
|
|
341
326
|
explicit_url = str(job_url or "").strip()
|
|
342
327
|
explicit_name = str(job_name or "").strip()
|
|
343
328
|
configured_url = str(jenkins_cfg.get("job_url") or "").strip()
|
|
344
|
-
configured_name = str(jenkins_cfg.get("job_name") or "").strip()
|
|
345
329
|
|
|
346
330
|
if effective_branch:
|
|
347
331
|
if explicit_url:
|
|
@@ -354,12 +338,9 @@ def _resolve_jenkins_job_candidates(
|
|
|
354
338
|
return _jenkins_branch_job_urls(_jenkins_job_url_from_name(base_url, explicit_name), effective_branch)
|
|
355
339
|
if configured_url:
|
|
356
340
|
return _jenkins_branch_job_urls(configured_url, effective_branch)
|
|
357
|
-
if configured_name:
|
|
358
|
-
base_url = str(jenkins_cfg.get("base_url") or "").strip()
|
|
359
|
-
return _jenkins_branch_job_urls(_jenkins_job_url_from_name(base_url, configured_name), effective_branch)
|
|
360
341
|
raise APError(
|
|
361
|
-
"Missing Jenkins multibranch root job location.
|
|
362
|
-
"or pass --
|
|
342
|
+
"Missing Jenkins multibranch root job location. Pass --job-url / --job-name together with "
|
|
343
|
+
"--branch-name, or pass --multibranch-root-job with jenkins.base_url."
|
|
363
344
|
)
|
|
364
345
|
|
|
365
346
|
return [_resolve_jenkins_job_url(cfg, job_name=job_name, job_url=job_url)]
|
|
@@ -404,17 +385,8 @@ def cmd_install(args: argparse.Namespace) -> None:
|
|
|
404
385
|
copy_tree(Path(__file__).resolve().parent / "core.py", tools_dir / "core.py")
|
|
405
386
|
copy_tree(Path(__file__).resolve().parent / "http_checks.py", tools_dir / "http_checks.py")
|
|
406
387
|
|
|
407
|
-
gi = repo / ".gitignore"
|
|
408
|
-
secret_line = "docs/ENGINEERING.md"
|
|
409
|
-
if gi.exists():
|
|
410
|
-
txt = gi.read_text(encoding="utf-8")
|
|
411
|
-
if secret_line not in txt:
|
|
412
|
-
gi.write_text(txt.rstrip() + "\n" + secret_line + "\n", encoding="utf-8")
|
|
413
|
-
else:
|
|
414
|
-
gi.write_text(secret_line + "\n", encoding="utf-8")
|
|
415
|
-
|
|
416
388
|
print(f"[install] OK: scaffold installed into {repo}")
|
|
417
|
-
print("[install] Next: edit docs/ENGINEERING.md frontmatter and
|
|
389
|
+
print("[install] Next: edit docs/ENGINEERING.md frontmatter, fill all platform credentials, and commit that file into Git.")
|
|
418
390
|
|
|
419
391
|
|
|
420
392
|
def _infer_title(taskbook: Path, task_id: str) -> str:
|
|
@@ -482,7 +454,7 @@ def cmd_gen_summary(args: argparse.Namespace) -> None:
|
|
|
482
454
|
- 变更记录位置:`{api_change_log}`
|
|
483
455
|
|
|
484
456
|
## 4. 质量证据
|
|
485
|
-
- 本地轻量校验:
|
|
457
|
+
- 本地轻量校验:light_gate or quick_test/test/build / api docs / jenkins / diff-check — TODO
|
|
486
458
|
- Jenkins Build:TODO
|
|
487
459
|
- 目标环境验证:TODO
|
|
488
460
|
- 闭环记录:TODO
|
|
@@ -568,23 +540,40 @@ def _load_cfg(repo: Path) -> dict:
|
|
|
568
540
|
return load_yaml(cfg_path)
|
|
569
541
|
|
|
570
542
|
|
|
571
|
-
def
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
return
|
|
543
|
+
def _text(value: object) -> str:
|
|
544
|
+
return str(value or "").strip()
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _is_placeholder(value: object) -> bool:
|
|
548
|
+
return _text(value).upper() in _INVALID_PLACEHOLDERS
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _is_explicit_fill(value: object) -> bool:
|
|
552
|
+
return bool(_text(value)) and not _is_placeholder(value)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _validate_url_field(errors: List[str], field: str, value: object) -> None:
|
|
556
|
+
raw = _text(value)
|
|
557
|
+
parsed = urllib.parse.urlparse(raw)
|
|
558
|
+
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
|
|
559
|
+
errors.append(f"{field} must be a valid http/https URL")
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _validate_path_field(errors: List[str], field: str, value: object) -> None:
|
|
563
|
+
raw = _text(value)
|
|
564
|
+
if not raw.startswith("/"):
|
|
565
|
+
errors.append(f"{field} must start with '/'")
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _require_explicit_field(missing: List[str], field: str, value: object) -> None:
|
|
569
|
+
raw = _text(value)
|
|
570
|
+
if not _is_explicit_fill(raw):
|
|
571
|
+
missing.append(f"{field} (must be explicitly filled, not blank/TODO)")
|
|
577
572
|
|
|
578
573
|
|
|
579
574
|
def _run_git_diff_check(repo: Path, cfg: dict) -> None:
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if configured:
|
|
583
|
-
print(f"[diff-check] {configured}")
|
|
584
|
-
run_shell(configured, cwd=repo)
|
|
585
|
-
else:
|
|
586
|
-
print("[diff-check] git diff --check")
|
|
587
|
-
run(["git", "diff", "--check"], cwd=repo)
|
|
575
|
+
print("[diff-check] git diff --check")
|
|
576
|
+
run(["git", "diff", "--check"], cwd=repo)
|
|
588
577
|
print("[diff-check] OK")
|
|
589
578
|
|
|
590
579
|
|
|
@@ -611,44 +600,27 @@ def cmd_run(args: argparse.Namespace) -> None:
|
|
|
611
600
|
|
|
612
601
|
def cmd_light_gate(args: argparse.Namespace) -> None:
|
|
613
602
|
repo = Path(args.repo).resolve()
|
|
603
|
+
cmd_doctor(argparse.Namespace(repo=str(repo)))
|
|
614
604
|
cfg = _load_cfg(repo)
|
|
615
605
|
commands = (cfg.get("commands") or {})
|
|
616
606
|
|
|
617
607
|
executed: List[str] = []
|
|
618
608
|
missing: List[str] = []
|
|
619
609
|
|
|
620
|
-
if
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
executed.append("build")
|
|
625
|
-
|
|
626
|
-
if str(commands.get("quick_test") or "").strip():
|
|
610
|
+
if str(commands.get("light_gate") or "").strip():
|
|
611
|
+
_run_configured_command(repo, cfg, "light_gate")
|
|
612
|
+
executed.append("light_gate")
|
|
613
|
+
elif str(commands.get("quick_test") or "").strip():
|
|
627
614
|
_run_configured_command(repo, cfg, "quick_test")
|
|
628
615
|
executed.append("quick_test")
|
|
629
616
|
elif str(commands.get("test") or "").strip():
|
|
630
617
|
_run_configured_command(repo, cfg, "test")
|
|
631
618
|
executed.append("test")
|
|
619
|
+
elif str(commands.get("build") or "").strip():
|
|
620
|
+
_run_configured_command(repo, cfg, "build")
|
|
621
|
+
executed.append("build")
|
|
632
622
|
else:
|
|
633
|
-
missing.append("commands.quick_test or commands.test")
|
|
634
|
-
|
|
635
|
-
static_executed = False
|
|
636
|
-
if str(commands.get("lint") or "").strip():
|
|
637
|
-
_run_configured_command(repo, cfg, "lint")
|
|
638
|
-
executed.append("lint")
|
|
639
|
-
static_executed = True
|
|
640
|
-
|
|
641
|
-
if str(commands.get("typecheck") or "").strip():
|
|
642
|
-
_run_configured_command(repo, cfg, "typecheck")
|
|
643
|
-
executed.append("typecheck")
|
|
644
|
-
static_executed = True
|
|
645
|
-
|
|
646
|
-
if not static_executed:
|
|
647
|
-
missing.append("commands.lint or commands.typecheck")
|
|
648
|
-
|
|
649
|
-
if str(commands.get("script_syntax") or "").strip():
|
|
650
|
-
_run_configured_command(repo, cfg, "script_syntax")
|
|
651
|
-
executed.append("script_syntax")
|
|
623
|
+
missing.append("commands.light_gate or commands.quick_test or commands.test or commands.build")
|
|
652
624
|
|
|
653
625
|
if missing:
|
|
654
626
|
raise APError(
|
|
@@ -668,8 +640,6 @@ def cmd_runtime_up(args: argparse.Namespace) -> None:
|
|
|
668
640
|
repo = Path(args.repo).resolve()
|
|
669
641
|
cfg = _load_cfg(repo)
|
|
670
642
|
runtime_cfg = (cfg.get("runtime") or {})
|
|
671
|
-
if _run_configured_command(repo, cfg, "compose_up"):
|
|
672
|
-
return
|
|
673
643
|
compose_args = _compose_base_args(runtime_cfg) + ["up", "-d"]
|
|
674
644
|
docker_service = str(runtime_cfg.get("docker_service") or "").strip()
|
|
675
645
|
if docker_service:
|
|
@@ -683,8 +653,6 @@ def cmd_runtime_down(args: argparse.Namespace) -> None:
|
|
|
683
653
|
repo = Path(args.repo).resolve()
|
|
684
654
|
cfg = _load_cfg(repo)
|
|
685
655
|
runtime_cfg = (cfg.get("runtime") or {})
|
|
686
|
-
if _run_configured_command(repo, cfg, "compose_down"):
|
|
687
|
-
return
|
|
688
656
|
compose_args = _compose_base_args(runtime_cfg) + ["down", "--remove-orphans"]
|
|
689
657
|
print(f"[runtime-down] {' '.join(compose_args)}")
|
|
690
658
|
run(compose_args, cwd=repo)
|
|
@@ -770,10 +738,7 @@ def cmd_verify_target(args: argparse.Namespace) -> None:
|
|
|
770
738
|
raise APError(f"Frontend target verification failed: {url} -> {status}\n{body[:400]}")
|
|
771
739
|
checks.append(f"frontend:{url}->{status}")
|
|
772
740
|
|
|
773
|
-
note = str(target_cfg.get("verify_notes") or "").strip()
|
|
774
741
|
summary = ", ".join(checks) if checks else "health-only"
|
|
775
|
-
if note:
|
|
776
|
-
summary = summary + f" | notes={note}"
|
|
777
742
|
print(f"[verify-target] OK: {summary}")
|
|
778
743
|
|
|
779
744
|
|
|
@@ -788,6 +753,8 @@ def cmd_verify_jenkins(args: argparse.Namespace) -> None:
|
|
|
788
753
|
raise APError(f"Jenkinsfile not found: {jenkinsfile}")
|
|
789
754
|
|
|
790
755
|
required = [
|
|
756
|
+
("jenkins.base_url", jenkins_cfg.get("base_url")),
|
|
757
|
+
("jenkins.job_url", jenkins_cfg.get("job_url")),
|
|
791
758
|
("jenkins.trigger_branch", jenkins_cfg.get("trigger_branch")),
|
|
792
759
|
("jenkins.image_repository", jenkins_cfg.get("image_repository")),
|
|
793
760
|
("jenkins.image_tag_strategy", jenkins_cfg.get("image_tag_strategy")),
|
|
@@ -796,15 +763,6 @@ def cmd_verify_jenkins(args: argparse.Namespace) -> None:
|
|
|
796
763
|
("target_env.health_path", target_cfg.get("health_path")),
|
|
797
764
|
]
|
|
798
765
|
missing = [name for name, value in required if not str(value or "").strip()]
|
|
799
|
-
if not str(jenkins_cfg.get("job_url") or "").strip():
|
|
800
|
-
base_url = str(jenkins_cfg.get("base_url") or "").strip()
|
|
801
|
-
job_name = str(jenkins_cfg.get("job_name") or "").strip()
|
|
802
|
-
multibranch_root = str(jenkins_cfg.get("multibranch_root_job") or "").strip()
|
|
803
|
-
if not (base_url and job_name) and not (base_url and multibranch_root):
|
|
804
|
-
missing.append(
|
|
805
|
-
"jenkins.job_url (or jenkins.base_url + jenkins.job_name, "
|
|
806
|
-
"or jenkins.base_url + jenkins.multibranch_root_job)"
|
|
807
|
-
)
|
|
808
766
|
if missing:
|
|
809
767
|
raise APError("Missing Jenkins config: " + ", ".join(missing))
|
|
810
768
|
print(f"[verify-jenkins] OK: {jenkinsfile}")
|
|
@@ -825,33 +783,35 @@ def cmd_doctor(args: argparse.Namespace) -> None:
|
|
|
825
783
|
|
|
826
784
|
if not str(project_cfg.get("name") or "").strip():
|
|
827
785
|
missing.append("project.name")
|
|
828
|
-
if not
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
786
|
+
if not (
|
|
787
|
+
str(commands.get("light_gate") or "").strip()
|
|
788
|
+
or str(commands.get("quick_test") or "").strip()
|
|
789
|
+
or str(commands.get("test") or "").strip()
|
|
790
|
+
or str(commands.get("build") or "").strip()
|
|
791
|
+
):
|
|
792
|
+
missing.append("commands.light_gate or commands.quick_test or commands.test or commands.build")
|
|
793
|
+
_require_explicit_field(missing, "target_env.name", target_cfg.get("name"))
|
|
794
|
+
_require_explicit_field(missing, "target_env.frontend_base_url", target_cfg.get("frontend_base_url"))
|
|
795
|
+
_require_explicit_field(missing, "target_env.frontend_username", target_cfg.get("frontend_username"))
|
|
796
|
+
_require_explicit_field(missing, "target_env.frontend_password", target_cfg.get("frontend_password"))
|
|
797
|
+
_require_explicit_field(missing, "target_env.backend_base_url", target_cfg.get("backend_base_url"))
|
|
798
|
+
_require_explicit_field(missing, "target_env.backend_username", target_cfg.get("backend_username"))
|
|
799
|
+
_require_explicit_field(missing, "target_env.backend_password", target_cfg.get("backend_password"))
|
|
800
|
+
_require_explicit_field(missing, "target_env.backend_root_username", target_cfg.get("backend_root_username"))
|
|
801
|
+
_require_explicit_field(missing, "target_env.backend_root_password", target_cfg.get("backend_root_password"))
|
|
802
|
+
_require_explicit_field(missing, "target_env.health_base_url", target_cfg.get("health_base_url"))
|
|
803
|
+
_require_explicit_field(missing, "target_env.health_path", target_cfg.get("health_path"))
|
|
804
|
+
|
|
805
|
+
_require_explicit_field(missing, "jenkins.base_url", jenkins_cfg.get("base_url"))
|
|
806
|
+
_require_explicit_field(missing, "jenkins.ui_username", jenkins_cfg.get("ui_username"))
|
|
807
|
+
_require_explicit_field(missing, "jenkins.ui_password", jenkins_cfg.get("ui_password"))
|
|
808
|
+
_require_explicit_field(missing, "jenkins.job_url", jenkins_cfg.get("job_url"))
|
|
809
|
+
_require_explicit_field(missing, "jenkins.trigger_branch", jenkins_cfg.get("trigger_branch"))
|
|
810
|
+
_require_explicit_field(missing, "jenkins.image_repository", jenkins_cfg.get("image_repository"))
|
|
811
|
+
_require_explicit_field(missing, "jenkins.image_tag_strategy", jenkins_cfg.get("image_tag_strategy"))
|
|
812
|
+
_require_explicit_field(missing, "jenkins.deploy_env", jenkins_cfg.get("deploy_env"))
|
|
813
|
+
_require_explicit_field(missing, "jenkins.api_user", jenkins_cfg.get("api_user"))
|
|
814
|
+
_require_explicit_field(missing, "jenkins.api_password", jenkins_cfg.get("api_password"))
|
|
855
815
|
|
|
856
816
|
repo_docs = {
|
|
857
817
|
"docs.taskbook": Path(repo, str(docs_cfg.get("taskbook", "docs/tasks/taskbook.md"))),
|
|
@@ -863,30 +823,31 @@ def cmd_doctor(args: argparse.Namespace) -> None:
|
|
|
863
823
|
if not path.exists():
|
|
864
824
|
warnings.append(f"{key} missing on disk: {path}")
|
|
865
825
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
api_user = str(jenkins_cfg.get("api_user") or "").strip()
|
|
874
|
-
api_secret = str(jenkins_cfg.get("api_token") or jenkins_cfg.get("api_password") or "").strip()
|
|
875
|
-
if _pair_state(api_user, api_secret) == "partial":
|
|
876
|
-
warnings.append("jenkins.api_user with jenkins.api_token/api_password is partial")
|
|
826
|
+
_validate_url_field(warnings, "target_env.frontend_base_url", target_cfg.get("frontend_base_url"))
|
|
827
|
+
_validate_url_field(warnings, "target_env.backend_base_url", target_cfg.get("backend_base_url"))
|
|
828
|
+
_validate_url_field(warnings, "target_env.health_base_url", target_cfg.get("health_base_url"))
|
|
829
|
+
_validate_path_field(warnings, "target_env.health_path", target_cfg.get("health_path"))
|
|
830
|
+
_validate_url_field(warnings, "jenkins.base_url", jenkins_cfg.get("base_url"))
|
|
831
|
+
_validate_url_field(warnings, "jenkins.job_url", jenkins_cfg.get("job_url"))
|
|
877
832
|
|
|
878
833
|
runtime_enabled = any(str(runtime_cfg.get(key) or "").strip() for key in ["docker_compose_file", "docker_service", "health_base_url", "health_path"])
|
|
879
|
-
if runtime_enabled and not
|
|
880
|
-
warnings.append("runtime config is partially enabled but
|
|
834
|
+
if runtime_enabled and not str(runtime_cfg.get("docker_compose_file") or "").strip():
|
|
835
|
+
warnings.append("runtime config is partially enabled but runtime.docker_compose_file is missing")
|
|
836
|
+
|
|
837
|
+
try:
|
|
838
|
+
timeout_s = int(jenkins_cfg.get("deploy_timeout_sec") or 0)
|
|
839
|
+
if timeout_s <= 0:
|
|
840
|
+
warnings.append("jenkins.deploy_timeout_sec must be a positive integer")
|
|
841
|
+
except Exception:
|
|
842
|
+
warnings.append("jenkins.deploy_timeout_sec must be a positive integer")
|
|
843
|
+
|
|
844
|
+
if warnings:
|
|
845
|
+
missing.extend([f"invalid {item}" for item in warnings])
|
|
881
846
|
|
|
882
847
|
if missing:
|
|
883
|
-
raise APError("Doctor found blocking config issues:\n- " + "\n- ".join(missing)
|
|
848
|
+
raise APError("Doctor found blocking config issues:\n- " + "\n- ".join(missing))
|
|
884
849
|
|
|
885
850
|
print("[doctor] OK")
|
|
886
|
-
if warnings:
|
|
887
|
-
print("[doctor] Warnings:")
|
|
888
|
-
for item in warnings:
|
|
889
|
-
print(f"- {item}")
|
|
890
851
|
|
|
891
852
|
|
|
892
853
|
def cmd_verify_jenkins_build(args: argparse.Namespace) -> None:
|
|
@@ -908,12 +869,10 @@ def cmd_verify_jenkins_build(args: argparse.Namespace) -> None:
|
|
|
908
869
|
timeout_s = int(args.timeout_sec or 300)
|
|
909
870
|
poll_s = int(args.poll_sec or 5)
|
|
910
871
|
inferred_branch = _resolve_git_branch_name(repo, git_ref)
|
|
911
|
-
branch_hint = str(args.branch_name or
|
|
872
|
+
branch_hint = str(args.branch_name or inferred_branch or "").strip()
|
|
912
873
|
root_hint = str(
|
|
913
874
|
args.multibranch_root_job
|
|
914
|
-
or jenkins_cfg.get("multibranch_root_job")
|
|
915
875
|
or args.job_name
|
|
916
|
-
or jenkins_cfg.get("job_name")
|
|
917
876
|
or args.job_url
|
|
918
877
|
or jenkins_cfg.get("job_url")
|
|
919
878
|
or ""
|
|
@@ -1064,6 +1023,7 @@ def cmd_record_closure(args: argparse.Namespace) -> None:
|
|
|
1064
1023
|
def cmd_commit_push(args: argparse.Namespace) -> None:
|
|
1065
1024
|
repo = Path(args.repo).resolve()
|
|
1066
1025
|
ensure_git_repo(repo)
|
|
1026
|
+
cmd_doctor(argparse.Namespace(repo=str(repo)))
|
|
1067
1027
|
|
|
1068
1028
|
msg = args.msg
|
|
1069
1029
|
|
package/cli/src/index.js
CHANGED
|
@@ -62,19 +62,6 @@ function resolveTargetDir(ai, mode, destOverride){
|
|
|
62
62
|
die(`unknown ai: ${ai}`);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function ensureGitignore(projectDir){
|
|
66
|
-
const gi = path.join(projectDir, ".gitignore");
|
|
67
|
-
const line = "docs/ENGINEERING.md";
|
|
68
|
-
if (!exists(gi)) {
|
|
69
|
-
fs.writeFileSync(gi, `${line}\n`, "utf-8");
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const txt = fs.readFileSync(gi, "utf-8");
|
|
73
|
-
if (!txt.includes(line)) {
|
|
74
|
-
fs.appendFileSync(gi, (txt.endsWith("\n") ? "" : "\n") + line + "\n");
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
65
|
function main(){
|
|
79
66
|
const args = parseArgs(process.argv);
|
|
80
67
|
|
|
@@ -104,8 +91,6 @@ Examples:
|
|
|
104
91
|
const assetSkill = path.resolve(here, "..", "assets", "skill");
|
|
105
92
|
if (!exists(assetSkill)) die(`missing assets at ${assetSkill}`);
|
|
106
93
|
|
|
107
|
-
const proj = projectRoot();
|
|
108
|
-
|
|
109
94
|
for (const t of targets) {
|
|
110
95
|
const dstOverride = args.dest
|
|
111
96
|
? (targets.length > 1 ? path.join(args.dest, t) : args.dest)
|
|
@@ -119,8 +104,6 @@ Examples:
|
|
|
119
104
|
console.log(`[autocoding] installed skill to: ${dst}`);
|
|
120
105
|
}
|
|
121
106
|
|
|
122
|
-
if (args.mode === "project") ensureGitignore(proj);
|
|
123
|
-
|
|
124
107
|
console.log("[autocoding] done.");
|
|
125
108
|
}
|
|
126
109
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elvis1513/auto-coding-skill",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "CLI installer for auto-coding-skill (Claude Code + Codex CLI) with Go fullstack + Jenkins workflow support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"LICENSE"
|
|
14
14
|
],
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "node -c cli/src/index.js",
|
|
16
|
+
"test": "node -c cli/src/index.js && python3 -m py_compile src/auto-coding-skill/scripts/ap.py src/auto-coding-skill/scripts/core.py src/auto-coding-skill/scripts/http_checks.py cli/assets/skill/scripts/ap.py cli/assets/skill/scripts/core.py cli/assets/skill/scripts/http_checks.py && find src cli -type d -name '__pycache__' -prune -exec rm -rf {} +",
|
|
17
17
|
"sync-assets": "node cli/src/sync-assets.js",
|
|
18
18
|
"release:check": "npm run sync-assets && npm run test && npm pack --dry-run"
|
|
19
19
|
},
|