@agile-team/wl-skills-ui 1.17.0 → 1.18.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 +6 -0
- package/README.md +3 -1
- package/SKILL.md +4 -0
- package/bin/__tests__/protocol-cli.test.mjs +4 -2
- package/bin/__tests__/protocol-evidence.test.mjs +4 -2
- package/bin/__tests__/protocol-routing.test.mjs +3 -2
- package/bin/project-scope.cjs +198 -0
- package/bin/project-scope.schema.json +48 -0
- package/bin/protocol-cli.mjs +1 -1
- package/bin/task-gateway.mjs +3 -1
- package/bin/task-integration.mjs +6 -3
- package/bin/task-observability.cjs +14 -4
- package/bin/task-observability.schema.json +407 -52
- package/bin/wl-ui.js +2 -2
- package/docs/project-scope.md +44 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.18.0] - 2026-10-11
|
|
4
|
+
|
|
5
|
+
- 任务路由前校验目标项目自身接入与平台;未接入/移动端零规则零任务写入,不继承父安装。
|
|
6
|
+
- 最近项目边界与跨项目拆分、独立包禁用、严格范围Schema、notice/doctor/status范围与过期证据。
|
|
7
|
+
- 原生及编辑器入口收窄适用描述;补充单包、移动端、开源未接入、工作区、异常配置和Maven模块回归。
|
|
8
|
+
|
|
3
9
|
All notable changes to **@agile-team/wl-skills-ui** will be documented in this file.
|
|
4
10
|
|
|
5
11
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# @agile-team/wl-skills-ui
|
|
2
2
|
|
|
3
|
+
> 项目范围:只对目标自身已接入且平台适用的项目判定规则;父工作区安装不启用子项目。移动端与未接入开源项目不套用。详见 [精准触发与范围配置](docs/project-scope.md)。
|
|
4
|
+
|
|
3
5
|
**企业级 UI 一致性与 AI 精确治理工具包 v1.17.0** — 面向 Vue 3 + Element Plus 的设计令牌、能力 Profile、样式皮肤、页面骨架、运行时能力、静态扫描、确定性修复、AI Skill、MCP 与脱敏 UI 模板契约。
|
|
4
6
|
|
|
5
7
|
它把“视觉统一、问题识别、机械修复、项目适配和样例沉淀”拆成可验证、可组合的工程能力。脚本、规则和 MCP 先完成确定性工作,AI 只处理业务语义与人工决策,从而减少重复读源码、上下文 token 和误修改。
|
|
@@ -508,7 +510,7 @@ npx wl-ui doctor --project . --print-overrides
|
|
|
508
510
|
|
|
509
511
|
## 版本与进一步阅读
|
|
510
512
|
|
|
511
|
-
当前 v1.
|
|
513
|
+
当前 v1.18.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 依赖识别。
|
|
512
514
|
|
|
513
515
|
- [版本摘要](docs/version-history.md)
|
|
514
516
|
- [完整 Changelog](CHANGELOG.md)
|
package/SKILL.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wl-skills-ui
|
|
3
3
|
description: |
|
|
4
|
+
Only for PC Vue admin projects that adopted wl-skills-ui; excludes mobile and unadopted projects.
|
|
4
5
|
Audit and align Vue 3 + Element Plus enterprise UI projects with deterministic
|
|
5
6
|
scanning, profile-aware fixes, design tokens, vendor adapters, and runtime guards.
|
|
6
7
|
Use for UI consistency, legacy skin migration, rule diagnostics, or safe UI fixes.
|
|
7
8
|
---
|
|
8
9
|
|
|
10
|
+
先确认目标项目自身的接入证据(本包安装清单、直接依赖或 `.wl-skills-scope.json` 显式启用),再判定任务。只在目标已接入且平台适用时读取规则;父目录安装、兄弟包、Vue 文件或触发词不能证明接入。未接入的开源项目不套用规则、不建议自动安装;UniApp/小程序/App/PDA/移动 H5 不适用。跨项目目标分别判定,沿最近项目边界停止,不能越过未接入子项目找父清单。工具返回 `scope` 作为静态范围证据,宿主加载仍需实际事件。
|
|
11
|
+
|
|
12
|
+
|
|
9
13
|
# wl-skills-ui
|
|
10
14
|
|
|
11
15
|
Use deterministic project facts before reading or editing business source. Rules,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { test } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { mkdtempSync } from "node:fs";
|
|
6
|
+
import fs, { mkdtempSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
@@ -17,7 +17,9 @@ const pkg = require("../../package.json");
|
|
|
17
17
|
const BIN = path.join(here, "..", "wl-ui.js");
|
|
18
18
|
|
|
19
19
|
function tempRoot() {
|
|
20
|
-
|
|
20
|
+
const root = mkdtempSync(path.join(tmpdir(), "wl-ui-protocol-"));
|
|
21
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ devDependencies: { "@agile-team/wl-skills-ui": "*" } }));
|
|
22
|
+
return root;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
test("describe 返回协议版本、能力目录与五个统一操作", () => {
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { test } from "node:test";
|
|
5
5
|
import assert from "node:assert/strict";
|
|
6
|
-
import { mkdtempSync } from "node:fs";
|
|
6
|
+
import fs, { mkdtempSync } from "node:fs";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
|
|
10
10
|
const { protocol, runOperation } = await import("../protocol-cli.mjs");
|
|
11
11
|
|
|
12
12
|
function tempRoot() {
|
|
13
|
-
|
|
13
|
+
const root = mkdtempSync(path.join(tmpdir(), "wl-ui-evidence-"));
|
|
14
|
+
fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ devDependencies: { "@agile-team/wl-skills-ui": "*" } }));
|
|
15
|
+
return root;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
test("status 严格回查同一 runId:字段精确、不兜底", () => {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { test, before } from "node:test";
|
|
6
6
|
import assert from "node:assert/strict";
|
|
7
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
7
|
+
import fs, { mkdtempSync, rmSync } from "node:fs";
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
@@ -22,6 +22,7 @@ function tempRoot() {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function routeAt(projectRoot, task) {
|
|
25
|
+
if (!fs.existsSync(path.join(projectRoot, "package.json"))) fs.writeFileSync(path.join(projectRoot, "package.json"), JSON.stringify({ devDependencies: { "@agile-team/wl-skills-ui": "*" } }));
|
|
25
26
|
return protocol.request({ operation: "route", projectRoot, task }, runOperation);
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -40,7 +41,7 @@ for (const item of cases) {
|
|
|
40
41
|
const expectedBare = item.skill ? "gap" : item.status;
|
|
41
42
|
const expectedInstalled = item.installedStatus || (item.skill ? "matched" : item.status);
|
|
42
43
|
|
|
43
|
-
test(
|
|
44
|
+
test(`已声明接入但未初始化:「${item.task}」→ ${expectedBare}${item.skill ? ` + ${item.skill}` : ""}`, () => {
|
|
44
45
|
const envelope = routeAt(tempRoot(), item.task);
|
|
45
46
|
assert.equal(envelope.ok, true);
|
|
46
47
|
const decision = envelope.result.decision || envelope.result;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// Independent snapshot: project adoption is checked before skill intent or writes.
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const crypto = require("node:crypto");
|
|
7
|
+
const CONFIG = ".wl-skills-scope.json";
|
|
8
|
+
const MANIFESTS = {
|
|
9
|
+
kit: ".wl-skills-manifest.json", ui: ".wl-skills-ui-manifest.json",
|
|
10
|
+
bd: ".wl-skills-bd-manifest.json", design: ".wl-skills-design/state.json", test: ".wl-skills-test/manifest.json",
|
|
11
|
+
};
|
|
12
|
+
const TYPES = new Set(["pc", "backend", "documents", "testing", "mobile", "workspace", "unknown"]);
|
|
13
|
+
|
|
14
|
+
function inside(root, target) {
|
|
15
|
+
const rel = path.relative(root, target);
|
|
16
|
+
return !path.isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${path.sep}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Resolve missing paths through their nearest existing ancestor, including symlinks.
|
|
20
|
+
function realTarget(target) {
|
|
21
|
+
if (fs.existsSync(target)) return fs.realpathSync(target);
|
|
22
|
+
const parent = path.dirname(target);
|
|
23
|
+
if (parent === target) throw new Error("Cannot resolve target ancestor");
|
|
24
|
+
return path.join(realTarget(parent), path.basename(target));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJson(root, relative) {
|
|
28
|
+
const file = path.join(root, relative);
|
|
29
|
+
if (!fs.existsSync(file)) return { present: false, value: null };
|
|
30
|
+
try {
|
|
31
|
+
if (!inside(root, fs.realpathSync(file))) throw new Error("metadata path escapes project");
|
|
32
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
33
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected object");
|
|
34
|
+
return { present: true, value };
|
|
35
|
+
} catch (error) { return { present: true, value: null, error: `${relative}: ${error.message}` }; }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function boundary(root, fallback) {
|
|
39
|
+
const markers = ["package.json", ".git", CONFIG, ...Object.values(MANIFESTS)];
|
|
40
|
+
// A Java project's Maven modules share its adoption; a nested independent repo still stops lookup.
|
|
41
|
+
if (root === fallback || !fs.existsSync(path.join(fallback, "pom.xml"))) markers.push("pom.xml");
|
|
42
|
+
return markers.some((file) => fs.existsSync(path.join(root, file)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function nearestRoot(target, fallback) {
|
|
46
|
+
let dir = fs.existsSync(target) && fs.statSync(target).isDirectory() ? target : path.dirname(target);
|
|
47
|
+
while (inside(fallback, dir)) {
|
|
48
|
+
if (boundary(dir, fallback) || dir === fallback) return dir;
|
|
49
|
+
dir = path.dirname(dir);
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function targetRoots(root, targets) {
|
|
55
|
+
const resolved = targets.map((target) => realTarget(path.resolve(root, target)));
|
|
56
|
+
if (resolved.some((target) => !inside(root, target))) return { error: "target-outside-authorized-project", roots: [], resolved };
|
|
57
|
+
return { roots: [...new Set(resolved.map((target) => nearestRoot(target, root)))], resolved };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function scopeConfig(root) {
|
|
61
|
+
const config = readJson(root, CONFIG);
|
|
62
|
+
if (!config.present || config.error) return config;
|
|
63
|
+
const { value } = config;
|
|
64
|
+
const allowed = ["schemaVersion", "projectType", "packages"];
|
|
65
|
+
if (value.schemaVersion !== 1 || !TYPES.has(value.projectType) || Object.keys(value).some((key) => !allowed.includes(key)) || !validPackageSwitches(value.packages)) {
|
|
66
|
+
return { ...config, error: `${CONFIG}: invalid schemaVersion/projectType/packages` };
|
|
67
|
+
}
|
|
68
|
+
return config;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function validPackageSwitches(packages) {
|
|
72
|
+
if (packages === undefined) return true;
|
|
73
|
+
return Boolean(packages && typeof packages === "object" && !Array.isArray(packages) && Object.entries(packages).every(([key, enabled]) => Object.hasOwn(MANIFESTS, key) && typeof enabled === "boolean"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function mobileEvidence(root, pkg) {
|
|
77
|
+
const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
78
|
+
const mobile = deps.filter((name) => /^@dcloudio\/uni(?:-|$)|^@tarojs\/|^react-native$|^expo$|^@ionic\/|^@capacitor\//.test(name));
|
|
79
|
+
if (fs.existsSync(path.join(root, "pages.json")) && fs.existsSync(path.join(root, "manifest.json"))) mobile.push("pages.json+manifest.json");
|
|
80
|
+
return mobile;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function adoption(root, key, packageName, pkg, config) {
|
|
84
|
+
const manifest = readJson(root, MANIFESTS[key]);
|
|
85
|
+
validateAdoptionManifest(manifest, key, packageName);
|
|
86
|
+
const declared = [pkg.dependencies, pkg.devDependencies, pkg.optionalDependencies].some((deps) => deps?.[packageName]);
|
|
87
|
+
const explicit = config.value?.packages?.[key] === true;
|
|
88
|
+
const installed = manifest.present && !manifest.error;
|
|
89
|
+
return { adopted: explicit || declared || installed, sources: [
|
|
90
|
+
...(explicit ? [`${CONFIG}:packages.${key}`] : []), ...(declared ? ["package.json:direct-dependency"] : []),
|
|
91
|
+
...(installed ? [MANIFESTS[key]] : []),
|
|
92
|
+
], error: manifest.error || null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateAdoptionManifest(manifest, key, packageName) {
|
|
96
|
+
if (!manifest.present || manifest.error) return;
|
|
97
|
+
const { value } = manifest;
|
|
98
|
+
if (typeof value.version !== "string" || !value.version.trim() || value.package && value.package !== packageName) manifest.error = `${MANIFESTS[key]}: invalid package/version`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function verdict(status, reason, extra = {}) {
|
|
102
|
+
return { schemaVersion: 1, status, reason, adopted: false, projectType: "unknown", evidence: [], inherited: false, ...extra };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function inspectProject(root, key, packageName) {
|
|
106
|
+
const config = scopeConfig(root);
|
|
107
|
+
const packageFile = readJson(root, "package.json");
|
|
108
|
+
if (config.error || packageFile.error) return verdict("needs-context", "invalid-project-metadata", { diagnostics: [config.error || packageFile.error] });
|
|
109
|
+
const pkg = packageFile.value || {};
|
|
110
|
+
if (!["dependencies", "devDependencies", "optionalDependencies"].every((field) => validDependencies(pkg[field]))) {
|
|
111
|
+
return verdict("needs-context", "invalid-project-metadata", { diagnostics: ["package.json: dependency declarations must be nonempty strings"] });
|
|
112
|
+
}
|
|
113
|
+
const exclusion = platformExclusion(root, key, pkg, config);
|
|
114
|
+
if (exclusion) return exclusion;
|
|
115
|
+
const own = adoption(root, key, packageName, pkg, config);
|
|
116
|
+
if (own.error) return verdict("needs-context", "invalid-package-adoption", { diagnostics: [own.error] });
|
|
117
|
+
if (!own.adopted) return verdict("not-applicable", "package-not-adopted-in-target-project");
|
|
118
|
+
return adoptedScope(root, key, pkg, config, own);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function validDependencies(deps) {
|
|
122
|
+
if (deps === undefined) return true;
|
|
123
|
+
return Boolean(deps && typeof deps === "object" && !Array.isArray(deps) && Object.values(deps).every((value) => typeof value === "string" && value.trim()));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function platformExclusion(root, key, pkg, config) {
|
|
127
|
+
const mobile = mobileEvidence(root, pkg);
|
|
128
|
+
const type = config.value?.projectType;
|
|
129
|
+
if (mobile.length || type === "mobile") return verdict("not-applicable", "mobile-project-excluded", { projectType: "mobile", evidence: mobile.length ? mobile : [CONFIG] });
|
|
130
|
+
if (type === "workspace") return verdict("not-applicable", "aggregate-workspace-is-not-project", { projectType: type, evidence: [CONFIG] });
|
|
131
|
+
if (config.value?.packages?.[key] === false) return verdict("not-applicable", "package-explicitly-disabled", { projectType: type || "unknown", evidence: [CONFIG] });
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function unsupportedFrontend(root, key, pkg, type) {
|
|
136
|
+
if (!["kit", "ui"].includes(key)) return false;
|
|
137
|
+
if (type && type !== "pc") return "pc-frontend-package-excluded";
|
|
138
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
139
|
+
return !deps.vue && (deps.react || deps.svelte || deps["@angular/core"] || fs.existsSync(path.join(root, "pom.xml"))) ? "unsupported-project-framework" : false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function defaultType(key) { return { kit: "pc", ui: "pc", bd: "backend", design: "documents", test: "testing" }[key]; }
|
|
143
|
+
|
|
144
|
+
function scopeFingerprint(root, key) {
|
|
145
|
+
const metadata = [CONFIG, "package.json", "pom.xml", MANIFESTS[key], "pages.json", "manifest.json"].map((file) => {
|
|
146
|
+
const full = path.join(root, file);
|
|
147
|
+
if (!fs.existsSync(full)) return [file, "missing"];
|
|
148
|
+
if (!inside(root, fs.realpathSync(full))) return [file, "external-metadata"];
|
|
149
|
+
try { return [file, crypto.createHash("sha256").update(fs.readFileSync(full)).digest("hex")]; }
|
|
150
|
+
catch (error) { return [file, `unreadable:${error.code}`]; }
|
|
151
|
+
});
|
|
152
|
+
return crypto.createHash("sha256").update(JSON.stringify(metadata)).digest("hex");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function adoptedScope(root, key, pkg, config, own) {
|
|
156
|
+
const type = config.value?.projectType;
|
|
157
|
+
if (type === "unknown") return verdict("needs-context", "project-platform-needs-context", { adopted: true, evidence: own.sources });
|
|
158
|
+
const unsupported = unsupportedFrontend(root, key, pkg, type);
|
|
159
|
+
if (unsupported) return verdict("not-applicable", unsupported, { adopted: true, projectType: type || "unknown", evidence: ["package.json/pom.xml", ...own.sources] });
|
|
160
|
+
return verdict("applicable", "target-project-explicitly-adopted-package", {
|
|
161
|
+
adopted: true, projectType: type || defaultType(key),
|
|
162
|
+
typeEvidence: type ? "explicit-config" : "adoption-default-not-host-observed", evidence: own.sources,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Parent installation, Vue files and keyword matches are never adoption evidence. */
|
|
167
|
+
function resolveScope({ projectRoot, packageName, targets = [] }) {
|
|
168
|
+
const root = fs.realpathSync(path.resolve(projectRoot || process.cwd()));
|
|
169
|
+
const key = packageName.replace("@agile-team/wl-skills-", "");
|
|
170
|
+
if (!Object.hasOwn(MANIFESTS, key)) throw new Error("Unknown WL package");
|
|
171
|
+
const located = targetRoots(root, targets.filter((item) => typeof item === "string" && item.trim()));
|
|
172
|
+
if (located.error || located.roots.length > 1) return { projectRoot: root, targets, scope: verdict("needs-context", located.error || "multiple-project-targets-split-required", { projectRoots: located.roots }) };
|
|
173
|
+
const actualRoot = located.roots[0] || root;
|
|
174
|
+
const actualTargets = located.resolved.map((target) => path.relative(actualRoot, target).split(path.sep).join("/") || ".");
|
|
175
|
+
return { projectRoot: actualRoot, targets: actualTargets, scope: { ...inspectProject(actualRoot, key, packageName), projectRoot: actualRoot, fingerprint: scopeFingerprint(actualRoot, key) } };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function excludedDecision(scope) {
|
|
179
|
+
const messages = {
|
|
180
|
+
"mobile-project-excluded": "目标为移动端/UniApp/小程序项目,不适用本轮WL项目约束",
|
|
181
|
+
"aggregate-workspace-is-not-project": "目标是聚合工作区,请指定实际已接入项目",
|
|
182
|
+
"package-explicitly-disabled": "目标项目已明确禁用本包",
|
|
183
|
+
"package-not-adopted-in-target-project": "目标项目未接入本包,不继承父目录或兄弟项目的安装",
|
|
184
|
+
"pc-frontend-package-excluded": "目标项目不是已接入的PC前端,PC规范不适用",
|
|
185
|
+
"unsupported-project-framework": "目标技术栈不是本包支持的Vue管理端,不套用PC Vue规范",
|
|
186
|
+
"multiple-project-targets-split-required": "目标跨越多个项目,请按项目分别判定并复用任务runId",
|
|
187
|
+
"target-outside-authorized-project": "目标超出当前授权项目边界,请从实际目标项目根调用",
|
|
188
|
+
"project-platform-needs-context": "项目已接入但平台未确认,请确认PC/后端/文档/测试或移动端范围",
|
|
189
|
+
};
|
|
190
|
+
const reason = messages[scope.reason] || "项目范围元数据无效,需要核对后再应用规则";
|
|
191
|
+
return { recordVersion: 1, status: scope.status, applicable: scope.status === "needs-context" ? null : false,
|
|
192
|
+
reason, reasons: [reason], scope, selectedSkills: [], candidates: [], baselineRules: [], requiredRules: [],
|
|
193
|
+
ruleDetails: [], requiredChecks: [], requiredFiles: [], missingInputs: scope.status === "needs-context" ? ["有效的目标项目范围"] : [],
|
|
194
|
+
gaps: [], constraints: [], ready: false, executionStatus: "not-executed", validationStatus: "unverified",
|
|
195
|
+
selectionEvidence: "project-scope-before-intent", hostDiscovery: "unverified", contentLoaded: "unverified", unverified: ["host-discovery", "model-read-canonical-files"] };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = { CONFIG, MANIFESTS, resolveScope, excludedDecision };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "urn:wl-skills:project-scope:1",
|
|
4
|
+
"title": "Explicit WL project scope and per-package adoption",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": [
|
|
7
|
+
"schemaVersion",
|
|
8
|
+
"projectType"
|
|
9
|
+
],
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {
|
|
12
|
+
"schemaVersion": {
|
|
13
|
+
"const": 1
|
|
14
|
+
},
|
|
15
|
+
"projectType": {
|
|
16
|
+
"enum": [
|
|
17
|
+
"pc",
|
|
18
|
+
"backend",
|
|
19
|
+
"documents",
|
|
20
|
+
"testing",
|
|
21
|
+
"mobile",
|
|
22
|
+
"workspace",
|
|
23
|
+
"unknown"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"packages": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": false,
|
|
29
|
+
"properties": {
|
|
30
|
+
"kit": {
|
|
31
|
+
"type": "boolean"
|
|
32
|
+
},
|
|
33
|
+
"ui": {
|
|
34
|
+
"type": "boolean"
|
|
35
|
+
},
|
|
36
|
+
"bd": {
|
|
37
|
+
"type": "boolean"
|
|
38
|
+
},
|
|
39
|
+
"design": {
|
|
40
|
+
"type": "boolean"
|
|
41
|
+
},
|
|
42
|
+
"test": {
|
|
43
|
+
"type": "boolean"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/bin/protocol-cli.mjs
CHANGED
|
@@ -80,7 +80,7 @@ export const protocol = createProtocol({
|
|
|
80
80
|
packageName: pkg.name,
|
|
81
81
|
packageVersion: pkg.version,
|
|
82
82
|
capabilities: capabilitiesDocument.capabilities || [],
|
|
83
|
-
constraints: { node: (pkg.engines && pkg.engines.node) || null, boundaryVersion: capabilitiesDocument.boundaryVersion || null },
|
|
83
|
+
constraints: { projectScope: { config: ".wl-skills-scope.json", schema: "bin/project-scope.schema.json", adoption: "own-manifest-or-direct-dependency-or-explicit-enable", inheritance: "never-across-project-boundaries", excluded: ["mobile", "unadopted-project", "aggregate-workspace"] }, node: (pkg.engines && pkg.engines.node) || null, boundaryVersion: capabilitiesDocument.boundaryVersion || null },
|
|
84
84
|
operations: OPERATIONS,
|
|
85
85
|
inventory: buildInventory(),
|
|
86
86
|
});
|
package/bin/task-gateway.mjs
CHANGED
|
@@ -3,12 +3,14 @@ export const GATEWAY_PATH = ".agents/skills/wl-skills-ui/SKILL.md";
|
|
|
3
3
|
export function taskGateway() {
|
|
4
4
|
return `---
|
|
5
5
|
name: wl-skills-ui
|
|
6
|
-
description: Vue and Element Plus UI consistency, design tokens, styles, component layout and visual rule checks. Decide the applicable UI workflow and verify actual source files using local package tools.
|
|
6
|
+
description: Only for adopted PC Vue/Web admin projects; excludes mobile and unadopted projects. Vue and Element Plus UI consistency, design tokens, styles, component layout and visual rule checks. Decide the applicable UI workflow and verify actual source files using local package tools.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# wl-skills-ui task gateway
|
|
10
10
|
For one user task reuse the same \`--run-id\` or \`WL_TASK_RUN_ID\` across installed applicable packages. Do not install unused sibling packages or create unrelated IDs.
|
|
11
11
|
|
|
12
|
+
先确认目标项目自身的接入证据(本包安装清单、直接依赖或 \`.wl-skills-scope.json\` 显式启用),再判定任务。只在目标已接入且平台适用时读取规则;父目录安装、兄弟包、Vue 文件或触发词不能证明接入。未接入的开源项目不套用规则、不建议自动安装;UniApp/小程序/App/PDA/移动 H5 不适用。跨项目目标分别判定,沿最近项目边界停止,不能越过未接入子项目找父清单。工具返回 \`scope\` 作为静态范围证据,宿主加载仍需实际事件。
|
|
13
|
+
|
|
12
14
|
多项目工作区先沿目标路径向上找到本包安装清单与项目 AGENTS,切到该项目根再调用工具;不要以聚合工作区根代替 projectRoot,也不要在聚合根安装。不同项目分别保存项目身份,同一用户任务复用 runId。
|
|
13
15
|
|
|
14
16
|
1. Start each relevant task from its project root with \`node node_modules/@agile-team/wl-skills-ui/bin/wl-ui.js task "<task>" --target <path> --json\`. Before editing, visibly show the returned \`notice\`: actual package/version, decision, Skill or baseline, rule IDs/names, target, runId and unverified checks. Include baseline and gaps. Report missing/unsupported local commands and version drift explicitly. A routing decision does not execute checks.
|
package/bin/task-integration.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync, readdirSync, existsSync, realpathSync } from "node:fs";
|
|
|
2
2
|
import { join, relative, resolve, isAbsolute } from "node:path";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import core from "./task-observability.cjs";
|
|
5
|
+
import projectScope from "./project-scope.cjs";
|
|
5
6
|
import { GATEWAY_PATH } from "./task-gateway.mjs";
|
|
6
7
|
import { resolveProjectProfile } from "../standards/profiles-loader.mjs";
|
|
7
8
|
import { getRules } from "../scanner/rules/index.mjs";
|
|
@@ -70,14 +71,16 @@ function capabilityGap(decision, reason) {
|
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
export function runTaskAction(action, input = {}) {
|
|
73
|
-
const
|
|
74
|
+
const resolved = projectScope.resolveScope({ projectRoot: input.projectRoot || process.env.WL_PROJECT_ROOT || process.cwd(), packageName: pkg.name, targets: input.targets || [] });
|
|
75
|
+
const opts = taskOptions(resolved.projectRoot, { runId: input.runId, targets: resolved.targets });
|
|
74
76
|
if (action === "status") return core.readStatus(opts);
|
|
75
77
|
if (action === "doctor-host") return hostDiagnostic({ ...opts, host: input.host || "unknown" });
|
|
78
|
+
if (resolved.scope.status !== "applicable") return core.attachNotice(projectScope.excludedDecision(resolved.scope), opts);
|
|
76
79
|
const profile = resolveProjectProfile({ projectRoot: opts.projectRoot, profile: input.profile }).profile;
|
|
77
|
-
const routed = decideTask({ task: input.task || "", targets: opts.targets, skill: input.skill });
|
|
80
|
+
const routed = decideTask({ task: input.task || "", targets: opts.targets, skill: input.skill, context: input.context });
|
|
78
81
|
const applicable = getRules({ profile: profile.id });
|
|
79
82
|
const enabled = new Set(applicable.map((rule) => rule.id));
|
|
80
|
-
const decision = { ...routed, profile: profile.id, applicableConstraints: routed.applicable === true ? [...enabled] : [] };
|
|
83
|
+
const decision = { ...routed, scope: resolved.scope, profile: profile.id, applicableConstraints: routed.applicable === true ? [...enabled] : [] };
|
|
81
84
|
decision.baselineRules = decision.baselineRules.filter((id) => enabled.has(id));
|
|
82
85
|
decision.requiredRules = decision.requiredRules.filter((id) => enabled.has(id));
|
|
83
86
|
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` }));
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require("node:fs");
|
|
5
5
|
const path = require("node:path");
|
|
6
6
|
const crypto = require("node:crypto");
|
|
7
|
+
const projectScope = require("./project-scope.cjs");
|
|
7
8
|
|
|
8
9
|
const RECORD_VERSION = 1;
|
|
9
10
|
const STORAGE = {
|
|
@@ -418,7 +419,7 @@ function statusForRecords(records, options) {
|
|
|
418
419
|
function validationContext(plan, tools, options) {
|
|
419
420
|
const pending = pendingChecks(plan, tools);
|
|
420
421
|
const outdatedChecker = Boolean(options.packageVersion) && [...tools, ...plan ? [plan] : []].some((record) => record.packageVersion !== options.packageVersion);
|
|
421
|
-
const planStale = Boolean(plan) && !
|
|
422
|
+
const planStale = Boolean(plan) && !planInputsFresh(plan, options);
|
|
422
423
|
const stale = outdatedChecker || planStale || tools.some((record) => isFresh(record, options) === false);
|
|
423
424
|
const verifiers = tools.filter((record) => record.checks.length || record.validationStatus !== "unverified");
|
|
424
425
|
const scopeGaps = uncoveredScope(plan, verifiers, options);
|
|
@@ -427,6 +428,13 @@ function validationContext(plan, tools, options) {
|
|
|
427
428
|
return { validationStatus: summarizeValidation(verifiers, pending, stale, unresolvedDecision || scopeGaps.length > 0 || executionOrderUnverified), pendingChecks: pending, stale, outdatedChecker, planStale, scopeGaps, unresolvedDecision, executionOrderUnverified };
|
|
428
429
|
}
|
|
429
430
|
|
|
431
|
+
function planInputsFresh(plan, options) {
|
|
432
|
+
if (!snapshotsFresh([plan.ruleSnapshot, plan.configSnapshot], options)) return false;
|
|
433
|
+
if (!plan.decision.scope?.fingerprint) return true;
|
|
434
|
+
const current = projectScope.resolveScope({ projectRoot: options.projectRoot, packageName: options.packageName });
|
|
435
|
+
return current.scope.fingerprint === plan.decision.scope.fingerprint;
|
|
436
|
+
}
|
|
437
|
+
|
|
430
438
|
function uncoveredScope(plan, tools, options) {
|
|
431
439
|
if (!plan) return [];
|
|
432
440
|
const root = fs.realpathSync(path.resolve(options.projectRoot || process.cwd()));
|
|
@@ -477,7 +485,7 @@ function executionOrder(record) {
|
|
|
477
485
|
function readStatus(options) {
|
|
478
486
|
const records = allRecords(options);
|
|
479
487
|
const runId = options.runId || records.at(-1)?.runId;
|
|
480
|
-
return statusForRecords(records.filter((record) => record.runId === runId), options);
|
|
488
|
+
return { ...statusForRecords(records.filter((record) => record.runId === runId), options), currentScope: projectScope.resolveScope({ projectRoot: options.projectRoot, packageName: options.packageName }).scope };
|
|
481
489
|
}
|
|
482
490
|
|
|
483
491
|
function listGaps(options) {
|
|
@@ -501,6 +509,7 @@ function inspectHostFile(root, relative, maximum) {
|
|
|
501
509
|
|
|
502
510
|
function doctorHost(options) {
|
|
503
511
|
const root = fs.realpathSync(path.resolve(options.projectRoot || process.cwd()));
|
|
512
|
+
const { scope } = projectScope.resolveScope({ projectRoot: root, packageName: options.packageName });
|
|
504
513
|
const maximum = options.maxInstructionBytes || 32768;
|
|
505
514
|
const entries = strings(options.entryFiles).map((relative) => inspectHostFile(root, relative, maximum));
|
|
506
515
|
const skills = strings(options.skillPaths).map((relative) => inspectHostFile(root, relative, Infinity));
|
|
@@ -508,8 +517,8 @@ function doctorHost(options) {
|
|
|
508
517
|
const warnings = entries.filter((entry) => entry.oversized).map((entry) => `${entry.path} 超过 ${maximum} 字节参考预算;请核对宿主实际配置与内容加载范围`);
|
|
509
518
|
const runtime = inspectRuntime(root, options);
|
|
510
519
|
warnings.push(...runtime.warnings);
|
|
511
|
-
const ready = hostReady(gateway, skills, entries, runtime);
|
|
512
|
-
return { recordVersion: RECORD_VERSION, packageName: options.packageName, packageVersion: options.packageVersion, host: options.host || "unspecified", projectRoot: root, entries, skills, gateway, runtime, entryReadiness: ready ? "ready" : "incomplete", hostDiscovery: "unverified", mcpConnection: "unverified", contentLoaded: "unverified", warnings, instructionBudgetSource: "reference-default-not-observed-host-config" };
|
|
520
|
+
const ready = scope.status === "applicable" && hostReady(gateway, skills, entries, runtime);
|
|
521
|
+
return { recordVersion: RECORD_VERSION, packageName: options.packageName, packageVersion: options.packageVersion, host: options.host || "unspecified", projectRoot: root, scope, entries, skills, gateway, runtime, entryReadiness: ready ? "ready" : "incomplete", hostDiscovery: "unverified", mcpConnection: "unverified", contentLoaded: "unverified", warnings, instructionBudgetSource: "reference-default-not-observed-host-config" };
|
|
513
522
|
}
|
|
514
523
|
|
|
515
524
|
function hostReady(gateway, skills, entries, runtime) {
|
|
@@ -551,6 +560,7 @@ function noticeRules(item) {
|
|
|
551
560
|
}
|
|
552
561
|
function noticeScope(record, options, item) {
|
|
553
562
|
return { targets: strings(options.originalTargets || record.targets || options.targets),
|
|
563
|
+
...(item.scope ? { scope: item.scope } : {}),
|
|
554
564
|
executionStatus: record.executionStatus || "not-executed", validationStatus: record.validationStatus || "unverified",
|
|
555
565
|
unverified: strings(item.unverified || ["host-discovery", "model-read-canonical-files", "planned-actions-not-executed"]) };
|
|
556
566
|
}
|
|
@@ -3,44 +3,232 @@
|
|
|
3
3
|
"$id": "urn:wl-skills:task-observability:1",
|
|
4
4
|
"title": "WL independent package task and execution evidence v1",
|
|
5
5
|
"type": "object",
|
|
6
|
-
"required": [
|
|
6
|
+
"required": [
|
|
7
|
+
"recordVersion",
|
|
8
|
+
"kind",
|
|
9
|
+
"packageName",
|
|
10
|
+
"packageVersion",
|
|
11
|
+
"runId",
|
|
12
|
+
"recordedAt",
|
|
13
|
+
"eventId",
|
|
14
|
+
"targets",
|
|
15
|
+
"inputSnapshot",
|
|
16
|
+
"executionStatus",
|
|
17
|
+
"validationStatus"
|
|
18
|
+
],
|
|
7
19
|
"properties": {
|
|
8
|
-
"recordVersion": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
20
|
+
"recordVersion": {
|
|
21
|
+
"const": 1
|
|
22
|
+
},
|
|
23
|
+
"kind": {
|
|
24
|
+
"enum": [
|
|
25
|
+
"task-decision",
|
|
26
|
+
"tool-started",
|
|
27
|
+
"tool-finished"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"packageName": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"pattern": "^@agile-team/wl-skills-(kit|ui|bd|design|test)$"
|
|
33
|
+
},
|
|
34
|
+
"packageVersion": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"minLength": 1
|
|
37
|
+
},
|
|
38
|
+
"runId": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}$"
|
|
41
|
+
},
|
|
42
|
+
"eventId": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"minLength": 1
|
|
45
|
+
},
|
|
46
|
+
"recordedAt": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T"
|
|
49
|
+
},
|
|
50
|
+
"targets": {
|
|
51
|
+
"type": "array",
|
|
52
|
+
"items": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
"uniqueItems": true
|
|
56
|
+
},
|
|
57
|
+
"task": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"tool": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"executionStatus": {
|
|
64
|
+
"enum": [
|
|
65
|
+
"not-executed",
|
|
66
|
+
"started",
|
|
67
|
+
"completed",
|
|
68
|
+
"failed"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"validationStatus": {
|
|
72
|
+
"enum": [
|
|
73
|
+
"passed",
|
|
74
|
+
"failed",
|
|
75
|
+
"partial",
|
|
76
|
+
"unverified",
|
|
77
|
+
"not-applicable"
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"exitCode": {
|
|
81
|
+
"type": "integer"
|
|
82
|
+
},
|
|
83
|
+
"durationMs": {
|
|
84
|
+
"type": "number",
|
|
85
|
+
"minimum": 0
|
|
86
|
+
},
|
|
87
|
+
"inputSnapshot": {
|
|
88
|
+
"$ref": "#/definitions/snapshot"
|
|
89
|
+
},
|
|
90
|
+
"inputBefore": {
|
|
91
|
+
"$ref": "#/definitions/snapshot"
|
|
92
|
+
},
|
|
93
|
+
"ruleSnapshot": {
|
|
94
|
+
"$ref": "#/definitions/snapshot"
|
|
95
|
+
},
|
|
96
|
+
"configSnapshot": {
|
|
97
|
+
"$ref": "#/definitions/snapshot"
|
|
98
|
+
},
|
|
99
|
+
"checkedSnapshot": {
|
|
100
|
+
"$ref": "#/definitions/snapshot"
|
|
101
|
+
},
|
|
102
|
+
"executionId": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"minLength": 1
|
|
105
|
+
},
|
|
106
|
+
"executionStartedAt": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"pattern": "^\\d{4}-\\d{2}-\\d{2}T"
|
|
109
|
+
},
|
|
110
|
+
"decision": {
|
|
111
|
+
"$ref": "#/definitions/decision"
|
|
112
|
+
},
|
|
113
|
+
"checks": {
|
|
114
|
+
"type": "array",
|
|
115
|
+
"items": {
|
|
116
|
+
"$ref": "#/definitions/check"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"checkedFiles": {
|
|
120
|
+
"type": "array",
|
|
121
|
+
"items": {
|
|
122
|
+
"$ref": "#/definitions/file"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"checkedScopeSource": {
|
|
126
|
+
"enum": [
|
|
127
|
+
"executor",
|
|
128
|
+
"unreported"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"summary": {
|
|
132
|
+
"type": "object"
|
|
133
|
+
},
|
|
134
|
+
"artifacts": {
|
|
135
|
+
"type": "array",
|
|
136
|
+
"items": {
|
|
137
|
+
"$ref": "#/definitions/file"
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"changedDuringCheck": {
|
|
141
|
+
"type": "boolean"
|
|
142
|
+
},
|
|
143
|
+
"errorCode": {
|
|
144
|
+
"type": "string"
|
|
145
|
+
},
|
|
146
|
+
"recordPath": {
|
|
147
|
+
"type": [
|
|
148
|
+
"string",
|
|
149
|
+
"null"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
"notice": {
|
|
153
|
+
"$ref": "#/definitions/notice"
|
|
154
|
+
}
|
|
39
155
|
},
|
|
40
156
|
"allOf": [
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
157
|
+
{
|
|
158
|
+
"if": {
|
|
159
|
+
"properties": {
|
|
160
|
+
"kind": {
|
|
161
|
+
"const": "task-decision"
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"then": {
|
|
166
|
+
"required": [
|
|
167
|
+
"task",
|
|
168
|
+
"decision"
|
|
169
|
+
],
|
|
170
|
+
"properties": {
|
|
171
|
+
"executionStatus": {
|
|
172
|
+
"const": "not-executed"
|
|
173
|
+
},
|
|
174
|
+
"validationStatus": {
|
|
175
|
+
"const": "unverified"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
"if": {
|
|
182
|
+
"properties": {
|
|
183
|
+
"kind": {
|
|
184
|
+
"const": "tool-started"
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"then": {
|
|
189
|
+
"required": [
|
|
190
|
+
"tool"
|
|
191
|
+
],
|
|
192
|
+
"properties": {
|
|
193
|
+
"executionStatus": {
|
|
194
|
+
"const": "started"
|
|
195
|
+
},
|
|
196
|
+
"validationStatus": {
|
|
197
|
+
"const": "unverified"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"if": {
|
|
204
|
+
"properties": {
|
|
205
|
+
"kind": {
|
|
206
|
+
"const": "tool-finished"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"then": {
|
|
211
|
+
"required": [
|
|
212
|
+
"tool",
|
|
213
|
+
"executionId",
|
|
214
|
+
"executionStartedAt",
|
|
215
|
+
"exitCode",
|
|
216
|
+
"durationMs",
|
|
217
|
+
"checks",
|
|
218
|
+
"artifacts",
|
|
219
|
+
"inputBefore",
|
|
220
|
+
"changedDuringCheck"
|
|
221
|
+
],
|
|
222
|
+
"properties": {
|
|
223
|
+
"executionStatus": {
|
|
224
|
+
"enum": [
|
|
225
|
+
"completed",
|
|
226
|
+
"failed"
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
44
232
|
],
|
|
45
233
|
"definitions": {
|
|
46
234
|
"notice": {
|
|
@@ -134,38 +322,205 @@
|
|
|
134
322
|
},
|
|
135
323
|
"displayEvidence": {
|
|
136
324
|
"const": "unverified"
|
|
325
|
+
},
|
|
326
|
+
"scope": {
|
|
327
|
+
"$ref": "#/definitions/scope"
|
|
137
328
|
}
|
|
138
329
|
}
|
|
139
330
|
},
|
|
140
331
|
"file": {
|
|
141
|
-
"type": "object",
|
|
142
|
-
"
|
|
332
|
+
"type": "object",
|
|
333
|
+
"required": [
|
|
334
|
+
"path",
|
|
335
|
+
"sha256",
|
|
336
|
+
"bytes"
|
|
337
|
+
],
|
|
338
|
+
"properties": {
|
|
339
|
+
"path": {
|
|
340
|
+
"type": "string"
|
|
341
|
+
},
|
|
342
|
+
"sha256": {
|
|
343
|
+
"type": "string",
|
|
344
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
345
|
+
},
|
|
346
|
+
"bytes": {
|
|
347
|
+
"type": "integer",
|
|
348
|
+
"minimum": 0
|
|
349
|
+
}
|
|
350
|
+
}
|
|
143
351
|
},
|
|
144
352
|
"snapshot": {
|
|
145
|
-
"type": "object",
|
|
353
|
+
"type": "object",
|
|
354
|
+
"required": [
|
|
355
|
+
"targets",
|
|
356
|
+
"files",
|
|
357
|
+
"omitted",
|
|
358
|
+
"complete",
|
|
359
|
+
"sha256"
|
|
360
|
+
],
|
|
146
361
|
"properties": {
|
|
147
|
-
"targets": {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
362
|
+
"targets": {
|
|
363
|
+
"type": "array",
|
|
364
|
+
"items": {
|
|
365
|
+
"type": "string"
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"files": {
|
|
369
|
+
"type": "array",
|
|
370
|
+
"items": {
|
|
371
|
+
"$ref": "#/definitions/file"
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
"omitted": {
|
|
375
|
+
"type": "array",
|
|
376
|
+
"items": {
|
|
377
|
+
"type": "object",
|
|
378
|
+
"required": [
|
|
379
|
+
"path",
|
|
380
|
+
"reason"
|
|
381
|
+
],
|
|
382
|
+
"properties": {
|
|
383
|
+
"path": {
|
|
384
|
+
"type": "string"
|
|
385
|
+
},
|
|
386
|
+
"reason": {
|
|
387
|
+
"type": "string"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
"complete": {
|
|
393
|
+
"type": "boolean"
|
|
394
|
+
},
|
|
395
|
+
"sha256": {
|
|
396
|
+
"type": "string",
|
|
397
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
398
|
+
},
|
|
399
|
+
"resolution": {
|
|
400
|
+
"const": "project-internal-realpath"
|
|
401
|
+
}
|
|
152
402
|
}
|
|
153
403
|
},
|
|
154
404
|
"decision": {
|
|
155
|
-
"type": "object",
|
|
405
|
+
"type": "object",
|
|
406
|
+
"required": [
|
|
407
|
+
"status",
|
|
408
|
+
"selectedSkills",
|
|
409
|
+
"requiredChecks",
|
|
410
|
+
"gaps"
|
|
411
|
+
],
|
|
156
412
|
"properties": {
|
|
157
|
-
"status": {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
413
|
+
"status": {
|
|
414
|
+
"enum": [
|
|
415
|
+
"matched",
|
|
416
|
+
"baseline",
|
|
417
|
+
"ambiguous",
|
|
418
|
+
"gap",
|
|
419
|
+
"not-applicable",
|
|
420
|
+
"needs-context"
|
|
421
|
+
]
|
|
422
|
+
},
|
|
423
|
+
"selectedSkills": {
|
|
424
|
+
"type": "array",
|
|
425
|
+
"items": {
|
|
426
|
+
"type": "string"
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
"requiredChecks": {
|
|
430
|
+
"type": "array",
|
|
431
|
+
"items": {
|
|
432
|
+
"type": "string"
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
"requiredFiles": {
|
|
436
|
+
"type": "array",
|
|
437
|
+
"items": {
|
|
438
|
+
"type": "string"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
"gaps": {
|
|
442
|
+
"type": "array",
|
|
443
|
+
"items": {
|
|
444
|
+
"type": "object"
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
"scope": {
|
|
448
|
+
"$ref": "#/definitions/scope"
|
|
449
|
+
}
|
|
162
450
|
}
|
|
163
451
|
},
|
|
164
452
|
"check": {
|
|
165
|
-
"type": "object",
|
|
453
|
+
"type": "object",
|
|
454
|
+
"required": [
|
|
455
|
+
"id",
|
|
456
|
+
"status",
|
|
457
|
+
"reason",
|
|
458
|
+
"evidencePaths"
|
|
459
|
+
],
|
|
166
460
|
"properties": {
|
|
167
|
-
"id": {
|
|
168
|
-
|
|
461
|
+
"id": {
|
|
462
|
+
"type": "string"
|
|
463
|
+
},
|
|
464
|
+
"status": {
|
|
465
|
+
"enum": [
|
|
466
|
+
"passed",
|
|
467
|
+
"failed",
|
|
468
|
+
"skipped",
|
|
469
|
+
"not-applicable",
|
|
470
|
+
"unverified",
|
|
471
|
+
"not-executed"
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
"reason": {
|
|
475
|
+
"type": "string"
|
|
476
|
+
},
|
|
477
|
+
"evidencePaths": {
|
|
478
|
+
"type": "array",
|
|
479
|
+
"items": {
|
|
480
|
+
"type": "string"
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
"scope": {
|
|
486
|
+
"type": "object",
|
|
487
|
+
"required": [
|
|
488
|
+
"schemaVersion",
|
|
489
|
+
"status",
|
|
490
|
+
"reason",
|
|
491
|
+
"adopted",
|
|
492
|
+
"inherited"
|
|
493
|
+
],
|
|
494
|
+
"properties": {
|
|
495
|
+
"schemaVersion": {
|
|
496
|
+
"const": 1
|
|
497
|
+
},
|
|
498
|
+
"status": {
|
|
499
|
+
"enum": [
|
|
500
|
+
"applicable",
|
|
501
|
+
"not-applicable",
|
|
502
|
+
"needs-context"
|
|
503
|
+
]
|
|
504
|
+
},
|
|
505
|
+
"adopted": {
|
|
506
|
+
"type": "boolean"
|
|
507
|
+
},
|
|
508
|
+
"inherited": {
|
|
509
|
+
"const": false
|
|
510
|
+
},
|
|
511
|
+
"reason": {
|
|
512
|
+
"type": "string"
|
|
513
|
+
},
|
|
514
|
+
"projectType": {
|
|
515
|
+
"type": "string"
|
|
516
|
+
},
|
|
517
|
+
"projectRoot": {
|
|
518
|
+
"type": "string"
|
|
519
|
+
},
|
|
520
|
+
"fingerprint": {
|
|
521
|
+
"type": "string",
|
|
522
|
+
"pattern": "^[a-f0-9]{64}$"
|
|
523
|
+
}
|
|
169
524
|
}
|
|
170
525
|
}
|
|
171
526
|
}
|
package/bin/wl-ui.js
CHANGED
|
@@ -564,7 +564,7 @@ function transformForEditor(content, editor) {
|
|
|
564
564
|
if (!headerTemplate) return body;
|
|
565
565
|
|
|
566
566
|
const header = headerTemplate
|
|
567
|
-
.replace("{SKILL_DESCRIPTION}", description.split("\n")[0])
|
|
567
|
+
.replace("{SKILL_DESCRIPTION}", `仅用于自身已接入UI包的PC Vue管理端,排除移动端和未接入项目。${description.split("\n")[0]}`)
|
|
568
568
|
.replace("{APPLY_GLOB}", applyTo)
|
|
569
569
|
.replace(
|
|
570
570
|
"{SKILL_NAME}",
|
|
@@ -574,7 +574,7 @@ function transformForEditor(content, editor) {
|
|
|
574
574
|
?.slice(2) || "Skill",
|
|
575
575
|
);
|
|
576
576
|
|
|
577
|
-
return header + "\nStart each task with local `wl-ui task \"<task>\" --target <path>`. Before editing visibly show the returned notice: actual package/version, decision, Skill or baseline, rule IDs/names, targets, runId and unexecuted checks. Report local command failures/version drift. Read only the selected Skill. Finish actual tools with the same `--run-id` and report `wl-ui status`; model declarations do not prove verification.\n\n" + body;
|
|
577
|
+
return header + "\nOnly apply these rules inside a PC Vue admin project with its own UI manifest/direct dependency or explicit scope opt-in. Parent installations and Vue files do not adopt a project; mobile and unadopted projects receive no UI rules.\n\nStart each task with local `wl-ui task \"<task>\" --target <path>`. Before editing visibly show the returned notice: actual package/version, decision, Skill or baseline, rule IDs/names, targets, runId and unexecuted checks. Report local command failures/version drift. Read only the selected Skill. Finish actual tools with the same `--run-id` and report `wl-ui status`; model declarations do not prove verification.\n\n" + body;
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
function selectedSkills(mode, profile) {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 项目范围与精准触发
|
|
2
|
+
|
|
3
|
+
本包只应用于目标项目自身已接入、平台适用且任务属于本包职责的场景。父目录、全局安装、兄弟包、Vue文件和触发词都不能证明目标项目已接入。项目初始化与任务执行是不同阶段;已安装文件也不能证明宿主加载或检查通过。
|
|
4
|
+
|
|
5
|
+
## 判定顺序
|
|
6
|
+
|
|
7
|
+
1. 从目标文件确定最近项目边界:package.json、独立Git仓库、安装清单或范围配置。父清单不能越过未接入子项目。同一Java工程的Maven模块共享工程接入,独立嵌套仓库仍单独判断。
|
|
8
|
+
2. 排除移动端:UniApp/Taro/React Native/Expo/Ionic/Capacitor依赖、pages.json+manifest.json,或显式mobile配置。不靠“页面”“Vue”或仓库名字猜平台。
|
|
9
|
+
3. 检查本包自身接入:有效的本包安装清单、package.json直接依赖(含dev/optional),或范围文件显式启用。node_modules里的传递/提升依赖不作为接入证据。
|
|
10
|
+
4. 才进行既有技能、基础约束、歧义和缺口判断。每包独立执行此步骤,不要求兄弟包或harness。Kit/UI仍只支持PC Vue管理端;BD保留后端职责,design/test保留各自设计/测试职责。
|
|
11
|
+
|
|
12
|
+
| 情况 | 回执与行为 |
|
|
13
|
+
|---|---|
|
|
14
|
+
| PC开源项目未接入本包 | not-applicable,无规则/技能、无任务写入,不自动建议安装 |
|
|
15
|
+
| 父工作区已安装,子项目未接入 | not-applicable,不继承父安装 |
|
|
16
|
+
| 已接入但自己的canonical资产缺失 | gap,说明本包资产缺口 |
|
|
17
|
+
| 已接入的移动端 | not-applicable,移动事实优先于pc配置 |
|
|
18
|
+
| 多个目标跨独立项目 | needs-context,按项目拆分,复用用户任务runId,零写入 |
|
|
19
|
+
| 范围元数据无效、projectType=unknown | needs-context,零写入,不猜测 |
|
|
20
|
+
| 工具与项目版本不一致 | doctor-host显示mismatch,先正常升级对齐 |
|
|
21
|
+
|
|
22
|
+
## 可选显式配置
|
|
23
|
+
|
|
24
|
+
在实际项目根创建用户维护的 `.wl-skills-scope.json`;安装器不覆盖或登记该用户文件。它不需要共享包运行依赖。已有有效安装/直接依赖无需额外配置;平台不能可靠推断时(尤其纯Vue移动H5),应明确声明。
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"schemaVersion": 1,
|
|
29
|
+
"projectType": "pc",
|
|
30
|
+
"packages": { "ui": true }
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
projectType可为pc/backend/documents/testing/mobile/workspace/unknown。packages中的布尔false禁用对应包,true表示明确接入;未列出的包只根据各自清单/直接依赖判断,不自动启用。projectType本身不是接入证明。mobile排除这套项目约束;workspace只作聚合入口,指定已接入子项目才继续。父配置不跨项目继承。严格Schema随本包 `bin/project-scope.schema.json` 分发,protocol describe同时公布此契约。
|
|
35
|
+
|
|
36
|
+
工作区根可声明 `{"schemaVersion":1,"projectType":"workspace"}`;未接入的开源项目无需新增任何文件。移动H5可声明 `{"schemaVersion":1,"projectType":"mobile"}`,其现有组件、构建、目录及测试约定仍由自身项目维护。
|
|
37
|
+
|
|
38
|
+
## 使用建议与证据
|
|
39
|
+
|
|
40
|
+
把依赖与init/update放在实际项目根,进入最近项目AGENTS和原生gateway;编辑前显示工具notice里的实际包版本、scope证据、判定、规则来源、目标、runId和待检查项。使用同runId执行真实校验并读status;范围/接入配置变化使旧计划过期,currentScope反映当前范围。
|
|
41
|
+
|
|
42
|
+
包提供确定性判定和真实工具记录,宿主负责发现入口、调用工具与聊天展示。只npm安装可执行器而未初始化资产,会出现明确gap;只分发资产但缺可执行器,也不能宣称工具执行。Markdown不能强制所有宿主/模型调用,不保证任意自然语言100%匹配。不要为“没触发”自动安装包或扩大PC规则范围;相关能力缺口应先记录复现、审阅后完善。
|
|
43
|
+
|
|
44
|
+
本轮范围护栏保护task/route/explain及其CLI/MCP/公开protocol入口。用户明确调用的独立业务CLI仍遵循原授权与检查契约;护栏不代表任意外部命令的强制沙箱。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/wl-skills-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
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",
|