@agile-team/wl-skills-ui 1.20.0 → 1.20.1

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.20.1] - 2026-10-11
4
+
5
+ - 修复 task 在版本漂移后旧顶层 status/ready 与嵌套 decision、notice、公开协议准入不一致;从校正后的记录生成旧字段,gap 始终 ready=false。
6
+ - task/route/explain/status 的旧顶层 ready 复用公开结果核心判定,并核对当前适用 scope;缺资产、歧义、未接入/移动项目、缺上下文或计划过期均不准入。
7
+ - 增加真实 CLI 和协议版本漂移、恢复后重判、缺口/范围/新鲜度回归;ready 不证明宿主读取、代码执行或业务验证通过。
8
+
3
9
  ## [1.20.0] - 2026-10-11
4
10
 
5
11
  - scan 严格校验已注册规则、范围与参数,CLI/MCP 使用同源选择器;非法请求零检查零任务记录。
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 项目范围:只对目标自身已接入且平台适用的项目判定规则;父工作区安装不启用子项目。移动端与未接入开源项目不套用。详见 [精准触发与范围配置](docs/project-scope.md)。
4
4
 
5
- **企业级 UI 一致性与 AI 精确治理工具包 v1.17.0** — 面向 Vue 3 + Element Plus 的设计令牌、能力 Profile、样式皮肤、页面骨架、运行时能力、静态扫描、确定性修复、AI Skill、MCP 与脱敏 UI 模板契约。
5
+ **企业级 UI 一致性与 AI 精确治理工具包 v1.20.1** — 面向 Vue 3 + Element Plus 的设计令牌、能力 Profile、样式皮肤、页面骨架、运行时能力、静态扫描、确定性修复、AI Skill、MCP 与脱敏 UI 模板契约。
6
6
 
7
7
  它把“视觉统一、问题识别、机械修复、项目适配和样例沉淀”拆成可验证、可组合的工程能力。脚本、规则和 MCP 先完成确定性工作,AI 只处理业务语义与人工决策,从而减少重复读源码、上下文 token 和误修改。
8
8
 
@@ -17,6 +17,8 @@
17
17
 
18
18
  `wl-ui doctor-host --json` 对比已分发规范、本地执行器和正在运行的版本,漂移会显式报告。规范更新不会替代依赖升级:同步本包依赖、锁文件和受管入口;未使用的兄弟包无需安装。重开/刷新宿主加载后仍需观察真实任务调用,不能宣称所有 AI 自动触发。
19
19
 
20
+ `task/route/explain/status` 的旧顶层 `ready` 与公开协议的 `result.integration.ready` 表示本次工作流的有效就绪状态:当前项目必须已接入且 `scope=applicable`,判定为 matched/baseline,所需资产完整、版本没有已知漂移、计划尚未过期。gap、歧义、缺上下文、未接入/移动项目均为 false,不能只因规范文件存在就称 ready。漂移时旧顶层 task 的 status/ready 与嵌套 decision、notice 保持一致;状态回查以当前 scope 和新鲜度为准。ready=true 不证明宿主读取、自动检查权限、工具执行或业务验证通过,执行仍需核对动作许可和 allowedExecutors。
21
+
20
22
  ## 你能获得什么
21
23
 
22
24
  | 目标 | 包提供的能力 | 带来的效果 |
@@ -510,7 +512,7 @@ npx wl-ui doctor --project . --print-overrides
510
512
 
511
513
  ## 版本与进一步阅读
512
514
 
513
- 当前 v1.20.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 依赖识别。
515
+ 当前 v1.20.1:公开集成协议 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 依赖识别。
514
516
 
515
517
  - [版本摘要](docs/version-history.md)
516
518
  - [完整 Changelog](CHANGELOG.md)
@@ -0,0 +1,133 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import {spawnSync} from "node:child_process";
7
+
8
+ const repo = path.resolve(import.meta.dirname, '../..');
9
+ const pkg = JSON.parse(fs.readFileSync(path.join(repo, "package.json"), "utf8"));
10
+ const cli = path.join(repo, "bin", 'wl-ui.js');
11
+ const key = 'ui';
12
+ const manifestName = key === "kit" ? ".wl-skills-manifest.json" : ".wl-skills-ui-manifest.json";
13
+ const selected = "src/views/Example/index.vue";
14
+
15
+ function fixture() {
16
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `wl-${key}-ready-`));
17
+ fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({private: true, devDependencies: {[pkg.name]: pkg.version, vue: "^3.4.0"}}));
18
+ fs.mkdirSync(path.join(root, "node_modules/@agile-team"), {recursive: true});
19
+ fs.symlinkSync(repo, path.join(root, "node_modules", pkg.name), "dir");
20
+ fs.mkdirSync(path.dirname(path.join(root, selected)), {recursive: true});
21
+ fs.writeFileSync(path.join(root, selected), "<template><div>example</div></template>\n");
22
+ const initialized = spawnSync(process.execPath, [cli, "init", ...(key === "ui" ? ["--editor", "all", "--profile", "native-element"] : [])], {cwd: root, encoding: "utf8", timeout: 30000});
23
+ assert.equal(initialized.status, 0, initialized.stderr || initialized.stdout);
24
+ return root;
25
+ }
26
+
27
+ function request(root, transport, operation, input = {}) {
28
+ let args;
29
+ if (transport === "protocol") {
30
+ const file = path.join(root, "request.json");
31
+ fs.writeFileSync(file, JSON.stringify({operation, projectRoot: root, ...input}));
32
+ args = ["protocol", "request", "--input-file", file, "--json"];
33
+ } else {
34
+ args = [operation, ...(input.task ? [input.task] : []), ...(input.targets || []).flatMap(target => ["--target", target]), ...(input.runId ? ["--run-id", input.runId] : []), "--json"];
35
+ }
36
+ const processResult = spawnSync(process.execPath, [cli, ...args], {cwd: root, encoding: "utf8", timeout: 30000});
37
+ assert.equal(processResult.status, 0, processResult.stderr || processResult.stdout);
38
+ const result = JSON.parse(processResult.stdout);
39
+ if (transport === "protocol") {assert.equal(result.ok, true);return result.result;}
40
+ return result;
41
+ }
42
+
43
+ function readiness(result, ready, status, runtime) {
44
+ const decision = result.decision || result;
45
+ assert.equal(result.ready, ready, "legacy ready must express effective admission");
46
+ if (status) assert.equal(decision.status, status);
47
+ if (result.status && result.decision) assert.equal(result.status, decision.status, "flattened task status agrees with its recorded decision");
48
+ if (runtime) assert.equal(result.notice.runtime.status, runtime);
49
+ if (result.integration) {
50
+ assert.equal(result.integration.ready, ready);
51
+ if (!ready) assert.deepEqual(result.integration.allowedExecutors, []);
52
+ }
53
+ }
54
+
55
+ test("legacy 与公开协议:真实版本漂移拒绝且 task/status/route/explain 的 ready 一致", () => {
56
+ const root = fixture();
57
+ try {
58
+ for (const transport of ["legacy", "protocol"]) {
59
+ for (const operation of ["route", "explain", "task"]) {
60
+ const input = {task: "检查当前 Vue 代码", targets: [selected], ...(operation === "task" ? {runId: `aligned-${transport}`} : {})};
61
+ readiness(request(root, transport, operation, input), true, "baseline", "aligned");
62
+ }
63
+ readiness(request(root, transport, "status", {runId: `aligned-${transport}`}), true, "baseline", "aligned");
64
+ }
65
+ const manifest = path.join(root, manifestName);
66
+ const bytes = fs.readFileSync(manifest);
67
+ const old = JSON.parse(bytes);old.version = key === "kit" ? "2.30.0" : "1.19.0";
68
+ fs.writeFileSync(manifest, JSON.stringify(old));
69
+ for (const transport of ["legacy", "protocol"]) {
70
+ for (const operation of ["route", "explain", "task"]) {
71
+ const input = {task: "检查当前 Vue 代码", targets: [selected], ...(operation === "task" ? {runId: `drift-${transport}`} : {})};
72
+ const result = request(root, transport, operation, input);
73
+ readiness(result, false, "gap", "mismatch");
74
+ assert.equal((result.decision || result).scope.status, "applicable");
75
+ assert.ok((result.decision || result).gaps.some(gap => gap.code === "runtime-mismatch"));
76
+ }
77
+ readiness(request(root, transport, "status", {runId: `aligned-${transport}`}), false, "gap", "mismatch");
78
+ readiness(request(root, transport, "status", {runId: `drift-${transport}`}), false, "gap", "mismatch");
79
+ }
80
+ fs.writeFileSync(manifest, bytes);
81
+ readiness(request(root, "protocol", "task", {task: "检查当前 Vue 代码", targets: [selected], runId: "replanned"}), true, "baseline", "aligned");
82
+ readiness(request(root, "protocol", "status", {runId: "replanned"}), true, "baseline", "aligned");
83
+ } finally {fs.rmSync(root, {recursive: true, force: true});}
84
+ });
85
+
86
+ test("规则资产存在不能替代适用scope、无歧义与计划新鲜度", () => {
87
+ const root = fixture();
88
+ try {
89
+ for (const transport of ["legacy", "protocol"]) {
90
+ readiness(request(root, transport, "route", {task: "帮我写诗"}), false, "not-applicable");
91
+ readiness(request(root, transport, "route", {task: "闲聊"}), false, "needs-context");
92
+ readiness(request(root, transport, "route", {task: key === "kit" ? "创建页面并自动修复" : "检查表格和表单", targets: [selected]}), false, "ambiguous");
93
+ readiness(request(root, transport, "status", {runId: "absent"}), false);
94
+ }
95
+ const owned = key === "kit" ? path.join(root, ".wl-skills/standards/03-comments.md") : path.join(root, "node_modules", pkg.name);
96
+ const bytes = key === "kit" ? fs.readFileSync(owned) : null;
97
+ fs.unlinkSync(owned);
98
+ for (const transport of ["legacy", "protocol"]) {
99
+ const result = request(root, transport, "route", {task: "检查当前 Vue 代码", targets: [selected]});
100
+ readiness(result, false, "gap");
101
+ assert.ok((result.decision || result).missingInputs.length > 0);
102
+ }
103
+ if (key === "kit") fs.writeFileSync(owned, bytes);
104
+ else fs.symlinkSync(repo, owned, "dir");
105
+ readiness(request(root, "legacy", "task", {task: "检查当前 Vue 代码", targets: [selected], runId: "freshness"}), true, "baseline");
106
+ fs.appendFileSync(path.join(root, selected), "<!-- modified after inspect plan -->\n");
107
+ for (const transport of ["legacy", "protocol"]) {
108
+ const result = request(root, transport, "status", {runId: "freshness"});
109
+ assert.equal(result.planStale, true);readiness(result, false, "baseline");
110
+ }
111
+ fs.writeFileSync(path.join(root, ".wl-skills-scope.json"), JSON.stringify({schemaVersion: 1, projectType: "pc", packages: {[key]: false}}));
112
+ for (const transport of ["legacy", "protocol"]) {
113
+ const result = request(root, transport, "status", {runId: "freshness"});
114
+ assert.equal(result.currentScope.status, "not-applicable");readiness(result, false);
115
+ readiness(request(root, transport, "task", {task: "检查当前 Vue 代码", targets: [selected], runId: "disabled"}), false, "not-applicable");
116
+ }
117
+ } finally {fs.rmSync(root, {recursive: true, force: true});}
118
+ });
119
+
120
+ test("未接入和移动项目即使有Vue任务对象也不准入,不新建任务记录", () => {
121
+ for (const mobile of [false, true]) {
122
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `wl-${key}-excluded-`));
123
+ try {
124
+ fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({private: true, devDependencies: {vue: "^3.4.0", ...(mobile ? {[pkg.name]: pkg.version, "@dcloudio/uni-app": "*"} : {})}}));
125
+ for (const transport of ["legacy", "protocol"]) {
126
+ const result = request(root, transport, "task", {task: "检查当前 Vue 代码", targets: [selected], runId: "excluded"});
127
+ readiness(result, false, "not-applicable");
128
+ assert.deepEqual(result.requiredRules, []);
129
+ }
130
+ assert.equal(fs.existsSync(path.join(root, key === "kit" ? ".wl-skills/runs" : ".wl-skills-ui/runs")), false);
131
+ } finally {fs.rmSync(root, {recursive: true, force: true});}
132
+ }
133
+ });
@@ -4,6 +4,7 @@ import { parseArgs } from "node:util";
4
4
  import core from "./task-observability.cjs";
5
5
  import projectScope from "./project-scope.cjs";
6
6
  import taskIntent from "./task-intent.cjs";
7
+ import resultCore from "./integration-result.cjs";
7
8
  import { GATEWAY_PATH } from "./task-gateway.mjs";
8
9
  import { resolveProjectProfile } from "../standards/profiles-loader.mjs";
9
10
  import { getRules } from "../scanner/rules/index.mjs";
@@ -78,9 +79,9 @@ function capabilityGap(decision, reason) {
78
79
  export function runTaskAction(action, input = {}) {
79
80
  const resolved = projectScope.resolveScope({ projectRoot: input.projectRoot || process.env.WL_PROJECT_ROOT || process.cwd(), packageName: pkg.name, targets: input.targets || [] });
80
81
  const opts = taskOptions(resolved.projectRoot, { runId: input.runId, targets: resolved.targets });
81
- if (action === "status") return core.readStatus(opts);
82
+ if (action === "status") return taskResultReadiness(core.readStatus(opts), opts);
82
83
  if (action === "doctor-host") return hostDiagnostic({ ...opts, host: input.host || "unknown" });
83
- if (resolved.scope.status !== "applicable") return core.attachNotice(projectScope.excludedDecision(resolved.scope), opts);
84
+ if (resolved.scope.status !== "applicable") return taskResultReadiness(core.attachNotice(projectScope.excludedDecision(resolved.scope), opts), opts);
84
85
  const profile = resolveProjectProfile({ projectRoot: opts.projectRoot, profile: input.profile }).profile;
85
86
  const routed = decideTask({ task: input.task || "", targets: opts.targets, skill: input.skill, context: input.context });
86
87
  const applicable = getRules({ profile: profile.id });
@@ -90,8 +91,14 @@ export function runTaskAction(action, input = {}) {
90
91
  decision.requiredRules = decision.requiredRules.filter((id) => enabled.has(id));
91
92
  decision.ruleDetails = applicable.filter((rule) => decision.requiredRules.includes(rule.id)).map((rule) => ({ id: rule.id, name: rule.title, source: `node_modules/${pkg.name}/standards/rules.json` }));
92
93
  assetReadiness(decision, opts.projectRoot);
93
- if (action !== "task") return core.attachNotice(decision, opts);
94
- return core.attachNotice({ ...decision, ...core.startTask({ ...opts, task: input.task || "", decision }) }, opts);
94
+ if (action !== "task") return taskResultReadiness(core.attachNotice(decision, opts), opts);
95
+ const record = core.startTask({ ...opts, task: input.task || "", decision });
96
+ return taskResultReadiness(core.attachNotice({ ...record.decision, ...record }, opts), opts);
97
+ }
98
+
99
+ function taskResultReadiness(result, opts) {
100
+ const effective = resultCore.projectResult(result, opts, { packageName: pkg.name, packageVersion: pkg.version }).integration;
101
+ return { ...result, ready: effective.scope?.status === "applicable" && effective.ready === true };
95
102
  }
96
103
 
97
104
  function hostDiagnostic(opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-ui",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "description": "企业级 UI 风格对齐框架 — Vue + Element Plus 双模式、任务路由与真实检查回执(tokens / element / vendors / layouts / runtime / scanner / fixer / skills)",
5
5
  "type": "module",
6
6
  "main": "./es/index.js",