@agile-team/wl-skills-gate 0.6.1 → 0.9.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/CHANGELOG.md +24 -0
- package/README.md +89 -54
- package/assets/brand-tokens.json +45 -0
- package/assets/wl-gate-logo-dark.svg +38 -0
- package/assets/wl-gate-logo.svg +37 -0
- package/assets/wl-mark.svg +11 -0
- package/bin/wl-skills-gate.js +180 -8
- package/docs/01-quick-start.md +2 -2
- package/docs/02-config.md +7 -3
- package/docs/05-adapters.md +12 -2
- package/docs/06-gitlab.md +7 -5
- package/docs/09-selftest-coverage.md +4 -2
- package/docs/10-enforcement.md +2 -2
- package/docs/13-quality-cost-and-isolation.md +6 -6
- package/docs/14-low-token-execution.md +8 -8
- package/docs/15-java-report-triage.md +1 -1
- package/docs/16-project-ci-integration.md +64 -0
- package/docs/17-onboarding.md +75 -0
- package/docs/18-ci-source.md +42 -0
- package/docs/19-nine-dimension-review.md +105 -0
- package/docs/README.md +14 -8
- package/fixtures/onboarding/README.md +3 -0
- package/fixtures/onboarding/quality-audit-profile.xml +51 -0
- package/fixtures/onboarding/quality-unit-profile.xml +101 -0
- package/lib/adapters/cmd-json.js +12 -10
- package/lib/adapters/java-xml.js +3 -2
- package/lib/adapters/junit.js +78 -13
- package/lib/adapters/raw-json.js +4 -3
- package/lib/adapters/sarif.js +3 -3
- package/lib/automation.js +247 -0
- package/lib/ci-discovery.js +227 -0
- package/lib/ci-source.js +203 -0
- package/lib/config.js +7 -4
- package/lib/control-deployment.js +44 -0
- package/lib/control-service.js +244 -0
- package/lib/control-store.js +120 -0
- package/lib/deployment-delivery.js +89 -0
- package/lib/deployment-queue.js +101 -0
- package/lib/deployment.js +217 -0
- package/lib/doctor.js +110 -0
- package/lib/evidence.js +59 -7
- package/lib/fix.js +61 -9
- package/lib/governance-notifications.js +45 -0
- package/lib/governance.js +521 -0
- package/lib/identity.js +49 -0
- package/lib/lifecycle-source.js +161 -0
- package/lib/onboard-complete.js +333 -0
- package/lib/onboard-config.js +556 -0
- package/lib/onboard-environment.js +37 -0
- package/lib/onboard-legacy.js +43 -0
- package/lib/onboard-lifecycle.js +117 -0
- package/lib/onboard-registry.js +93 -0
- package/lib/onboard.js +118 -0
- package/lib/promote.js +17 -5
- package/lib/release-set.js +138 -0
- package/lib/report-input.js +22 -8
- package/lib/report-producers.js +37 -19
- package/lib/report-selection.js +136 -0
- package/lib/review-additions.js +159 -0
- package/lib/review-auto.js +75 -0
- package/lib/review-ci.js +119 -0
- package/lib/review-collect.js +146 -0
- package/lib/review-facts.js +160 -0
- package/lib/review-live.js +30 -0
- package/lib/review-publication.js +85 -0
- package/lib/review-publish.js +191 -0
- package/lib/review.js +376 -0
- package/lib/run.js +28 -5
- package/lib/yaml-lite.js +5 -0
- package/package.json +6 -4
- package/schemas/quality-summary.schema.json +22 -0
- package/scripts/lib-tests/automation.test.mjs +126 -0
- package/scripts/lib-tests/ci-discovery.test.mjs +123 -0
- package/scripts/lib-tests/ci-source.test.mjs +153 -0
- package/scripts/lib-tests/control-review.test.mjs +110 -0
- package/scripts/lib-tests/control-service.test.mjs +88 -0
- package/scripts/lib-tests/control-store.test.mjs +77 -0
- package/scripts/lib-tests/deployment-http.test.mjs +66 -0
- package/scripts/lib-tests/deployment.test.mjs +250 -0
- package/scripts/lib-tests/doctor.test.mjs +106 -0
- package/scripts/lib-tests/fix-state.test.mjs +55 -0
- package/scripts/lib-tests/governance-lifecycle.test.mjs +131 -0
- package/scripts/lib-tests/governance.test.mjs +240 -0
- package/scripts/lib-tests/java-unit-wrapper.test.mjs +107 -0
- package/scripts/lib-tests/junit-evidence.test.mjs +214 -0
- package/scripts/lib-tests/lifecycle-source.test.mjs +135 -0
- package/scripts/lib-tests/onboard-complete.test.mjs +324 -0
- package/scripts/lib-tests/onboard-registry.test.mjs +51 -0
- package/scripts/lib-tests/onboard-review-facts.test.mjs +89 -0
- package/scripts/lib-tests/onboard.test.mjs +180 -0
- package/scripts/lib-tests/review-additions.test.mjs +98 -0
- package/scripts/lib-tests/review-auto.test.mjs +94 -0
- package/scripts/lib-tests/review-ci.test.mjs +72 -0
- package/scripts/lib-tests/review-collect.test.mjs +61 -0
- package/scripts/lib-tests/review-facts.test.mjs +44 -0
- package/scripts/lib-tests/review-publication.test.mjs +47 -0
- package/scripts/lib-tests/review-publish.test.mjs +89 -0
- package/scripts/lib-tests/review.test.mjs +212 -0
- package/scripts/lib-tests/yaml-lite.test.mjs +21 -0
- package/scripts/run-integration.mjs +49 -7
- package/scripts/test-support/control-review-fixture.mjs +57 -0
- package/scripts/test-support/review-collect-fixture.mjs +47 -0
- package/templates/ci/Jenkinsfile +58 -0
- package/templates/ci/Jenkinsfile.attest +32 -0
- package/templates/ci/Jenkinsfile.automation +20 -0
- package/templates/ci/Jenkinsfile.deployment +78 -0
- package/templates/ci/Jenkinsfile.release-sign +26 -0
- package/templates/ci/Jenkinsfile.review-sign +30 -0
- package/templates/ci/central-review.sh +10 -0
- package/templates/ci/controlled-deployment-http.mjs +72 -0
- package/templates/ci/create-deployment-facts.mjs +7 -0
- package/templates/ci/deployment-http.json +14 -0
- package/templates/ci/gitlab-backend.yml +40 -0
- package/templates/ci/gitlab-frontend.yml +32 -0
- package/templates/ci/produce-review-facts.mjs +15 -0
- package/templates/ci/publish-reviews.mjs +5 -0
- package/templates/ci/sign-collected-release.mjs +7 -0
- package/templates/ci/wl-quality-automation.service +18 -0
- package/templates/ci/wl-quality-control.service +18 -0
- package/templates/onboarding/automation/ide-tasks.json +31 -0
- package/templates/onboarding/automation/project-actions.mjs +266 -0
- package/templates/onboarding/backend/java-unit.mjs +84 -0
- package/templates/onboarding/backend/java-unit.plan.json +15 -0
- package/templates/onboarding/backend/policy.yml +63 -0
- package/templates/onboarding/backend/quality-tools.package.json +9 -0
- package/templates/onboarding/backend/run.mjs +10 -0
- package/templates/onboarding/frontend/policy.yml +40 -0
- package/templates/onboarding/legacy/backend-commit-msg.sh +21 -0
- package/templates/onboarding/legacy/frontend-common-core-postinstall.mjs +52 -0
- package/templates/onboarding/legacy/frontend-enforce-pnpm.mjs +9 -0
- package/templates/onboarding/legacy/frontend-pre-commit.sh +15 -0
- package/templates/onboarding/legacy/frontend-pre-push.sh +16 -0
- package/templates/platform/README.md +33 -0
- package/templates/platform/action.example.json +15 -0
- package/templates/platform/automation-config.json +66 -0
- package/templates/platform/ci-source.backend.json +40 -0
- package/templates/platform/ci-source.frontend.json +42 -0
- package/templates/platform/ci-source.gitlab.json +18 -0
- package/templates/platform/ci-source.jenkins.json +31 -0
- package/templates/platform/control-config.json +48 -0
- package/templates/platform/deployment-config.json +92 -0
- package/templates/platform/event.example.json +17 -0
- package/templates/platform/expectations.example.json +16 -0
- package/templates/platform/governance-config.json +106 -0
- package/templates/platform/lifecycle-event.example.json +29 -0
- package/templates/platform/review-ci.backend.json +6 -0
- package/templates/platform/review-ci.frontend.json +6 -0
- package/templates/platform/review-collector.backend.json +59 -0
- package/templates/platform/review-collector.frontend.json +67 -0
- package/templates/platform/review-policy.backend.json +72 -0
- package/templates/platform/review-policy.frontend.json +71 -0
- package/templates/platform/review-publish-config.json +45 -0
- package/templates/quality/java-unit.plan.json +13 -0
- package/templates/quality/review-evidence.plan.json +11 -0
- package/templates/quality/review-input.example.json +25 -0
- package/templates/quality/review-policy.json +15 -0
- package/templates/quality/rules.lock.json +1 -1
- package/templates/scripts-quality/java-unit.mjs +83 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
# 0.9.0 — 2026-10-15
|
|
2
|
+
|
|
3
|
+
- 修复接入环境与依赖准备、候选Review上下文和索引、删除/暂存区修复恢复、真实阻断与必要环境关闭、消息隔离。
|
|
4
|
+
- 补齐真实CI发现与可恢复调度、九维原件采集/补证/认证确认、授权前CI重新核验、OIDC处理页与投递恢复。
|
|
5
|
+
- 补齐独立签发、前后端组合与API/数据库/顺序约束、单次部署授权、实际冒烟和显式故障恢复。
|
|
6
|
+
- 重整README为接入、运行流程与文档入口,提供可复用的WL系列字标及Gate品牌资产。
|
|
7
|
+
- 本地源码和独立tgz分别验收;业务仓库、远端权限/群/SSO与真实业务必测仍以现场回执确认。
|
|
8
|
+
|
|
9
|
+
# 0.8.0 — 2026-10-15(本地交付候选,未发布)
|
|
10
|
+
|
|
11
|
+
- Gate 收入版本化前后端接入资产和 onboard,识别实际项目、显示完整差异并绑定确认;quality-tools 明确为后端私有 Node 依赖目录。
|
|
12
|
+
- 九维 Review 草稿、同口径修前修后对比、原始证据重算和逐维独立授权;正式签发及晋级强制要求 v2 记录,旧的仅身份/SHA批准不再放行。
|
|
13
|
+
- 固定 GitLab/Jenkins API 只读采集、真实 SHA/ref/全部必需作业与精确工件校验;拒绝 WebHook 自报成功、allow_failure、缺项、取消/跳过旧报告和采集中漂移。
|
|
14
|
+
- 飞书分类卡片、Review/构建/部署独立事实流共用签名台账与待发箱;构建成功、投递成功不会关闭质量问题。
|
|
15
|
+
- 文档明确包内实现、项目实接、远端权限/身份/存储与业务复验的验收责任;本地候选不代表 registry 发布或生产已接通。
|
|
16
|
+
|
|
17
|
+
# 0.7.0 — 2026-10-13(源码修订,未发布)
|
|
18
|
+
|
|
19
|
+
- JUnit 多模块明确 files/reportDirs/requiredSuites,报告全集、必测计数、生产一次和终轮摘要验证;拒绝旧 XML、源码覆盖、解析损坏与后续篡改。
|
|
20
|
+
- doctor 只读预检项目接线,Java 单测包装器明确模块/报告全集与依赖准备,补充前后端 GitLab 和 Jenkins 接线片段。
|
|
21
|
+
- governance 最小中央签名事件台账、独立复核关闭、漏跑对账、自然分钟 SLA、消息摘要/认证跳转、飞书签名与企业微信投递;默认预览。并发租约、未知投递和死信显式失败。
|
|
22
|
+
- 台账固定配置/公钥身份,动作和通知绑定当前 revision;拒绝过期卡片、输出路径别名与损坏台账。平台 API/SSO 查询、真实接线、服务和群投递仍须操作手册实测。
|
|
23
|
+
- 同步 V3 三份文档职责与精选 code-scan-kit 规则已正式集成的定位;无需 AI API。
|
|
24
|
+
|
|
1
25
|
# 0.6.1 — 2026-10-12
|
|
2
26
|
|
|
3
27
|
- npm 发布访问策略改为 public;公开获取与使用授权分开,保留 UNLICENSED,不授予开源许可。
|
package/README.md
CHANGED
|
@@ -1,79 +1,114 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ChenyCHENYU/wl-skills-gate/v0.9.0/assets/wl-gate-logo-dark.svg" />
|
|
4
|
+
<img src="https://raw.githubusercontent.com/ChenyCHENYU/wl-skills-gate/v0.9.0/assets/wl-gate-logo.svg" width="920" alt="WL Gate — Automated quality workflow" />
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
2
7
|
|
|
3
|
-
|
|
8
|
+
<p align="center">
|
|
9
|
+
<strong>从提交检查到复验关闭的质量工程执行包</strong><br />
|
|
10
|
+
自动接入 · 脚本修复与复扫 · 九维 Review · 构建发布门禁
|
|
11
|
+
</p>
|
|
4
12
|
|
|
5
|
-
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://www.npmjs.com/package/@agile-team/wl-skills-gate"><img src="https://img.shields.io/npm/v/@agile-team/wl-skills-gate?color=0D9488&label=npm" alt="npm version" /></a>
|
|
15
|
+
<img src="https://img.shields.io/badge/Node.js-%E2%89%A522.12-172B43" alt="Node.js 22.12 or later" />
|
|
16
|
+
<img src="https://img.shields.io/badge/runtime_dependencies-0-0D9488" alt="Zero runtime dependencies" />
|
|
17
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-authorization_required-64748B" alt="Authorization required" /></a>
|
|
18
|
+
</p>
|
|
6
19
|
|
|
7
|
-
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="#三步接入"><strong>快速接入</strong></a> ·
|
|
22
|
+
<a href="#接入后怎么运行">运行流程</a> ·
|
|
23
|
+
<a href="#wl-系列怎么配合">包与目录</a> ·
|
|
24
|
+
<a href="./templates/ci">CI 模板</a> ·
|
|
25
|
+
<a href="./docs/README.md">完整文档</a> ·
|
|
26
|
+
<a href="./CHANGELOG.md">更新记录</a>
|
|
27
|
+
</p>
|
|
8
28
|
|
|
9
|
-
|
|
29
|
+
---
|
|
10
30
|
|
|
11
|
-
|
|
31
|
+
## Gate 做什么
|
|
12
32
|
|
|
13
|
-
|
|
33
|
+
Gate 把项目检查器、Git Hook、CI、Review 与处置记录接成同一条流程。开发者正常提交代码即可触发检查,技术经理和运维统一维护规则、必测、权限与通知。
|
|
14
34
|
|
|
15
|
-
|
|
35
|
+
| 需要解决的事 | Gate 的处理方式 |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| 问题到构建时才暴露 | 提交、推送阶段自动检查;CI 再做完整核验 |
|
|
38
|
+
| 修复依赖反复问 AI | 先展示问题和修复计划,确认后执行批准脚本,自动复扫 |
|
|
39
|
+
| Review 只有一张扫描结果 | 汇总九维证据、代码差异和修前修后变化,由非作者逐维确认 |
|
|
40
|
+
| 构建成功就被当作闭环 | 分别记录检查、Review、构建、部署和复验;满足关闭条件才结项 |
|
|
16
41
|
|
|
17
|
-
|
|
18
|
-
|---|---|---|
|
|
19
|
-
| 三档检查 | `run --profile fast/standard/release` | 退出码 0/1/2;失败也归档;release 要求真实干净 HEAD、合法环境、最低检查集与实际工具版本锁 |
|
|
20
|
-
| 报告 | `validate-report` | JSON/Markdown/HTML;Code Quality JSON 自动导出;JUnit 保留真实生产者 XML,不能拿转换后的计数替代原始测试报告 |
|
|
21
|
-
| 精简查询 | `inspect <quality-summary.json>` | 默认摘要;按规则/等级/状态分页;默认 12,000 UTF-8 字节上限;只读现有报告,保留原判定与全量计数,不能替代可信门禁证据 |
|
|
22
|
-
| 成本统计 | `cost <artifact-dir> --out <metrics.json>` | 离线汇总各档/各检查 P50/P95、共享生产次数、候选重复;真实 usage 可选导入,缺失保持未知,不调用模型 |
|
|
23
|
-
| 机械修复 | `fix --plan`、`fix --apply --confirm` | standard 全量报告;文件快照、白名单、防漂移、异常回滚、强制复扫;只进入待复验,不能自动关闭业务问题 |
|
|
24
|
-
| 候选检查 | `promote` | 在无特权 Runner 中合并、准备依赖、检查并归档 bundle;不更新共享分支 |
|
|
25
|
-
| 受控晋级 | `promote-apply` | 核验签名证明和独立 Review,在新 bare 仓库中核验最新远端 HEAD 后核验父提交后以批准的旧 HEAD 条件更新;不运行项目脚本或 Hook |
|
|
26
|
-
| 制品清单 | `manifest` | 绑定真实 HEAD、环境、实际文件/目录及可选配置、数据脚本摘要 |
|
|
27
|
-
| 证明签发 | `attest` | 受控独立作业签发 Ed25519 证明;仅签通过的 standard/release 证据 |
|
|
28
|
-
| Review 签发 | `review-sign` | 受控 Reviewer 作业,身份取自平台认证;非作者、候选绑定、阻断问题已处置 |
|
|
29
|
-
| 部署验证 | `verify-artifact` | 固定公钥、签发者和作业 ID、SHA、release 档、环境、策略/基线/例外、时效、真实制品摘要全部核验 |
|
|
30
|
-
| 自测 | `self-test`、`npm test` | self-test 只跑 29 个 fixture;npm test 额外跑单元与本地 Git/签名/制品集成 |
|
|
42
|
+
**九维范围:** 提交 · 分支 · 编码 · UI 与交互 · Code Review · 测试 · 扫描门禁 · CI/CD · 工程治理。
|
|
31
43
|
|
|
32
|
-
##
|
|
44
|
+
## 三步接入
|
|
33
45
|
|
|
34
|
-
|
|
46
|
+
准备 **Node.js ≥22.12**,并使用项目固定的包管理器、检查器及 Java 工具链。以下命令由接入维护者执行一次;安装源可替换为团队批准的镜像。
|
|
47
|
+
|
|
48
|
+
**1. 预览。** 在实际 Git 项目上识别技术栈,查看文件差异、固定依赖、Hook、检查范围和阻断项。
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx --yes @agile-team/wl-skills-gate@0.9.0 onboard \
|
|
52
|
+
--root /path/to/project --complete --registry https://registry.npmjs.org
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**2. 确认执行。** 审核预览后,使用本次返回的 `planHash` 应用同一计划。
|
|
35
56
|
|
|
36
57
|
```bash
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
pnpm exec wl-skills-gate run --profile standard
|
|
41
|
-
pnpm exec wl-skills-gate validate-report quality-reports/quality-summary.json
|
|
42
|
-
# 给人工/AI 查看现有报告,不再执行扫描;完整报告仍独立归档。
|
|
43
|
-
pnpm exec wl-skills-gate inspect quality-reports/quality-summary.json
|
|
44
|
-
pnpm exec wl-skills-gate inspect quality-reports/quality-summary.json --view findings --status new --limit 10
|
|
58
|
+
npx --yes @agile-team/wl-skills-gate@0.9.0 onboard \
|
|
59
|
+
--root /path/to/project --complete --registry https://registry.npmjs.org \
|
|
60
|
+
--apply --plan-hash "<本次 planHash>" --confirm
|
|
45
61
|
```
|
|
46
62
|
|
|
47
|
-
|
|
63
|
+
接入任务按计划安装依赖、同步适用规则、配置 Hook,并验证真实检查和报告。失败保留阶段回执,修正后续跑。
|
|
64
|
+
|
|
65
|
+
**3. 验证效果。** 查看首份报告及 Hook 正反例,再接入下表的 CI 与中央服务。日常提交、推送自动检查,开发者不用输入扫描命令。
|
|
66
|
+
|
|
67
|
+
| 项目 | 工具装在哪里 | 业务构建 |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| PC Vue 前端,如 `wl-ui-produce` | 现有 `package.json` / `node_modules` | 沿用原前端构建 |
|
|
70
|
+
| Java/Maven 后端,如 `wl-produce` | 独立的 `quality-tools/` 目录 | 沿用 Maven,Node 工具不进入 jar |
|
|
71
|
+
|
|
72
|
+
内置后端配套针对 `wl-produce` PL 模块;其他 Maven 项目先批准自己的模块与必测配套。移动端、UniApp 不套用 PC 配套。完整步骤见 [项目接入](./docs/17-onboarding.md)。
|
|
73
|
+
|
|
74
|
+
## 接入后怎么运行
|
|
48
75
|
|
|
49
|
-
|
|
76
|
+
| 环节 | 自动执行 | 人员操作与结果 |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| 提交 / 推送 | Hook 执行快速或标准检查,生成报告 | 有阻断先处理,保留项目已有提交约定 |
|
|
79
|
+
| 修复 / 复扫 | 按批准计划执行脚本,核对变更范围并复扫 | 确认后修复;查看新问题、剩余项与真实差异 |
|
|
80
|
+
| 九维 Review | 收集 Git、CI、测试及责任证据,生成待确认记录 | 非作者补充实际 UI / 影响 / 外部测试材料,再逐维确认 |
|
|
81
|
+
| 合并 / 发布 | 核验候选 SHA、正式 Review、制品、环境和发布组合 | 合并、部署和恢复分别授权;冒烟由独立验证人确认 |
|
|
82
|
+
| 通知 / 关闭 | 分类推送,漏跑与超时对账,失败重试并留证 | 主备认领,按必要环境复验后关闭;复发重新打开 |
|
|
50
83
|
|
|
51
|
-
|
|
84
|
+
**项目接入与远端接线分别验收。** 运维配置受保护分支、必需作业、固定 Runner 和独立凭据;中央服务连接真实 CI、SSO、证据存储与飞书群。可直接使用包内 [CI 模板](./templates/ci) 和 [中央配置](./templates/platform/README.md)。
|
|
52
85
|
|
|
53
|
-
|
|
54
|
-
- P3C 使用 `format: p3c`;XML 的 PMD 引擎版本与 P3C 提供者版本分开记录,未提供的版本保持未知。
|
|
55
|
-
- SpotBugs 多模块配置显式 `sourceRoots`。未配置只兼容实际存在的单模块 `src/main/java`;缺失、歧义和符号链接定位返回 invalid。
|
|
56
|
-
- 成员身份进入 Java 指纹,旧成员基线可能需要重新审查。七项 PMD 底线不可基线/豁免;SpotBugs 四类上下文复核 warning 保留发现,不自动排除。
|
|
57
|
-
- `rules.lock.json` 的 Gate 版本须与实际安装版本一致;模板中的检查器版本占位值仍需替换为真实版本。
|
|
86
|
+
检查退出码:`0` 通过、`1` 阻断、`2` 执行或证据无效。扫描通过、消息送达和人工补证都不会自动清除必需测试失败或批准发布。
|
|
58
87
|
|
|
59
|
-
##
|
|
88
|
+
## wl 系列怎么配合
|
|
60
89
|
|
|
61
|
-
|
|
90
|
+
| 名称 | 职责 |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| **Gate** | 统一接入、执行检查、裁决、修复复扫、证据与处置流程 |
|
|
93
|
+
| **Kit** | PC Vue 管理端编码与工程约定 |
|
|
94
|
+
| **UI** | PC 管理端 UI、样式与交互检查 |
|
|
95
|
+
| **BD** | Java 后端规范、规则资产及检查能力 |
|
|
96
|
+
| `quality-tools/` | Java 仓库的 Node 工具目录,里面安装 Gate / BD;不是另一个包 |
|
|
62
97
|
|
|
63
|
-
|
|
98
|
+
Gate 复用项目已有 ESLint、类型检查、Maven、JUnit 及适用规则包,统一报告与裁决。检查、修复、归档和通知由脚本执行,Gate 不调用大模型 API。按需查看报告可用 [`inspect`](./docs/14-low-token-execution.md),无需重复扫描。
|
|
64
99
|
|
|
65
|
-
##
|
|
100
|
+
## 文档入口
|
|
66
101
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
-
|
|
102
|
+
| 要做什么 | 从这里开始 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| 项目接入、Hook 与首扫 | [接入指南](./docs/17-onboarding.md) · [项目与运维集成](./docs/16-project-ci-integration.md) |
|
|
105
|
+
| 配置检查器、范围与等级 | [配置](./docs/02-config.md) · [适配器](./docs/05-adapters.md) · [Java 报告](./docs/15-java-report-triage.md) |
|
|
106
|
+
| CI、SSO、飞书与处置 | [可信 CI 来源](./docs/18-ci-source.md) · [中央接线](./templates/platform/README.md) |
|
|
107
|
+
| 九维证据与正式 Review | [Review 契约](./docs/19-nine-dimension-review.md) |
|
|
108
|
+
| 验证、升级与排障 | [完整索引](./docs/README.md) · [自测与环境验收](./docs/09-selftest-coverage.md) · [更新记录](./CHANGELOG.md) |
|
|
74
109
|
|
|
75
|
-
##
|
|
110
|
+
## 开发与授权
|
|
76
111
|
|
|
77
|
-
|
|
112
|
+
仓库验证使用 `npm test`,包含单元、规则场景及本地 Git / 签名 / 制品集成测试。发布前对固定 tgz 独立安装并复验。
|
|
78
113
|
|
|
79
|
-
|
|
114
|
+
[WL 系列标识](./assets/wl-mark.svg)与[品牌配色](./assets/brand-tokens.json)随包交付,后续 wl 包可复用同一字标并区分模块色。使用授权见 [LICENSE](./LICENSE);npm 公开可下载不等于开源授权,授权申请通过[仓库 Issues](https://github.com/ChenyCHENYU/wl-skills-gate/issues)。
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"series": "WL Engineering",
|
|
4
|
+
"product": "WL Gate",
|
|
5
|
+
"tagline": "Automated quality workflow",
|
|
6
|
+
"mark": {
|
|
7
|
+
"file": "wl-mark.svg",
|
|
8
|
+
"viewBox": "0 0 128 128",
|
|
9
|
+
"color": "currentColor",
|
|
10
|
+
"minimumSizePx": 32,
|
|
11
|
+
"clearSpaceViewBoxUnits": 16,
|
|
12
|
+
"construction": "Geometric W and L, open gate frame, check node. Keep the same paths across the WL series."
|
|
13
|
+
},
|
|
14
|
+
"banner": {
|
|
15
|
+
"viewBox": "0 0 920 220",
|
|
16
|
+
"light": "wl-gate-logo.svg",
|
|
17
|
+
"dark": "wl-gate-logo-dark.svg",
|
|
18
|
+
"cornerRadius": 28
|
|
19
|
+
},
|
|
20
|
+
"palette": {
|
|
21
|
+
"navy": "#102E46",
|
|
22
|
+
"navyDeep": "#0B2034",
|
|
23
|
+
"navySurface": "#173B59",
|
|
24
|
+
"mint": "#49D9BA",
|
|
25
|
+
"mintAccessibleOnLight": "#159D82",
|
|
26
|
+
"paper": "#F7FAFC",
|
|
27
|
+
"mist": "#EEF6F8",
|
|
28
|
+
"textLight": "#F2F8FC",
|
|
29
|
+
"textMutedLight": "#537083",
|
|
30
|
+
"textMutedDark": "#9EBDC9"
|
|
31
|
+
},
|
|
32
|
+
"typography": {
|
|
33
|
+
"family": "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif",
|
|
34
|
+
"productSize": 66,
|
|
35
|
+
"productWeight": 750,
|
|
36
|
+
"seriesSize": 14,
|
|
37
|
+
"seriesTracking": 3.8,
|
|
38
|
+
"taglineSize": 20
|
|
39
|
+
},
|
|
40
|
+
"extension": {
|
|
41
|
+
"shared": ["WL monogram paths", "banner geometry", "typographic hierarchy", "navy base"],
|
|
42
|
+
"perProduct": ["product name", "tagline", "accent color"],
|
|
43
|
+
"examples": ["WL Kit", "WL UI", "WL BD"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 920 220" role="img" aria-labelledby="wl-gate-dark-title wl-gate-dark-desc">
|
|
2
|
+
<title id="wl-gate-dark-title">WL Gate — WL Engineering</title>
|
|
3
|
+
<desc id="wl-gate-dark-desc">WL Gate. Automated quality workflow. An open gate frames a geometric WL monogram and a mint check node, on a deep blue background.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="wl-gate-dark-background" x1="0" y1="0" x2="920" y2="220" gradientUnits="userSpaceOnUse">
|
|
6
|
+
<stop stop-color="#0B2034"/>
|
|
7
|
+
<stop offset="1" stop-color="#102D43"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="wl-gate-dark-tile" x1="34" y1="30" x2="194" y2="190" gradientUnits="userSpaceOnUse">
|
|
10
|
+
<stop stop-color="#1B405B"/>
|
|
11
|
+
<stop offset="1" stop-color="#102B42"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
<rect width="920" height="220" rx="28" fill="url(#wl-gate-dark-background)"/>
|
|
15
|
+
<rect x=".75" y=".75" width="918.5" height="218.5" rx="27.25" fill="none" stroke="#9CC9D8" stroke-opacity=".12" stroke-width="1.5"/>
|
|
16
|
+
<rect x="34" y="30" width="160" height="160" rx="36" fill="url(#wl-gate-dark-tile)"/>
|
|
17
|
+
<rect x="34.75" y="30.75" width="158.5" height="158.5" rx="35.25" fill="none" stroke="#91C4D7" stroke-opacity=".16" stroke-width="1.5"/>
|
|
18
|
+
<g transform="translate(50 46)" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
19
|
+
<g stroke="#F2F8FC">
|
|
20
|
+
<path d="M18 106V22h92v19M18 106h60" stroke-width="9"/>
|
|
21
|
+
<path d="m31 50 10 37 13-25 13 25 10-37M90 50v37h18" stroke-width="8"/>
|
|
22
|
+
</g>
|
|
23
|
+
<circle cx="105" cy="105" r="17" stroke="#49D9BA" stroke-width="7"/>
|
|
24
|
+
<path d="m97 105 5 5 11-12" stroke="#49D9BA" stroke-width="6"/>
|
|
25
|
+
</g>
|
|
26
|
+
<g font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif">
|
|
27
|
+
<text x="246" y="63" fill="#88B5C5" font-size="14" font-weight="700" letter-spacing="3.8">WL ENGINEERING</text>
|
|
28
|
+
<text x="242" y="132" fill="#F2F8FC" font-size="66" font-weight="750" letter-spacing="-2.4">WL Gate</text>
|
|
29
|
+
<text x="246" y="174" fill="#9EBDC9" font-size="20" font-weight="450" letter-spacing=".2">Automated quality workflow</text>
|
|
30
|
+
</g>
|
|
31
|
+
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
32
|
+
<path d="M739 110h95" stroke="#33596C" stroke-width="2"/>
|
|
33
|
+
<circle cx="739" cy="110" r="6" fill="#102D43" stroke="#6A96A8" stroke-width="2"/>
|
|
34
|
+
<circle cx="786" cy="110" r="6" fill="#102D43" stroke="#6A96A8" stroke-width="2"/>
|
|
35
|
+
<circle cx="841" cy="110" r="17" fill="#153F45" stroke="#49D9BA" stroke-width="2"/>
|
|
36
|
+
<path d="m834 110 5 5 9-10" stroke="#49D9BA" stroke-width="2.5"/>
|
|
37
|
+
</g>
|
|
38
|
+
</svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 920 220" role="img" aria-labelledby="wl-gate-title wl-gate-desc">
|
|
2
|
+
<title id="wl-gate-title">WL Gate — WL Engineering</title>
|
|
3
|
+
<desc id="wl-gate-desc">WL Gate. Automated quality workflow. An open gate frames a geometric WL monogram and a mint check node.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="wl-gate-background" x1="0" y1="0" x2="920" y2="220" gradientUnits="userSpaceOnUse">
|
|
6
|
+
<stop stop-color="#F7FAFC"/>
|
|
7
|
+
<stop offset="1" stop-color="#EEF6F8"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="wl-gate-tile" x1="34" y1="30" x2="194" y2="190" gradientUnits="userSpaceOnUse">
|
|
10
|
+
<stop stop-color="#173B59"/>
|
|
11
|
+
<stop offset="1" stop-color="#0C233A"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
<rect width="920" height="220" rx="28" fill="url(#wl-gate-background)"/>
|
|
15
|
+
<rect x=".75" y=".75" width="918.5" height="218.5" rx="27.25" fill="none" stroke="#173B59" stroke-opacity=".08" stroke-width="1.5"/>
|
|
16
|
+
<rect x="34" y="30" width="160" height="160" rx="36" fill="url(#wl-gate-tile)"/>
|
|
17
|
+
<g transform="translate(50 46)" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
18
|
+
<g stroke="#F2F8FC">
|
|
19
|
+
<path d="M18 106V22h92v19M18 106h60" stroke-width="9"/>
|
|
20
|
+
<path d="m31 50 10 37 13-25 13 25 10-37M90 50v37h18" stroke-width="8"/>
|
|
21
|
+
</g>
|
|
22
|
+
<circle cx="105" cy="105" r="17" stroke="#49D9BA" stroke-width="7"/>
|
|
23
|
+
<path d="m97 105 5 5 11-12" stroke="#49D9BA" stroke-width="6"/>
|
|
24
|
+
</g>
|
|
25
|
+
<g font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif">
|
|
26
|
+
<text x="246" y="63" fill="#3B6A80" font-size="14" font-weight="700" letter-spacing="3.8">WL ENGINEERING</text>
|
|
27
|
+
<text x="242" y="132" fill="#102E46" font-size="66" font-weight="750" letter-spacing="-2.4">WL Gate</text>
|
|
28
|
+
<text x="246" y="174" fill="#537083" font-size="20" font-weight="450" letter-spacing=".2">Automated quality workflow</text>
|
|
29
|
+
</g>
|
|
30
|
+
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
31
|
+
<path d="M739 110h95" stroke="#B9CFD6" stroke-width="2"/>
|
|
32
|
+
<circle cx="739" cy="110" r="6" fill="#F2F8FA" stroke="#7296A5" stroke-width="2"/>
|
|
33
|
+
<circle cx="786" cy="110" r="6" fill="#F2F8FA" stroke="#7296A5" stroke-width="2"/>
|
|
34
|
+
<circle cx="841" cy="110" r="17" fill="#E0F5EE" stroke="#159D82" stroke-width="2"/>
|
|
35
|
+
<path d="m834 110 5 5 9-10" stroke="#159D82" stroke-width="2.5"/>
|
|
36
|
+
</g>
|
|
37
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none" role="img" aria-labelledby="wl-mark-title wl-mark-desc">
|
|
2
|
+
<title id="wl-mark-title">WL</title>
|
|
3
|
+
<desc id="wl-mark-desc">The WL Engineering monogram: geometric W and L inside an open gate, completed by a check node.</desc>
|
|
4
|
+
<g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
|
5
|
+
<path d="M18 106V22h92v19" stroke-width="9"/>
|
|
6
|
+
<path d="m31 50 10 37 13-25 13 25 10-37M90 50v37h18" stroke-width="8"/>
|
|
7
|
+
<path d="M18 106h60" stroke-width="9"/>
|
|
8
|
+
<circle cx="105" cy="105" r="17" stroke-width="7"/>
|
|
9
|
+
<path d="m97 105 5 5 11-12" stroke-width="6"/>
|
|
10
|
+
</g>
|
|
11
|
+
</svg>
|
package/bin/wl-skills-gate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { mkdirSync, writeFileSync, lstatSync, statSync, realpathSync } from "node:fs";
|
|
4
|
+
import { git } from "../lib/source-state.js";
|
|
4
5
|
import { runGate } from "../lib/run.js";
|
|
5
6
|
import { buildFixPlan, applyFixPlan } from "../lib/fix.js";
|
|
6
7
|
import { verifyArtifact } from "../lib/verify-artifact.js";
|
|
@@ -9,7 +10,7 @@ import { signGateProof, createArtifactManifest, signReviewRecord } from "../lib/
|
|
|
9
10
|
import { validateCommitMessage } from "../lib/commit-message.js";
|
|
10
11
|
|
|
11
12
|
const [command, ...rest] = process.argv.slice(2);
|
|
12
|
-
const FLAGS = new Set(["--confirm", "--push", "--allow-any"]);
|
|
13
|
+
const FLAGS = new Set(["--confirm", "--push", "--allow-any", "--include-digest", "--send", "--watch"]);
|
|
13
14
|
|
|
14
15
|
function options(allowed) {
|
|
15
16
|
const out = { positional: [] };
|
|
@@ -33,6 +34,27 @@ function writeJson(file, value) {
|
|
|
33
34
|
console.log(`[${command}] 已写入 ${file}`);
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
function protectOutput(file, inputs = [], projectRoot) {
|
|
38
|
+
if (!file) return;
|
|
39
|
+
const destination = path.resolve(file); let current = path.parse(destination).root;
|
|
40
|
+
for (const part of destination.slice(current.length).split(path.sep)) {
|
|
41
|
+
current = path.join(current, part); let info;
|
|
42
|
+
try { info = lstatSync(current); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
43
|
+
if (info?.isSymbolicLink()) throw new Error("输出路径不能包含符号链接或断链");
|
|
44
|
+
}
|
|
45
|
+
let outputStat; try { outputStat = statSync(destination); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
46
|
+
for (const input of inputs.filter(Boolean)) {
|
|
47
|
+
if (path.resolve(input) === destination) throw new Error("输出不能覆盖台账/配置/原输入或密钥");
|
|
48
|
+
if (outputStat) { let inputStat; try { inputStat = statSync(input); } catch (error) { if (error.code !== "ENOENT") throw error; }
|
|
49
|
+
if (inputStat && inputStat.dev === outputStat.dev && inputStat.ino === outputStat.ino) throw new Error("输出不能覆盖原输入的硬链接别名"); }
|
|
50
|
+
}
|
|
51
|
+
if (projectRoot) {
|
|
52
|
+
const root = path.resolve(projectRoot), relative = path.relative(root, destination);
|
|
53
|
+
let tracked = []; try { tracked = git(["ls-files", "-z"], root).split("\0"); } catch {}
|
|
54
|
+
if (tracked.includes(relative)) throw new Error("只读预检输出不能覆盖已跟踪项目文件");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
const PROOF_FLAGS = ["--summary", "--sha", "--project", "--env", "--expect-policy-hash", "--expect-rules-hash",
|
|
37
59
|
"--expect-baseline-hash", "--expect-waivers-hash",
|
|
38
60
|
"--expect-pipeline-id", "--expect-job-id",
|
|
@@ -51,6 +73,142 @@ function proofOptions(o) {
|
|
|
51
73
|
try {
|
|
52
74
|
if (command === "run") {
|
|
53
75
|
process.exitCode = (await runGate({ argv: rest })).exitCode;
|
|
76
|
+
} else if (command === "onboard") {
|
|
77
|
+
const { runOnboardCli } = await import("../lib/onboard.js");
|
|
78
|
+
process.exitCode = await runOnboardCli(rest);
|
|
79
|
+
} else if (command === "doctor") {
|
|
80
|
+
const o = options(["--root", "--kind", "--out"]);
|
|
81
|
+
if (o.positional.length) throw new Error("doctor 不接受位置参数");
|
|
82
|
+
const { doctor } = await import("../lib/doctor.js");
|
|
83
|
+
if (o["--out"]) { const root = path.resolve(o["--root"] || "."), canonicalRoot = realpathSync(root), out = path.resolve(o["--out"]); if ([root, canonicalRoot].some((r) => out === r || out.startsWith(r + path.sep))) throw new Error("doctor 只读项目,--out 必须放在项目外证据目录"); }
|
|
84
|
+
const result = doctor({ root: o["--root"], kind: o["--kind"] });
|
|
85
|
+
protectOutput(o["--out"], [], o["--root"] || ".");
|
|
86
|
+
if (o["--out"]) writeJson(o["--out"], result); else process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
87
|
+
process.exitCode = result.status === "gaps" ? 2 : 0;
|
|
88
|
+
} else if (command === "automation") {
|
|
89
|
+
const o = options(["--config", "--send", "--watch"]);
|
|
90
|
+
if (o.positional.length || !o["--config"]) throw new Error("automation 要求中央 --config;--watch 定时运行;--send 实际投递");
|
|
91
|
+
const { automationTick, startAutomation } = await import("../lib/automation.js");
|
|
92
|
+
if (o["--watch"]) { const loop = startAutomation({ configFile: o["--config"], send: !!o["--send"] }); for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => loop.stop()); }
|
|
93
|
+
else { const result = await automationTick({ configFile: o["--config"], send: !!o["--send"] }); process.stdout.write(JSON.stringify(result, null, 2) + "\n"); process.exitCode = result.status === "ok" ? 0 : 2; }
|
|
94
|
+
} else if (command === "control") {
|
|
95
|
+
const operation = rest.shift();
|
|
96
|
+
const o = options(["--config", "--port", "--review-id", "--record", "--evidence-root", "--provenance", "--plan-id", "--config-id", "--plan", "--release-set"]);
|
|
97
|
+
if (o.positional.length || !o["--config"]) throw new Error("control 要求 --config;子命令 serve/register-review/register-deployment");
|
|
98
|
+
const control = await import("../lib/control-service.js"); let result;
|
|
99
|
+
if (operation === "serve") {
|
|
100
|
+
const server = await control.serveControl({ configFile: o["--config"], port: o["--port"] === undefined ? 8787 : Number(o["--port"]) });
|
|
101
|
+
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => server.close());
|
|
102
|
+
result = { status: "listening", host: "127.0.0.1", port: server.address().port };
|
|
103
|
+
} else if (operation === "register-review") result = await control.registerControlReview({ configFile: o["--config"], reviewId: o["--review-id"], recordFile: o["--record"], evidenceRoot: o["--evidence-root"], provenanceFile: o["--provenance"] });
|
|
104
|
+
else if (operation === "register-deployment") { const { registerControlDeployment } = await import("../lib/control-deployment.js"); result = await registerControlDeployment({ config: control.loadControlConfig(o["--config"]), planId: o["--plan-id"], configId: o["--config-id"], planFile: o["--plan"], releaseSetFile: o["--release-set"] }); }
|
|
105
|
+
else throw new Error("control 子命令 serve/register-review/register-deployment");
|
|
106
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
107
|
+
} else if (command === "review-facts") {
|
|
108
|
+
const o = options(["--root", "--summary", "--plan", "--out-dir", "--base-sha"]);
|
|
109
|
+
if (o.positional.length) throw new Error("review-facts 不接受位置参数");
|
|
110
|
+
const { produceReviewFacts } = await import("../lib/review-facts.js");
|
|
111
|
+
process.stdout.write(JSON.stringify(produceReviewFacts({ root: o["--root"] || process.cwd(), summaryFile: o["--summary"] || "quality-reports/quality-summary.json", planFile: o["--plan"], outDir: o["--out-dir"] || "quality-reports/review", baseSha: o["--base-sha"] }), null, 2) + "\n");
|
|
112
|
+
} else if (command === "review-publish") {
|
|
113
|
+
const o = options(["--config"]); if (o.positional.length || !o["--config"]) throw new Error("review-publish 要求独立签发作业 --config");
|
|
114
|
+
const { publishReviewAuthorizations } = await import("../lib/review-publish.js"); const result = await publishReviewAuthorizations({ configFile: o["--config"] });
|
|
115
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n"); process.exitCode = result.status === "ok" ? 0 : 2;
|
|
116
|
+
} else if (command === "review-ci") {
|
|
117
|
+
const o = options(["--config", "--source", "--out"]);
|
|
118
|
+
if (o.positional.length || !o["--config"] || !o["--source"] || !o["--out"]) throw new Error("review-ci 要求 --config --source --out 新证据目录");
|
|
119
|
+
const { createCiReview } = await import("../lib/review-ci.js");
|
|
120
|
+
process.stdout.write(JSON.stringify(await createCiReview({ configFile: o["--config"], sourceFile: o["--source"], outDir: o["--out"] }), null, 2) + "\n");
|
|
121
|
+
} else if (command === "review-collect") {
|
|
122
|
+
const o = options(["--config", "--source", "--input", "--record", "--evidence-root", "--out"]);
|
|
123
|
+
if (o.positional.length) throw new Error("review-collect 不接受位置参数");
|
|
124
|
+
for (const flag of ["--config", "--source", "--input", "--record", "--evidence-root", "--out"]) if (!o[flag]) throw new Error(`review-collect 缺少 ${flag}`);
|
|
125
|
+
const { collectReviewEvidence } = await import("../lib/review-collect.js");
|
|
126
|
+
const result = collectReviewEvidence({ configFile: o["--config"], sourceFile: o["--source"], inputFile: o["--input"], recordFile: o["--record"], evidenceRoot: o["--evidence-root"], outDir: o["--out"] });
|
|
127
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
128
|
+
} else if (command === "release-set") {
|
|
129
|
+
const operation = rest.shift(), o = options(["--input", "--evidence-root", "--expect-input-hash", "--private-key", "--issuer", "--out", "--release-set", "--public-key", "--expect-issuer", "--expect-release-set-hash", "--env"]);
|
|
130
|
+
if (o.positional.length) throw new Error("release-set 不接受位置参数");
|
|
131
|
+
const { createReleaseSet, verifyReleaseSet } = await import("../lib/release-set.js"); let result;
|
|
132
|
+
if (operation === "create") { protectOutput(o["--out"], [o["--input"], o["--private-key"]]); result = createReleaseSet({ inputFile: o["--input"], evidenceRoot: o["--evidence-root"], expectInputHash: o["--expect-input-hash"], privateKeyFile: o["--private-key"], issuer: o["--issuer"], outFile: o["--out"] }); }
|
|
133
|
+
else if (operation === "verify") result = verifyReleaseSet({ releaseSetFile: o["--release-set"], publicKeyFile: o["--public-key"], expectIssuer: o["--expect-issuer"], expectReleaseSetHash: o["--expect-release-set-hash"], env: o["--env"] });
|
|
134
|
+
else throw new Error("release-set 子命令 create/verify");
|
|
135
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
136
|
+
} else if (command === "deployment") {
|
|
137
|
+
const operation = rest.shift(), o = options(["--config", "--release-set", "--expect-release-set-hash", "--state", "--action", "--out", "--out-dir", "--plan", "--authorization", "--private-key", "--send", "--execution", "--executor-public-key", "--project", "--sequence", "--pipeline-id", "--job-id", "--pipeline-url", "--report-url", "--requests-dir", "--plan-id", "--config-id", "--authorization-dir", "--timeout-ms", "--delivery-dir"]);
|
|
138
|
+
if (o.positional.length) throw new Error("deployment 不接受位置参数");
|
|
139
|
+
const d = await import("../lib/deployment.js"), base = { configFile: o["--config"], releaseSetFile: o["--release-set"], expectReleaseSetHash: o["--expect-release-set-hash"], stateFile: o["--state"] }; let result;
|
|
140
|
+
if (operation === "plan") { protectOutput(o["--out"], [o["--config"], o["--release-set"], o["--state"]]); result = d.planDeployment({ ...base, action: o["--action"], outFile: o["--out"] }); }
|
|
141
|
+
else if (operation === "queue") { const { queueDeploymentPlan } = await import("../lib/deployment-queue.js"); result = queueDeploymentPlan({ requestsDir: o["--requests-dir"], planId: o["--plan-id"], configId: o["--config-id"], planFile: o["--plan"], releaseSetFile: o["--release-set"] }); }
|
|
142
|
+
else if (operation === "run-authorized") {
|
|
143
|
+
const { waitForDeploymentAuthorization } = await import("../lib/deployment-queue.js");
|
|
144
|
+
if (!o["--authorization-dir"]) throw new Error("run-authorized 要求中央固定 --authorization-dir");
|
|
145
|
+
// A preview never waits for or consumes a human authorization.
|
|
146
|
+
if (!o["--send"]) result = d.runDeployment({ ...base, planFile: o["--plan"], privateKeyFile: o["--private-key"], outDir: o["--out-dir"], send: false });
|
|
147
|
+
else {
|
|
148
|
+
const selected = await waitForDeploymentAuthorization({ configFile: o["--config"], planFile: o["--plan"], authorizationDir: o["--authorization-dir"], timeoutMs: o["--timeout-ms"] === undefined ? 900000 : Number(o["--timeout-ms"]) });
|
|
149
|
+
result = selected.status === "authorized" ? d.runDeployment({ ...base, planFile: o["--plan"], authorizationFile: selected.authorizationFile, privateKeyFile: o["--private-key"], outDir: o["--out-dir"], send: true }) : selected;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else if (operation === "deliver") { const { queueDeploymentDelivery } = await import("../lib/deployment-queue.js"); result = queueDeploymentDelivery({ deliveryDir: o["--delivery-dir"], configId: o["--config-id"], executionFile: o["--execution"], releaseSetFile: o["--release-set"], expectReleaseSetHash: o["--expect-release-set-hash"] }); }
|
|
153
|
+
else if (operation === "run") result = d.runDeployment({ ...base, planFile: o["--plan"], authorizationFile: o["--authorization"], privateKeyFile: o["--private-key"], outDir: o["--out-dir"], send: !!o["--send"] });
|
|
154
|
+
else if (operation === "lifecycle") result = d.createDeploymentLifecycle({ ...base, executionFile: o["--execution"], executorPublicKeyFile: o["--executor-public-key"], project: o["--project"], sequence: Number(o["--sequence"]), pipelineId: o["--pipeline-id"], jobId: o["--job-id"], pipelineUrl: o["--pipeline-url"], reportUrl: o["--report-url"], outDir: o["--out-dir"] });
|
|
155
|
+
else throw new Error("deployment 子命令 plan/queue/run-authorized/run/deliver/lifecycle");
|
|
156
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n"); if (result.exitCode) process.exitCode = result.exitCode;
|
|
157
|
+
} else if (command === "attest-collected") {
|
|
158
|
+
const o = options(["--config", "--source", "--sha", "--env", "--manifest", "--private-key", "--issuer", "--out", "--expect-policy-hash", "--expect-rules-hash", "--expect-baseline-hash", "--expect-waivers-hash"]);
|
|
159
|
+
if (o.positional.length) throw new Error("attest-collected 不接受位置参数");
|
|
160
|
+
const { signCollectedReleaseProof } = await import("../lib/lifecycle-source.js");
|
|
161
|
+
protectOutput(o["--out"], [o["--config"], o["--source"], o["--private-key"]]);
|
|
162
|
+
const result = signCollectedReleaseProof({ configFile: o["--config"], sourceFile: o["--source"], sha: o["--sha"], env: o["--env"], manifestPath: o["--manifest"], privateKeyFile: o["--private-key"], issuer: o["--issuer"], outFile: o["--out"], expectPolicyHash: o["--expect-policy-hash"], expectRulesHash: o["--expect-rules-hash"], expectBaselineHash: o["--expect-baseline-hash"], expectWaiversHash: o["--expect-waivers-hash"] });
|
|
163
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
164
|
+
} else if (command === "collect-ci") {
|
|
165
|
+
const o = options(["--config", "--request", "--out"]);
|
|
166
|
+
if (o.positional.length || !o["--config"] || !o["--request"] || !o["--out"]) throw new Error("collect-ci 要求 --config --request --out 新证据目录");
|
|
167
|
+
const { collectCiSource } = await import("../lib/ci-source.js");
|
|
168
|
+
const result = await collectCiSource({ configFile: o["--config"], requestFile: o["--request"], outDir: o["--out"] });
|
|
169
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
170
|
+
process.exitCode = result.passed ? 0 : 2;
|
|
171
|
+
} else if (command === "lifecycle-build" || command === "lifecycle-review") {
|
|
172
|
+
const allowed = ["--config", "--source", "--sha", "--env", "--out", "--manifest", "--record", "--evidence-root", "--public-key", "--authorization-public-key", "--expect-authorization-issuer", "--report-url", "--sequence", "--profile", "--expect-review-policy-hash", "--expect-policy-hash", "--expect-rules-hash", "--expect-baseline-hash", "--expect-waivers-hash"];
|
|
173
|
+
const o = options(allowed);
|
|
174
|
+
if (o.positional.length || !o["--config"] || !o["--source"] || !o["--sha"] || !o["--out"]) throw new Error("生命周期构造要求 --config --source --sha --out 新目录");
|
|
175
|
+
const { createBuildLifecycle, createReviewLifecycle } = await import("../lib/lifecycle-source.js");
|
|
176
|
+
const base = { configFile: o["--config"], sourceFile: o["--source"], sha: o["--sha"], env: o["--env"], outDir: o["--out"] };
|
|
177
|
+
const result = command === "lifecycle-build" ? createBuildLifecycle({ ...base, manifestPath: o["--manifest"] }) : createReviewLifecycle({ ...base,
|
|
178
|
+
recordFile: o["--record"], evidenceRoot: o["--evidence-root"], publicKeyFile: o["--public-key"], authorizationPublicKeyFile: o["--authorization-public-key"], expectAuthorizationIssuer: o["--expect-authorization-issuer"],
|
|
179
|
+
reportUrl: o["--report-url"], sequence: Number(o["--sequence"]), profile: o["--profile"], expectReviewPolicyHash: o["--expect-review-policy-hash"], expectPolicyHash: o["--expect-policy-hash"], expectRulesHash: o["--expect-rules-hash"], expectBaselineHash: o["--expect-baseline-hash"], expectWaiversHash: o["--expect-waivers-hash"] });
|
|
180
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
181
|
+
} else if (command === "governance") {
|
|
182
|
+
const operation = rest.shift();
|
|
183
|
+
const allowed = {
|
|
184
|
+
receipt: ["--input", "--private-key", "--issuer", "--out"],
|
|
185
|
+
ingest: ["--config", "--state", "--event", "--summary", "--out"],
|
|
186
|
+
lifecycle: ["--config", "--state", "--event", "--source", "--out"],
|
|
187
|
+
action: ["--config", "--state", "--action", "--out"],
|
|
188
|
+
reconcile: ["--config", "--state", "--expectations", "--out"],
|
|
189
|
+
outbox: ["--config", "--state", "--include-digest", "--out"],
|
|
190
|
+
dispatch: ["--config", "--state", "--include-digest", "--send", "--out"],
|
|
191
|
+
migrate: ["--config", "--new-config", "--state", "--authorization", "--out"],
|
|
192
|
+
};
|
|
193
|
+
if (!Object.hasOwn(allowed, operation)) throw new Error("governance 子命令: receipt/ingest/lifecycle/action/reconcile/outbox/dispatch");
|
|
194
|
+
const o = options(allowed[operation]); if (o.positional.length) throw new Error("governance 不接受多余位置参数");
|
|
195
|
+
const g = await import("../lib/governance.js");
|
|
196
|
+
const required = operation === "receipt" ? ["--input", "--private-key", "--issuer", "--out"] : ["--config", "--state", ...(operation === "ingest" ? ["--event", "--summary"] : operation === "lifecycle" ? ["--event", "--source"] : operation === "action" ? ["--action"] : operation === "reconcile" ? ["--expectations"] : operation === "migrate" ? ["--new-config", "--authorization"] : [])];
|
|
197
|
+
for (const key of required) if (!o[key]) throw new Error(`缺少 ${key}`);
|
|
198
|
+
protectOutput(o["--out"], [o["--state"], o["--config"], o["--input"], o["--event"], o["--summary"], o["--source"], o["--action"], o["--expectations"], o["--private-key"]]);
|
|
199
|
+
if (operation !== "receipt") protectOutput(o["--out"], [g.loadGovernanceConfig(o["--config"]).publicKeyFile]);
|
|
200
|
+
let result;
|
|
201
|
+
const base = { configFile: o["--config"], stateFile: o["--state"], includeDigest: !!o["--include-digest"] };
|
|
202
|
+
if (operation === "receipt") { const { readFileSync } = await import("node:fs"); result = g.signPlatformReceipt(JSON.parse(readFileSync(o["--input"], "utf8")), { privateKeyFile: o["--private-key"], issuer: o["--issuer"] }); }
|
|
203
|
+
else if (operation === "ingest") result = g.ingestGovernance({ ...base, eventFile: o["--event"], summaryFile: o["--summary"] });
|
|
204
|
+
else if (operation === "lifecycle") result = g.ingestLifecycleGovernance({ ...base, eventFile: o["--event"], sourceFile: o["--source"] });
|
|
205
|
+
else if (operation === "action") result = g.actionGovernance({ ...base, actionFile: o["--action"] });
|
|
206
|
+
else if (operation === "reconcile") result = g.reconcileGovernance({ ...base, expectationsFile: o["--expectations"] });
|
|
207
|
+
else if (operation === "outbox") result = g.outboxGovernance(base);
|
|
208
|
+
else if (operation === "migrate") result = g.migrateGovernanceConfig({ ...base, newConfigFile: o["--new-config"], authorizationFile: o["--authorization"] });
|
|
209
|
+
else result = await g.dispatchGovernance({ ...base, send: !!o["--send"] });
|
|
210
|
+
if (o["--out"]) writeJson(o["--out"], result); else process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
211
|
+
if (result.results?.some((x) => x.status !== "delivered") || result.unknownDeliveries?.length || result.deadLetters?.length) process.exitCode = 2;
|
|
54
212
|
} else if (command === "cost") {
|
|
55
213
|
const o = options(["--usage", "--out"]);
|
|
56
214
|
if (!o.positional[0] || !o["--out"]) throw new Error("cost 必须提供工件目录和 --out;不默认将完整统计塞入控制台");
|
|
@@ -93,10 +251,24 @@ try {
|
|
|
93
251
|
writeJson(o["--out"], signGateProof({ ...proofOptions(o), summaryFile: o["--summary"] || o.positional[0],
|
|
94
252
|
profile: o["--profile"], privateKeyFile: o["--private-key"], issuer: o["--issuer"],
|
|
95
253
|
pipelineId: o["--pipeline-id"], jobId: o["--job-id"], promotionRecordFile: o["--promotion-record"] }));
|
|
254
|
+
} else if (command === "review-draft") {
|
|
255
|
+
const o = options(["--input", "--evidence-root", "--out"]);
|
|
256
|
+
if (o.positional.length || !o["--input"] || !o["--out"]) throw new Error("review-draft 要求 --input --out,证据须放在 --evidence-root 内");
|
|
257
|
+
const { createReviewDraft } = await import("../lib/review.js");
|
|
258
|
+
const result = createReviewDraft({ inputFile: o["--input"], evidenceRoot: o["--evidence-root"], outDir: o["--out"] });
|
|
259
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
96
260
|
} else if (command === "review-sign") {
|
|
97
|
-
const o = options(["--private-key", "--reviewer-id", "--author-id", "--project", "--sha", "--out"]);
|
|
98
|
-
|
|
99
|
-
|
|
261
|
+
const o = options(["--private-key", "--reviewer-id", "--author-id", "--project", "--sha", "--out", "--authorization", "--authorization-public-key", "--expect-authorization-issuer", "--evidence-root", "--expect-review-policy-hash", "--profile", "--env", "--expect-policy-hash", "--expect-rules-hash", "--expect-baseline-hash", "--expect-waivers-hash"]);
|
|
262
|
+
const recordFile = o.positional[0];
|
|
263
|
+
protectOutput(o["--out"], [recordFile, o["--private-key"], o["--authorization"]]);
|
|
264
|
+
const { validateReviewOutputLocation } = await import("../lib/review.js");
|
|
265
|
+
const destination = validateReviewOutputLocation({ outFile: o["--out"], recordFile, evidenceRoot: o["--evidence-root"] });
|
|
266
|
+
const signedReview = signReviewRecord({ recordFile, privateKeyFile: o["--private-key"],
|
|
267
|
+
reviewerId: o["--reviewer-id"], authorId: o["--author-id"], project: o["--project"], sha: o["--sha"],
|
|
268
|
+
authorizationFile: o["--authorization"], authorizationPublicKeyFile: o["--authorization-public-key"], expectAuthorizationIssuer: o["--expect-authorization-issuer"], evidenceRoot: o["--evidence-root"], expectReviewPolicyHash: o["--expect-review-policy-hash"],
|
|
269
|
+
profile: o["--profile"], env: o["--env"], expectPolicyHash: o["--expect-policy-hash"], expectRulesHash: o["--expect-rules-hash"], expectBaselineHash: o["--expect-baseline-hash"], expectWaiversHash: o["--expect-waivers-hash"] });
|
|
270
|
+
writeFileSync(destination, JSON.stringify(signedReview, null, 2) + "\n", { flag: "wx", mode: 0o600 });
|
|
271
|
+
console.log(`[review-sign] 已写入 ${destination}`);
|
|
100
272
|
} else if (command === "verify-artifact") {
|
|
101
273
|
const r = verifyArtifact(proofOptions(options(PROOF_FLAGS)));
|
|
102
274
|
if (!r.ok) throw new Error(r.errors.join("; "));
|
|
@@ -111,9 +283,9 @@ try {
|
|
|
111
283
|
for (const reason of r.reasons) console.error(reason);
|
|
112
284
|
process.exitCode = r.exitCode;
|
|
113
285
|
} else if (command === "promote-apply") {
|
|
114
|
-
const o = options([...PROOF_FLAGS, "--record", "--remote-url", "--bundle", "--review", "--review-public-key", "--out"]);
|
|
286
|
+
const o = options([...PROOF_FLAGS, "--record", "--remote-url", "--bundle", "--review", "--review-public-key", "--review-evidence-root", "--expect-review-policy-hash", "--authorization-public-key", "--expect-authorization-issuer", "--out"]);
|
|
115
287
|
const r = applyPromotion({ ...proofOptions(o), recordFile: o["--record"], remoteUrl: o["--remote-url"],
|
|
116
|
-
bundleFile: o["--bundle"], reviewFile: o["--review"], reviewPublicKeyFile: o["--review-public-key"], outDir: o["--out"] });
|
|
288
|
+
bundleFile: o["--bundle"], reviewFile: o["--review"], reviewPublicKeyFile: o["--review-public-key"], reviewEvidenceRoot: o["--review-evidence-root"], expectReviewPolicyHash: o["--expect-review-policy-hash"], authorizationPublicKeyFile: o["--authorization-public-key"], expectAuthorizationIssuer: o["--expect-authorization-issuer"], outDir: o["--out"] });
|
|
117
289
|
console.log(`[promote-apply] ${r.status}`);
|
|
118
290
|
for (const reason of r.reasons) console.error(reason);
|
|
119
291
|
process.exitCode = r.exitCode;
|
|
@@ -121,5 +293,5 @@ try {
|
|
|
121
293
|
if (rest.length) throw new Error("self-test 不接受额外参数");
|
|
122
294
|
const { runAllFixtures } = await import("../scripts/run-fixtures.mjs");
|
|
123
295
|
process.exitCode = await runAllFixtures();
|
|
124
|
-
} else throw new Error("用法: wl-skills-gate <run|inspect|cost|fix|validate-report|commit-message|manifest|attest|review-sign|verify-artifact|promote|promote-apply|self-test>,详见 docs/README.md");
|
|
296
|
+
} else throw new Error("用法: wl-skills-gate <onboard|run|doctor|automation|control|collect-ci|review-facts|review-ci|review-collect|review-publish|lifecycle-build|lifecycle-review|governance|inspect|cost|fix|validate-report|commit-message|manifest|attest|attest-collected|release-set|deployment|review-draft|review-sign|verify-artifact|promote|promote-apply|self-test>,详见 docs/README.md");
|
|
125
297
|
} catch (e) { console.error(`[${command || "wl-skills-gate"}] ${e.message}`); process.exitCode = 2; }
|