@agile-team/wl-skills-ui 1.14.0 → 1.16.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 +23 -3
- package/SKILL.md +2 -0
- package/bin/__tests__/protocol-cli.test.mjs +122 -0
- package/bin/__tests__/protocol-evidence.test.mjs +50 -0
- package/bin/__tests__/protocol-routing-cases.json +105 -0
- package/bin/__tests__/protocol-routing.test.mjs +63 -0
- package/bin/capabilities.json +31 -1
- package/bin/integration-protocol.cjs +283 -0
- package/bin/protocol-cli.mjs +134 -0
- package/bin/task-gateway.mjs +20 -0
- package/bin/task-integration.mjs +156 -0
- package/bin/task-observability.cjs +528 -0
- package/bin/task-observability.schema.json +77 -0
- package/bin/wl-ui.js +24 -2
- package/docs/task-observability.md +25 -0
- package/docs/version-history.md +23 -0
- package/mcp/README.md +7 -1
- package/mcp/server.js +45 -2
- package/package.json +4 -4
- package/runtime/vite/check.mjs +17 -3
- package/scanner/engine.mjs +24 -8
- package/scanner/index.mjs +31 -1
- package/scanner/report.mjs +6 -2
- package/scanner/sfc-parser.mjs +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.16.0] - 2026-10-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- 新增公开集成协议 describe/request 统一信封:能力清单与路由语料经真实打包产物验证,供外部宿主与适配器按公开契约接入。
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- 输入校验补齐:context 为 null、targets 为空等边界输入返回可解释拒绝,不再产生歧义结果。
|
|
18
|
+
- MCP 目录对齐真实 `tools/list` 的 18 项,修复目录声明与实际暴露不一致。
|
|
19
|
+
|
|
20
|
+
## [1.15.0] - 2026-10-07
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- 新增 task/route/explain/status/doctor-host CLI 与 5 个 MCP 工具,任务计划与真实执行回执分开;支持跨已安装适用包复用 runId,无兄弟包运行依赖。
|
|
25
|
+
- 新增本包独有 Codex `.agents/skills/wl-skills-ui/SKILL.md` 薄入口,按需读取 canonical Skill;缺资产或未发布显式技能报告 gap,静态入口诊断不冒充宿主实际发现。
|
|
26
|
+
- scan/check/fix/all 与 MCP、Vite 依赖检查记录执行/验证状态、实际检查文件、规则回调次数、规则/配置快照与过期状态。未执行、零文件、跳过或人工复核不会假绿。
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- 实际 Vue SFC 解析错误形成不可被 only/skip 隐藏的 SFC_PARSE 诊断;fast/fallback 不声称语法验证,脚本类型编译仍需项目独立 typecheck。
|
|
31
|
+
- 不适用/缺上下文任务不附加全量 UI 约束;Markdown 无问题文件占比不再称为规范覆盖率,零文件明确未检查。Vite 插件成功也明确仅验证依赖组合、源码规则未运行。
|
|
32
|
+
|
|
9
33
|
## [1.14.0] - 2026-10-07
|
|
10
34
|
|
|
11
35
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @agile-team/wl-skills-ui
|
|
2
2
|
|
|
3
|
-
**企业级 UI 一致性与 AI 精确治理工具包 v1.
|
|
3
|
+
**企业级 UI 一致性与 AI 精确治理工具包 v1.16.0** — 面向 Vue 3 + Element Plus 的设计令牌、能力 Profile、样式皮肤、页面骨架、运行时能力、静态扫描、确定性修复、AI Skill、MCP 与脱敏 UI 模板契约。
|
|
4
4
|
|
|
5
5
|
它把“视觉统一、问题识别、机械修复、项目适配和样例沉淀”拆成可验证、可组合的工程能力。脚本、规则和 MCP 先完成确定性工作,AI 只处理业务语义与人工决策,从而减少重复读源码、上下文 token 和误修改。
|
|
6
6
|
|
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
| 低成本治理老项目 | Skin 模式、Base*/jh-*/C_*/AG Grid 兼容层、定制页豁免 | 不改封装源码也能统一大部分视觉,避免一次性重构风险 |
|
|
16
16
|
| 阻止 UI 回归 | 40 条 R-rule、接入检查、漂移基线、Git 增量扫描、CI 门禁 | 表头/内容不共轴、按钮无图标、日期弹层全屏、密集表单失衡等问题在提交前暴露 |
|
|
17
17
|
| 安全机械修复 | Profile、only/skip、dry-run 计划哈希、快照、复检与回滚 | 自动化只处理可证明安全且已批准的动作,不让 AI 猜测批改 |
|
|
18
|
-
| 降低 AI token | summary、compact-v2 分页、changed-only、Skill 路由、
|
|
18
|
+
| 降低 AI token | summary、compact-v2 分页、changed-only、Skill 路由、18 个 MCP Tool | AI 先读总览和结构化事实,只在必要时获取有界明细或局部源码 |
|
|
19
19
|
| 沉淀领域样例 | `ui-contract` extract / validate / match | 页面转为脱敏语义 JSON,不复制整页代码、接口、字段值或原始文案 |
|
|
20
20
|
| 兼容多代项目 | EP 2.2+jh-ui 与原生 EP 2.7+ DOM 契约、Vite/doctor 检查 | 支持声明有 fixture 和真实浏览器证据,不靠文档口头约定 |
|
|
21
21
|
| 降低运行时开销 | Shared Observer Hub、可选 guard、幂等安装与完整卸载 | 全部 guard 共用 1 个 MutationObserver + 1 个 ResizeObserver |
|
|
22
22
|
|
|
23
23
|
适合多项目 Vue 3 企业后台、存量系统视觉治理、AI 辅助开发和领域页面模式沉淀。运行时最低要求:Node.js 22、Vue 3.2、Element Plus 2.2。
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
每次任务先运行 `wl-ui task "任务" --target <path>`,按选定技能执行工具,最后用 `wl-ui status --run-id <id>` 查看实际执行与验证状态;安装或匹配不会自动变成检查通过。详见[任务判定与真实检查回执](docs/task-observability.md)。
|
|
27
|
+
|
|
25
28
|
## 快速开始
|
|
26
29
|
|
|
27
30
|
### 1. 安装
|
|
@@ -91,6 +94,23 @@ CLI、安装器和 MCP 无需安装其他 wl-skills 包或统一客户端即可
|
|
|
91
94
|
|
|
92
95
|
`init` 新插入的 HTML tokens link 使用独立边界标记并登记贡献,`clean` 只移除未修改的本包链接,保留项目其他 HTML。历史未标记的链接缺少归属证据,升级时仍作为项目配置保留;SCSS 与 runtime 接入只给出建议,不自动修改或认领项目已有 import。
|
|
93
96
|
|
|
97
|
+
## 公开集成协议 v1
|
|
98
|
+
|
|
99
|
+
供 harness、客户端或其他编排层以统一 JSON 入口调用本包判定与状态能力(业务执行仍走本包 CLI/MCP,不因协议新增兄弟包依赖)。
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
wl-ui protocol describe --json # 能力目录、五操作映射、请求/响应 Schema、错误码
|
|
103
|
+
wl-ui protocol request --input-file request.json --json
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- 操作:`route` / `explain`(只读判定)、`task`(判定并持久化计划,尚未执行)、`status`(回查本包执行回执)、`doctor-host`(宿主入口静态诊断)。
|
|
107
|
+
- 请求字段:`protocolVersion`(1)、`operation`、`requestId`、`runId`、`projectRoot`、`task`、`targets`、`skill`、`host`+ `profile`。
|
|
108
|
+
- 统一信封:`{ ok, result | error, diagnostics }`;机器结果与诊断分离;非法输入在触达执行器前判 `invalid-input`,不写入任何记录。
|
|
109
|
+
- 错误码:`unsupported-protocol` / `unknown-operation` / `missing-input` / `invalid-input` / `internal-error`;请求失败(exit 2)时 stdout 仍为可解析 JSON 信封。
|
|
110
|
+
- **`ok=true` 只表示协议调用成功,不代表业务验证通过**;业务状态以 `result` 内 `validationStatus` / `executionStatus` 为准(仅计划时为 `unverified` / `not-executed`)。
|
|
111
|
+
- 副作用:仅 `task` 写入本包自己的 runs 目录(见 describe 中各操作 `sideEffects`);其余操作只读。
|
|
112
|
+
- 协议实现为本包内置的同源快照 `integration-protocol.cjs`,五包一致;跨包一致性由 conformance `integration-protocol.check.cjs` 验收。
|
|
113
|
+
|
|
94
114
|
## 能力地图
|
|
95
115
|
|
|
96
116
|
| 层级 | 能力 | 典型内容 | 是否需要业务源码 |
|
|
@@ -480,7 +500,7 @@ npx wl-ui doctor --project . --print-overrides
|
|
|
480
500
|
|
|
481
501
|
## 版本与进一步阅读
|
|
482
502
|
|
|
483
|
-
当前 v1.14.0:明确独立能力边界,安装、更新、清理按本包贡献保护用户及其他包内容;共享文件采用单文件原子替换,支持历史路由与 `.clinerules` 目录迁移。v1.13.2:R044 审查多列表单超宽标签,R040 识别自定义 `stepper`,均供人工复核。v1.13.0:新增 `native-jh-ag` Profile、显式 Profile 兼容承诺与联邦 AG 依赖识别。
|
|
503
|
+
当前 v1.16.0:公开集成协议 describe/request 统一信封(能力清单与路由语料经真实打包产物验证)、输入校验收口与 MCP 目录对齐真实 tools/list。v1.15.0:任务判定、真实执行/验证回执、实际检查范围和过期检测;Codex 原生薄入口按需加载 canonical Skill。确定性路由覆盖已发布目录与显式缺口,不保证识别所有自然语言。v1.14.0:明确独立能力边界,安装、更新、清理按本包贡献保护用户及其他包内容;共享文件采用单文件原子替换,支持历史路由与 `.clinerules` 目录迁移。v1.13.2:R044 审查多列表单超宽标签,R040 识别自定义 `stepper`,均供人工复核。v1.13.0:新增 `native-jh-ag` Profile、显式 Profile 兼容承诺与联邦 AG 依赖识别。
|
|
484
504
|
|
|
485
505
|
- [版本摘要](docs/version-history.md)
|
|
486
506
|
- [完整 Changelog](CHANGELOG.md)
|
package/SKILL.md
CHANGED
|
@@ -14,6 +14,8 @@ do not reconstruct them from memory or duplicate their wording.
|
|
|
14
14
|
|
|
15
15
|
## Start here
|
|
16
16
|
|
|
17
|
+
Start every task with `npx wl-ui task "<task>" --target <path>`. Read the selected canonical Skill and required rules only after resolving ambiguous/gap/needs-context or missing own assets. A match is a plan, not evidence of execution. Use the returned `--run-id` (or `WL_TASK_RUN_ID`) for real CLI/MCP tools and finish with `npx wl-ui status --run-id <id>`. Reuse the same ID across installed applicable packages without installing unused sibling packages. Report execution and validation separately, including skipped checks and stale evidence; host discovery/reading remain unverified without host events.
|
|
18
|
+
|
|
17
19
|
```bash
|
|
18
20
|
# Small summary first; does not modify files
|
|
19
21
|
npx wl-ui scan --project . --target src --output summary --parser auto
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* protocol-cli.test.mjs — ui 公开集成协议统一信封与接线测试
|
|
3
|
+
*/
|
|
4
|
+
import { test } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const { protocol, runOperation } = await import("../protocol-cli.mjs");
|
|
16
|
+
const pkg = require("../../package.json");
|
|
17
|
+
const BIN = path.join(here, "..", "wl-ui.js");
|
|
18
|
+
|
|
19
|
+
function tempRoot() {
|
|
20
|
+
return mkdtempSync(path.join(tmpdir(), "wl-ui-protocol-"));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test("describe 返回协议版本、能力目录与五个统一操作", () => {
|
|
24
|
+
const described = protocol.describe();
|
|
25
|
+
assert.equal(described.protocolVersion, 1);
|
|
26
|
+
assert.equal(described.package, pkg.name);
|
|
27
|
+
assert.ok(Array.isArray(described.capabilities) && described.capabilities.length > 0);
|
|
28
|
+
assert.deepEqual(described.operations.map((operation) => operation.id), ["route", "explain", "task", "status", "doctor-host"]);
|
|
29
|
+
assert.ok(described.errorCodes.includes("unsupported-protocol"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("route 只读判定返回六类状态之一", () => {
|
|
33
|
+
const envelope = protocol.request({ operation: "route", projectRoot: tempRoot(), task: "统一这个页面的表格视觉" }, runOperation);
|
|
34
|
+
assert.equal(envelope.ok, true);
|
|
35
|
+
const status = envelope.result.decision ? envelope.result.decision.status : envelope.result.status;
|
|
36
|
+
assert.ok(["matched", "baseline", "ambiguous", "gap", "not-applicable", "needs-context"].includes(status));
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("task 持久化返回 runId 且 status 可回查", () => {
|
|
40
|
+
const root = tempRoot();
|
|
41
|
+
const planned = protocol.request({ operation: "task", projectRoot: root, task: "扫描 src/views 视觉问题" }, runOperation);
|
|
42
|
+
assert.equal(planned.ok, true);
|
|
43
|
+
assert.ok(planned.result.runId);
|
|
44
|
+
const status = protocol.request({ operation: "status", projectRoot: root, runId: planned.result.runId }, runOperation);
|
|
45
|
+
assert.equal(status.ok, true);
|
|
46
|
+
assert.equal(status.result.runId, planned.result.runId);
|
|
47
|
+
assert.equal(status.result.executionStatus, "not-executed");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("doctor-host 按指定 host 诊断", () => {
|
|
51
|
+
const envelope = protocol.request({ operation: "doctor-host", projectRoot: tempRoot(), host: "claude" }, runOperation);
|
|
52
|
+
assert.equal(envelope.ok, true);
|
|
53
|
+
assert.equal(envelope.result.host, "claude");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("协议错误码:missing-input / unknown-operation / invalid-input / unsupported-protocol", () => {
|
|
57
|
+
assert.equal(protocol.request({ operation: "route" }, runOperation).error.code, "missing-input");
|
|
58
|
+
assert.equal(protocol.request({ operation: "skin" }, runOperation).error.code, "unknown-operation");
|
|
59
|
+
assert.equal(protocol.request(42, runOperation).error.code, "invalid-input");
|
|
60
|
+
assert.equal(protocol.request({ protocolVersion: 3, operation: "route", task: "x" }, runOperation).error.code, "unsupported-protocol");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("边界输入校验:非法类型在触达执行器前判 invalid-input(独立复验缺陷回归)", () => {
|
|
64
|
+
const invalidPayloads = [
|
|
65
|
+
{ operation: "task", task: true },
|
|
66
|
+
{ operation: "task", task: { text: "bad" } },
|
|
67
|
+
{ operation: "task", task: "检查目标", targets: [null, 42, {}] },
|
|
68
|
+
{ operation: "route", task: "检查目标", projectRoot: 42 },
|
|
69
|
+
{ operation: "task", task: "检查目标", runId: {} },
|
|
70
|
+
];
|
|
71
|
+
for (const payload of invalidPayloads) {
|
|
72
|
+
const envelope = protocol.request(payload, () => { throw new Error("不应触达执行器"); });
|
|
73
|
+
assert.equal(envelope.ok, false);
|
|
74
|
+
assert.equal(envelope.error.code, "invalid-input");
|
|
75
|
+
assert.ok(envelope.error.field);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("CLI 缺 --input-file 时 stdout 输出 missing-input JSON 信封", () => {
|
|
80
|
+
const run = spawnSync(process.execPath, [BIN, "protocol", "request"], { encoding: "utf8" });
|
|
81
|
+
assert.equal(run.status, 2);
|
|
82
|
+
const envelope = JSON.parse(run.stdout);
|
|
83
|
+
assert.equal(envelope.ok, false);
|
|
84
|
+
assert.equal(envelope.error.code, "missing-input");
|
|
85
|
+
assert.equal(envelope.error.field, "input-file");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("CLI protocol describe / request 全链路", () => {
|
|
89
|
+
const describe = spawnSync(process.execPath, [BIN, "protocol", "describe"], { encoding: "utf8" });
|
|
90
|
+
assert.equal(describe.status, 0);
|
|
91
|
+
assert.equal(JSON.parse(describe.stdout).package, "@agile-team/wl-skills-ui");
|
|
92
|
+
const root = tempRoot();
|
|
93
|
+
const file = path.join(root, "req.json");
|
|
94
|
+
const { writeFileSync } = require("node:fs");
|
|
95
|
+
writeFileSync(file, JSON.stringify({ operation: "doctor-host", projectRoot: root, host: "copilot" }));
|
|
96
|
+
const request = spawnSync(process.execPath, [BIN, "protocol", "request", "--input-file", file], { encoding: "utf8" });
|
|
97
|
+
assert.equal(request.status, 0);
|
|
98
|
+
assert.equal(JSON.parse(request.stdout).result.host, "copilot");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
test("context null 判 invalid-input 且零写入(CLI 回归)", () => {
|
|
103
|
+
const root = tempRoot();
|
|
104
|
+
const file = path.join(root, "req.json");
|
|
105
|
+
const { writeFileSync, readdirSync } = require("node:fs");
|
|
106
|
+
writeFileSync(file, JSON.stringify({ operation: "task", projectRoot: root, task: "扫描 src/views 视觉问题", runId: "ctx-null", context: null }));
|
|
107
|
+
const run = spawnSync(process.execPath, [BIN, "protocol", "request", "--input-file", file], { encoding: "utf8" });
|
|
108
|
+
assert.equal(run.status, 2);
|
|
109
|
+
const envelope = JSON.parse(run.stdout);
|
|
110
|
+
assert.equal(envelope.ok, false);
|
|
111
|
+
assert.equal(envelope.error.code, "invalid-input");
|
|
112
|
+
assert.equal(envelope.error.field, "context");
|
|
113
|
+
assert.equal(readdirSync(root).filter((name) => name.startsWith(".")).length, 0, "不得写入任何任务记录");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("targets 空数组与 Schema 一致(运行时接受空范围)", () => {
|
|
117
|
+
const root = tempRoot();
|
|
118
|
+
const envelope = protocol.request({ operation: "route", projectRoot: root, task: "扫描 src/views 视觉问题", targets: [] }, runOperation);
|
|
119
|
+
assert.equal(envelope.ok, true);
|
|
120
|
+
const described = protocol.describe();
|
|
121
|
+
assert.equal(described.schemas.request.properties.targets.minItems, undefined, "Schema 不得要求非空 targets");
|
|
122
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* protocol-evidence.test.mjs — ui 证据闭环严格断言(不得兜底通过)
|
|
3
|
+
*/
|
|
4
|
+
import { test } from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { mkdtempSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const { protocol, runOperation } = await import("../protocol-cli.mjs");
|
|
11
|
+
|
|
12
|
+
function tempRoot() {
|
|
13
|
+
return mkdtempSync(path.join(tmpdir(), "wl-ui-evidence-"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test("status 严格回查同一 runId:字段精确、不兜底", () => {
|
|
17
|
+
const root = tempRoot();
|
|
18
|
+
const planned = protocol.request({ operation: "task", projectRoot: root, task: "统一表格视觉", runId: "evidence-run-a" }, runOperation);
|
|
19
|
+
assert.equal(planned.ok, true);
|
|
20
|
+
assert.equal(planned.result.runId, "evidence-run-a");
|
|
21
|
+
assert.equal(planned.result.executionStatus, "not-executed");
|
|
22
|
+
assert.equal(planned.result.validationStatus, "unverified");
|
|
23
|
+
const status = protocol.request({ operation: "status", projectRoot: root, runId: "evidence-run-a" }, runOperation);
|
|
24
|
+
assert.equal(status.result.runId, "evidence-run-a");
|
|
25
|
+
assert.equal(status.result.executionStatus, "not-executed");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("混用 runId 不得串记录", () => {
|
|
29
|
+
const root = tempRoot();
|
|
30
|
+
protocol.request({ operation: "task", projectRoot: root, task: "任务A统一表格视觉", runId: "evidence-run-a" }, runOperation);
|
|
31
|
+
protocol.request({ operation: "task", projectRoot: root, task: "任务B统一表格视觉", runId: "evidence-run-b" }, runOperation);
|
|
32
|
+
const statusB = protocol.request({ operation: "status", projectRoot: root, runId: "evidence-run-b" }, runOperation);
|
|
33
|
+
assert.equal(statusB.result.runId, "evidence-run-b");
|
|
34
|
+
assert.equal(JSON.stringify(statusB.result).includes("任务A"), false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("不存在的 runId 不得伪造成功记录", () => {
|
|
38
|
+
const root = tempRoot();
|
|
39
|
+
const status = protocol.request({ operation: "status", projectRoot: root, runId: "no-such-run" }, runOperation);
|
|
40
|
+
const serialized = JSON.stringify(status.result);
|
|
41
|
+
assert.equal(serialized.includes('"validationStatus":"passed"'), false);
|
|
42
|
+
assert.equal(serialized.includes('"executionStatus":"completed"'), false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("空检查集不得判定为通过", () => {
|
|
46
|
+
const root = tempRoot();
|
|
47
|
+
const planned = protocol.request({ operation: "task", projectRoot: root, task: "统一表格视觉", runId: "empty-run" }, runOperation);
|
|
48
|
+
assert.notEqual(planned.result.validationStatus, "passed");
|
|
49
|
+
assert.equal(planned.result.executionStatus, "not-executed");
|
|
50
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"task": "统一表格视觉",
|
|
4
|
+
"skill": "element/el-table"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"task": "查询表单密集审计",
|
|
8
|
+
"skill": "element/el-form"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"task": "弹窗样式对齐",
|
|
12
|
+
"skill": "element/el-dialog"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"task": "状态标签颜色统一",
|
|
16
|
+
"skill": "element/el-tag"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"task": "列表布局骨架",
|
|
20
|
+
"skill": "layouts/list-page"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"task": "AG Grid 适配",
|
|
24
|
+
"skill": "vendors/ag-grid"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"task": "UI扫描",
|
|
28
|
+
"skill": "ops/scan"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"task": "UI修复统一视觉",
|
|
32
|
+
"skill": "ops/fix"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"task": "UI迁移迁移tokens",
|
|
36
|
+
"skill": "ops/migrate"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"task": "设计令牌接入消除颜色硬编码",
|
|
40
|
+
"skill": "runtime/design-tokens"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"task": "写诗",
|
|
44
|
+
"skill": null,
|
|
45
|
+
"status": "not-applicable"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"task": "生成测试用例",
|
|
49
|
+
"skill": null,
|
|
50
|
+
"status": "needs-context"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"task": "检查",
|
|
54
|
+
"skill": null,
|
|
55
|
+
"status": "needs-context"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"task": "整理页签和卡片组件",
|
|
59
|
+
"skill": "element/component-family"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"task": "详情布局整改",
|
|
63
|
+
"skill": "layouts/detail-page"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"task": "调整表单弹窗布局",
|
|
67
|
+
"skill": "layouts/form-dialog"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"task": "树列表布局",
|
|
71
|
+
"skill": "layouts/tree-list"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"task": "做一次UI审计",
|
|
75
|
+
"skill": "ops/audit"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"task": "runtime迁移",
|
|
79
|
+
"skill": "runtime/migration"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"task": "样式统一风格对齐",
|
|
83
|
+
"skill": "runtime/style-align"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"task": "BaseTable 适配",
|
|
87
|
+
"skill": "vendors/base-table"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"task": "C组件用法审计",
|
|
91
|
+
"skill": "vendors/c-components"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"task": "自定义封装组件治理",
|
|
95
|
+
"skill": "vendors/custom-wrappers"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"task": "jh组件接入",
|
|
99
|
+
"skill": "vendors/jh-components"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"task": "未知封装组件排查",
|
|
103
|
+
"skill": "vendors/unknown-wrapper"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* protocol-routing.test.mjs — ui 路由准确性逐技能语料(预期经人工复核冻结)
|
|
3
|
+
* ui 是依赖型包:安装态通过真实打包产物 npm 安装到隔离项目验证。
|
|
4
|
+
*/
|
|
5
|
+
import { test, before } from "node:test";
|
|
6
|
+
import assert from "node:assert/strict";
|
|
7
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const { protocol, runOperation } = await import("../protocol-cli.mjs");
|
|
15
|
+
const cases = JSON.parse((await import("node:fs")).readFileSync(path.join(here, "protocol-routing-cases.json"), "utf8"));
|
|
16
|
+
const PKG_ROOT = path.join(here, "..", "..");
|
|
17
|
+
|
|
18
|
+
const state = { installedRoot: null, tarball: null };
|
|
19
|
+
|
|
20
|
+
function tempRoot() {
|
|
21
|
+
return mkdtempSync(path.join(tmpdir(), "wl-ui-routing-"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function routeAt(projectRoot, task) {
|
|
25
|
+
return protocol.request({ operation: "route", projectRoot, task }, runOperation);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
before(() => {
|
|
29
|
+
const packDir = tempRoot();
|
|
30
|
+
const pack = spawnSync("npm", ["pack", "--pack-destination", packDir, "--ignore-scripts"], { cwd: PKG_ROOT, encoding: "utf8", timeout: 180000 });
|
|
31
|
+
assert.equal(pack.status, 0, pack.stderr);
|
|
32
|
+
state.tarball = path.join(packDir, pack.stdout.trim().split("\n").pop());
|
|
33
|
+
state.installedRoot = tempRoot();
|
|
34
|
+
assert.equal(spawnSync("npm", ["init", "-y"], { cwd: state.installedRoot, encoding: "utf8" }).status, 0);
|
|
35
|
+
const install = spawnSync("npm", ["i", state.tarball], { cwd: state.installedRoot, encoding: "utf8", timeout: 180000 });
|
|
36
|
+
assert.equal(install.status, 0, install.stderr);
|
|
37
|
+
}, 600000);
|
|
38
|
+
|
|
39
|
+
for (const item of cases) {
|
|
40
|
+
const expectedBare = item.skill ? "gap" : item.status;
|
|
41
|
+
const expectedInstalled = item.installedStatus || (item.skill ? "matched" : item.status);
|
|
42
|
+
|
|
43
|
+
test(`未安装:「${item.task}」→ ${expectedBare}${item.skill ? ` + ${item.skill}` : ""}`, () => {
|
|
44
|
+
const envelope = routeAt(tempRoot(), item.task);
|
|
45
|
+
assert.equal(envelope.ok, true);
|
|
46
|
+
const decision = envelope.result.decision || envelope.result;
|
|
47
|
+
assert.equal(decision.status, expectedBare);
|
|
48
|
+
if (item.skill) assert.ok(decision.selectedSkills.includes(item.skill), `应选中 ${item.skill},实际 ${decision.selectedSkills}`);
|
|
49
|
+
else assert.equal((decision.selectedSkills || []).length, 0);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test(`已安装(真实打包产物):「${item.task}」→ ${expectedInstalled}${item.skill ? ` + ${item.skill}` : ""}`, () => {
|
|
53
|
+
const envelope = routeAt(state.installedRoot, item.task);
|
|
54
|
+
assert.equal(envelope.ok, true);
|
|
55
|
+
const decision = envelope.result.decision || envelope.result;
|
|
56
|
+
assert.equal(decision.status, expectedInstalled);
|
|
57
|
+
if (item.skill) assert.ok(decision.selectedSkills.includes(item.skill));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test.after(() => {
|
|
62
|
+
if (state.tarball) rmSync(path.dirname(state.tarball), { recursive: true, force: true });
|
|
63
|
+
});
|
package/bin/capabilities.json
CHANGED
|
@@ -20,6 +20,27 @@
|
|
|
20
20
|
"runtime-renderers",
|
|
21
21
|
"visual-findings"
|
|
22
22
|
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "ui.task-observability",
|
|
26
|
+
"intents": [
|
|
27
|
+
"route",
|
|
28
|
+
"explain",
|
|
29
|
+
"record-execution",
|
|
30
|
+
"status",
|
|
31
|
+
"diagnose-host"
|
|
32
|
+
],
|
|
33
|
+
"inputs": [
|
|
34
|
+
"task-description",
|
|
35
|
+
"target-paths",
|
|
36
|
+
"actual-tool-results"
|
|
37
|
+
],
|
|
38
|
+
"outputs": [
|
|
39
|
+
"task-decision",
|
|
40
|
+
"execution-receipts",
|
|
41
|
+
"gap-proposals",
|
|
42
|
+
"host-entry-diagnostics"
|
|
43
|
+
]
|
|
23
44
|
}
|
|
24
45
|
],
|
|
25
46
|
"standalone": {
|
|
@@ -38,6 +59,15 @@
|
|
|
38
59
|
"optionalTraceInput": "design-model",
|
|
39
60
|
"unknownProtocol": "reject-or-explicit-migration",
|
|
40
61
|
"apiAuthority": "validated-effective-api-manifest",
|
|
41
|
-
"sharedWrites": "own-block-or-json-entry-only"
|
|
62
|
+
"sharedWrites": "own-block-or-json-entry-only",
|
|
63
|
+
"observability": {
|
|
64
|
+
"recordVersion": 1,
|
|
65
|
+
"schema": "bin/task-observability.schema.json",
|
|
66
|
+
"storage": ".wl-skills-ui/runs",
|
|
67
|
+
"runCorrelation": "explicit-runId-or-WL_TASK_RUN_ID",
|
|
68
|
+
"eventPublication": "immutable-per-package-events",
|
|
69
|
+
"hostDiscovery": "unverified-without-host-events",
|
|
70
|
+
"gapPolicy": "propose-review-before-changing-business-rules"
|
|
71
|
+
}
|
|
42
72
|
}
|
|
43
73
|
}
|