@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 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` or environment variables.
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
- - build
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
- - 本地完整 smoke / regression
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 / smoke / regression
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` 或 `jenkins.base_url + jenkins.job_name` 或 `jenkins.base_url + jenkins.multibranch_root_job`
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 accounts, Jenkins UI/API accounts, commands, docs paths here only.
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 fails if required commands are missing instead of returning `OK` after skipping everything.
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` or `jenkins.base_url + jenkins.job_name` or `jenkins.base_url + jenkins.multibranch_root_job`
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 only: build, unit/quick test, lint, typecheck, API docs, Jenkinsfile / script syntax, `git diff --check`.
137
- - `doctor` should be used early to catch missing config before the first implementation loop.
138
- - `light-gate` now fails if the required default commands are not configured.
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
- - 默认不要求本地完整 smoke / regression。
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 账号、Job、分支、镜像、部署环境
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.frontend_username` / `target_env.frontend_password`:目标环境前端登录账号(如需要)
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` + `jenkins.api_token` / `jenkins.api_password`:Jenkins API 校验凭据
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` 或 `jenkins.base_url + jenkins.job_name` 或 `jenkins.base_url + jenkins.multibranch_root_job`
126
+ - `jenkins.job_url`
129
127
 
130
128
  按需填写:
131
129
  - `runtime.*`:仅在本地运行诊断时使用
132
- - `commands.compose_up` / `commands.compose_down` / `commands.smoke` / `commands.regression`
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
- - 编译 / build
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:lint / typecheck / script_syntax / diff_check
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
- - jenkinsfile / scripts
13
+ - jenkins
15
14
 
16
15
  ## 3. 按需本地运行验证(如果有)
17
- - runtime-up / health / smoke / regression
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
- - 本地轻量校验:build / test or quick_test / lint / typecheck / api docs / jenkinsfile / diff-check
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
- direct_user = str(jenkins_cfg.get("api_user") or jenkins_cfg.get("ui_username") or "").strip()
64
- direct_token = str(jenkins_cfg.get("api_token") or "").strip()
65
- direct_password = str(jenkins_cfg.get("api_password") or jenkins_cfg.get("ui_password") or "").strip()
66
- user_env = str(jenkins_cfg.get("api_user_env") or "JENKINS_USER").strip() or "JENKINS_USER"
67
- token_env = str(jenkins_cfg.get("api_token_env") or "JENKINS_TOKEN").strip() or "JENKINS_TOKEN"
68
- password_env = str(jenkins_cfg.get("api_password_env") or "JENKINS_PASSWORD").strip() or "JENKINS_PASSWORD"
69
- user = direct_user or os.getenv(user_env) or os.getenv("JENKINS_USER")
70
- token = (
71
- direct_token
72
- or direct_password
73
- or os.getenv(token_env)
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 / jenkins.api_token / jenkins.api_password "
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}:{token}".encode("utf-8")
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
- raise APError(
207
- f"Jenkins API request failed: {url}\n"
208
- f"HTTP 403\n{body or '(empty response body)'}\n"
209
- "Jenkins may require crumb/CSRF handling, but no crumb issuer endpoint was available. "
210
- "Fill jenkins.base_url or jenkins.crumb_url in docs/ENGINEERING.md if needed."
211
- ) from exc
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, or fill both "
320
- "jenkins.base_url and jenkins.job_name in docs/ENGINEERING.md."
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 jenkins_cfg.get("branch_name") or "").strip()
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 jenkins_cfg.get("multibranch_root_job") or "").strip()
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. Fill jenkins.multibranch_root_job + jenkins.base_url, "
362
- "or pass --job-url / --job-name together with --branch-name."
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 fill project/commands/target_env/jenkins fields")
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
- - 本地轻量校验:build / test or quick_test / lint / typecheck / api docs / jenkinsfile / diff-check — TODO
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 _pair_state(left: str, right: str) -> str:
572
- if left and right:
573
- return "complete"
574
- if not left and not right:
575
- return "empty"
576
- return "partial"
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
- commands = (cfg.get("commands") or {})
581
- configured = str(commands.get("diff_check") or "").strip()
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 not str(commands.get("build") or "").strip():
621
- missing.append("commands.build")
622
- else:
623
- _run_configured_command(repo, cfg, "build")
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 str(commands.get("build") or "").strip():
829
- missing.append("commands.build")
830
- if not (str(commands.get("quick_test") or "").strip() or str(commands.get("test") or "").strip()):
831
- missing.append("commands.quick_test or commands.test")
832
- if not (str(commands.get("lint") or "").strip() or str(commands.get("typecheck") or "").strip()):
833
- missing.append("commands.lint or commands.typecheck")
834
- if not str(target_cfg.get("name") or "").strip():
835
- missing.append("target_env.name")
836
- if not str(target_cfg.get("health_base_url") or "").strip():
837
- missing.append("target_env.health_base_url")
838
- if not str(target_cfg.get("health_path") or "").strip():
839
- missing.append("target_env.health_path")
840
- if not str(jenkins_cfg.get("trigger_branch") or "").strip():
841
- missing.append("jenkins.trigger_branch")
842
- if not str(jenkins_cfg.get("image_repository") or "").strip():
843
- missing.append("jenkins.image_repository")
844
- if not str(jenkins_cfg.get("image_tag_strategy") or "").strip():
845
- missing.append("jenkins.image_tag_strategy")
846
- if not str(jenkins_cfg.get("deploy_env") or "").strip():
847
- missing.append("jenkins.deploy_env")
848
-
849
- base_url = str(jenkins_cfg.get("base_url") or "").strip()
850
- job_name = str(jenkins_cfg.get("job_name") or "").strip()
851
- job_url = str(jenkins_cfg.get("job_url") or "").strip()
852
- multibranch_root = str(jenkins_cfg.get("multibranch_root_job") or "").strip()
853
- if not job_url and not (base_url and job_name) and not (base_url and multibranch_root):
854
- missing.append("jenkins.job_url or (jenkins.base_url + jenkins.job_name) or (jenkins.base_url + jenkins.multibranch_root_job)")
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
- if _pair_state(str(target_cfg.get("backend_username") or "").strip(), str(target_cfg.get("backend_password") or "").strip()) == "partial":
867
- warnings.append("target_env.backend_username / target_env.backend_password is partial")
868
- if _pair_state(str(target_cfg.get("frontend_username") or "").strip(), str(target_cfg.get("frontend_password") or "").strip()) == "partial":
869
- warnings.append("target_env.frontend_username / target_env.frontend_password is partial")
870
- if _pair_state(str(jenkins_cfg.get("ui_username") or "").strip(), str(jenkins_cfg.get("ui_password") or "").strip()) == "partial":
871
- warnings.append("jenkins.ui_username / jenkins.ui_password is partial")
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 (str(commands.get("compose_up") or "").strip() or str(runtime_cfg.get("docker_compose_file") or "").strip()):
880
- warnings.append("runtime config is partially enabled but compose_up or docker_compose_file is missing")
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) + (("\nWarnings:\n- " + "\n- ".join(warnings)) if warnings else ""))
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 jenkins_cfg.get("branch_name") or inferred_branch or "").strip()
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.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
  },