@agile-team/wl-skills-ui 1.18.0 → 1.19.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.19.0] - 2026-10-11
4
+
5
+ - 区分当前请求、否定动作与历史引用,增加动作模式;解释/规划不自动检查,意图不作为业务写入授权。
6
+ - 公开协议保持 v1 和原有字段,增加带 Schema 的 result.integration、规则身份与包自有只读执行器目录。
7
+ - 编辑器 gateway 展示真实动作与规则回执;补充独立包语料、统一契约和执行边界回归,无兄弟包/Harness 依赖。
8
+ - 触发词迁至独立目录并双向核验,明确 scan 样式检查与 check 接入检查的职责。
9
+
10
+
3
11
  ## [1.18.0] - 2026-10-11
4
12
 
5
13
  - 任务路由前校验目标项目自身接入与平台;未接入/移动端零规则零任务写入,不继承父安装。
package/README.md CHANGED
@@ -510,7 +510,7 @@ npx wl-ui doctor --project . --print-overrides
510
510
 
511
511
  ## 版本与进一步阅读
512
512
 
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 依赖识别。
513
+ 当前 v1.19.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 依赖识别。
514
514
 
515
515
  - [版本摘要](docs/version-history.md)
516
516
  - [完整 Changelog](CHANGELOG.md)
@@ -532,3 +532,15 @@ npm run pack:dry
532
532
  ```
533
533
 
534
534
  视觉测试基线按操作系统管理;如果本机没有对应平台截图,完整 `test:visual` 会明确报告缺失,不应自动生成并提交一套未经评审的新基线。发布门禁使用跨浏览器计算样式契约 `test:visual:contracts`;真实浏览器兼容契约也可以单独执行 `browser-compat.spec.ts`。
535
+
536
+ ### 当前请求、动作边界与 Harness 集成
537
+
538
+ 任务判定保留原始文本,并将当前请求、历史引用和明确否定分开。自然语言判定覆盖已公开语料与规则,不能承诺任意表述都自动命中;遇到歧义或缺口应补充目标/领域或显式指定本包能力,不能静默选择。编辑前显示真实 `notice`,包括版本、技能、规则、目标、runId、动作与待检查项。
539
+
540
+ `protocol describe --json` 声明 `task-intent-v1`、`result-core-v1`、`executor-catalog-v1`;`schemas.resultCore` 与五操作 `schemas.results` 可供宿主验证。原有结果字段保留,新增 `result.integration`:项目适用范围、动作模式、规则身份、目标、就绪状态、允许执行器和真实执行/验证状态。`ok` 只表示调用成功。未声明使用的输入字段进入 diagnostics,不会转成授权依据。
541
+
542
+ `action.mode` 为 explain/plan/inspect/apply/unspecified;解释和规划不自动启动检查。`businessWritesAuthorized=false`,任务意图不能替代业务写入授权。`ruleRefs` 为包自有规则身份(包名、规则 ID、版本、来源、性质),自然语言约束有稳定 ID;原规则名称与列表保留。所有规则的机械/语义验证仍以实际回执为准。
543
+
544
+ `inventory.executors` 声明本包业务只读检查器、目标与参数 Schema;其适用条件由本包判定。外部适配器须核对同项目、同包、同版本、同 runId 的真实计划、新鲜度与允许动作,指定一个目标,不能扩大范围;支持规则子集的执行器不得选择计划外规则。多目标需拆分调用。五包和 Harness 均为可选组合,不新增兄弟包或 Harness 运行时依赖;卸下适配器后原 CLI/MCP 继续独立使用。
545
+
546
+ 样式规则检查使用 `scan`(支持计划内规则子集);`check` 只核验接入完整性。技能触发词由本包 `bin/task-triggers.json` 维护并接受目录一致性检查。
@@ -0,0 +1,34 @@
1
+ [
2
+ {
3
+ "task": "旧需求写过“UI迁移迁移tokens”,本次只审查表单尺寸",
4
+ "status": "matched",
5
+ "skills": [
6
+ "element/el-form"
7
+ ],
8
+ "mode": "inspect"
9
+ },
10
+ {
11
+ "task": "不要迁移tokens,只检查现有表单样式",
12
+ "status": "matched",
13
+ "skills": [
14
+ "element/el-form"
15
+ ],
16
+ "mode": "inspect"
17
+ },
18
+ {
19
+ "task": "只说明 UI修复 的影响,不执行修改",
20
+ "status": "matched",
21
+ "skills": [
22
+ "ops/fix"
23
+ ],
24
+ "mode": "explain"
25
+ },
26
+ {
27
+ "task": "迁移tokens",
28
+ "status": "matched",
29
+ "skills": [
30
+ "ops/migrate"
31
+ ],
32
+ "mode": "apply"
33
+ }
34
+ ]
@@ -0,0 +1,41 @@
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
+ const dirname = import.meta.dirname;
8
+ const repo = path.resolve(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 cases = JSON.parse(fs.readFileSync(path.join(dirname, 'task-intent-cases.json'), 'utf8'));
12
+ const key = "ui";
13
+ test('当前请求、否定、历史引用与只讨论:公开协议精确回归', () => {
14
+ const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'wl-intent-ui-'));
15
+ const project = path.join(temporary, 'project');fs.mkdirSync(project);
16
+ const run = args => {
17
+ const result = spawnSync(process.execPath,[cli,...args],{cwd:project,encoding:'utf8',timeout:30000});
18
+ assert.equal(result.status,0,result.stderr || result.stdout);return result.stdout;
19
+ };
20
+ try {
21
+ fs.writeFileSync(path.join(project,'package.json'),JSON.stringify({private:true,devDependencies:{[pkg.name]:'*'}}));
22
+ const scoped = path.join(project,'node_modules/@agile-team');fs.mkdirSync(scoped,{recursive:true});fs.symlinkSync(repo,path.join(scoped,'wl-skills-ui'),'dir');
23
+ run(['init','--project',project,'--editor','agents-generic','--profile','native-element','--skills-only']);
24
+ for(const row of cases){
25
+ if(row.javaProject)fs.writeFileSync(path.join(project,'pom.xml'),'<project><modelVersion>4.0.0</modelVersion><groupId>audit</groupId><artifactId>fixture</artifactId><version>1</version></project>');
26
+ const file = path.join(temporary,'request.json');
27
+ fs.writeFileSync(file,JSON.stringify({operation:'route',projectRoot:project,task:row.task,...(row.domain ? {domain:row.domain} : {})}));
28
+ const envelope = JSON.parse(run(['protocol','request','--input-file',file,'--json']));
29
+ assert.equal(envelope.ok,true,row.task);const d = envelope.result.decision || envelope.result;
30
+ assert.equal(d.status,row.status,row.task);assert.deepEqual(d.selectedSkills,row.skills,row.task);
31
+ if(row.rules)assert.deepEqual(d.requiredRules,row.rules,row.task);
32
+ assert.equal(d.action.mode,row.mode,row.task);assert.equal(d.action.businessWritesAuthorized,false);
33
+ if(row.mode === 'explain')assert.equal(d.action.checksAllowed,false);
34
+ const core = envelope.result.integration;assert.equal(core.packageName,pkg.name);assert.equal(core.packageVersion,pkg.version);
35
+ assert.equal(core.contractVersion,1);assert.equal(core.runId,null);
36
+ assert.ok(core.ruleRefs.every(rule => rule.packageName === pkg.name && rule.ruleVersion === pkg.version));
37
+ }
38
+ const storage = {kit:'.wl-skills',ui:'.wl-skills-ui',bd:'.wl-skills-bd',design:'.wl-skills-design',test:'.wl-skills-test'}[key];
39
+ assert.equal(fs.existsSync(path.join(project,storage,'runs')),false,'route 不写任务记录');
40
+ } finally {fs.rmSync(temporary,{recursive:true,force:true});}
41
+ });
@@ -0,0 +1,7 @@
1
+ import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';
2
+ import {taskCatalog} from '../task-integration.mjs';
3
+ test('触发词清单与本包技能目录双向完整且不含空词',() => {
4
+ const triggers = JSON.parse(fs.readFileSync(new URL('../task-triggers.json',import.meta.url),'utf8'));const catalog = taskCatalog();
5
+ assert.deepEqual(Object.keys(triggers).sort(),catalog.map(s => s.id).sort());
6
+ for(const skill of catalog){assert.ok(skill.triggers.length,skill.id);assert.ok(skill.triggers.every(p => typeof p === 'string' && p.trim()));assert.deepEqual(skill.triggers,triggers[skill.id]);}
7
+ });
@@ -0,0 +1,48 @@
1
+ [
2
+ {
3
+ "id": "scan",
4
+ "command": [
5
+ "scan"
6
+ ],
7
+ "when": {
8
+ "checks": [
9
+ "scan"
10
+ ]
11
+ },
12
+ "parameters": {
13
+ "type": "object",
14
+ "required": [
15
+ "targets"
16
+ ],
17
+ "properties": {
18
+ "targets": {
19
+ "type": "array",
20
+ "items": {
21
+ "type": "string",
22
+ "minLength": 1,
23
+ "pattern": "\\S"
24
+ },
25
+ "minItems": 1,
26
+ "maxItems": 1
27
+ },
28
+ "rules": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "string",
32
+ "pattern": "^R[0-9]{3}$"
33
+ },
34
+ "minItems": 1,
35
+ "uniqueItems": true
36
+ }
37
+ },
38
+ "additionalProperties": false
39
+ },
40
+ "sideEffects": {
41
+ "business": "none",
42
+ "records": true,
43
+ "reports": "stdout"
44
+ },
45
+ "authorization": "read-only-business-check",
46
+ "receipt": "package-execution-record-v1"
47
+ }
48
+ ]
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  const PROTOCOL_VERSION = 1;
19
+ const resultCore = require("./integration-result.cjs");
19
20
  const SUPPORTED_VERSIONS = [1];
20
21
  const ERROR_CODES = ["unsupported-protocol", "unknown-operation", "missing-input", "invalid-input", "internal-error"];
21
22
  const OPTIONAL_STRING_FIELDS = ["requestId", "runId", "projectRoot", "skill", "host", "type", "domain", "profile"];
@@ -233,6 +234,7 @@ function validateFieldTypes(input) {
233
234
  commands: inventory.commands || [],
234
235
  mcpTools: inventory.mcpTools || [],
235
236
  loadErrors: inventory.loadErrors || [],
237
+ executors: config.executors || [],
236
238
  note: "业务执行仍走各包原 CLI/MCP;inventory 只声明公开调用映射与入口,不改变执行归属;loadErrors 非空表示目录派生存在异常,不得忽略",
237
239
  };
238
240
  }
@@ -255,9 +257,11 @@ function validateFieldTypes(input) {
255
257
  return {
256
258
  ...base(null, null),
257
259
  capabilities: config.capabilities || [],
260
+ features: ["task-intent-v1", "result-core-v1", "executor-catalog-v1"],
258
261
  operations: config.operations.map((operation) => ({ ...operation })),
259
262
  inventory: buildInventory(config),
260
- schemas: { request: requestSchema(config), envelope: envelopeSchema(config.packageName) },
263
+ schemas: { request: requestSchema(config), envelope: envelopeSchema(config.packageName), resultCore: resultCore.SCHEMA,
264
+ results: Object.fromEntries(config.operations.map((operation) => [operation.id, { type: "object", required: ["integration"], properties: { integration: resultCore.SCHEMA }, additionalProperties: true }])) },
261
265
  constraints: buildConstraints(config),
262
266
  errorCodes: [...ERROR_CODES],
263
267
  };
@@ -267,8 +271,13 @@ function validateFieldTypes(input) {
267
271
  const { operation, error } = validateRequest(input);
268
272
  if (error) return error;
269
273
  const diagnostics = [];
274
+ const supportedFields = new Set(["operation", "protocolVersion", "requestId", ...operation.required, ...(operation.requireAny || []), ...operation.optional]);
275
+ for (const field of Object.keys(input)) {
276
+ if (!supportedFields.has(field)) diagnostics.push(`操作 ${operation.id} 未声明使用字段 ${field};该字段不构成执行或授权依据`);
277
+ }
270
278
  try {
271
- const result = runOperation(operation.id, input, diagnostics) ?? {};
279
+ const original = runOperation(operation.id, input, diagnostics) ?? {};
280
+ const result = resultCore.projectResult(original, input, config);
272
281
  return { ...base(operation.id, input.requestId), ok: true, result, diagnostics };
273
282
  } catch (thrown) {
274
283
  diagnostics.push(String((thrown && thrown.message) || thrown));
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+
3
+ // Additive, typed core: original package-specific result fields are preserved.
4
+ const path = require("node:path");
5
+ const DECISIONS = ["matched", "baseline", "gap", "ambiguous", "not-applicable", "needs-context"];
6
+ const strings = (items) => Array.isArray(items) ? items.filter((value) => typeof value === "string") : [];
7
+ const nullableString = { type: ["string", "null"] };
8
+ const SCHEMA = {
9
+ type: "object", additionalProperties: false,
10
+ required: ["contractVersion", "packageName", "packageVersion", "projectRoot", "runId", "decisionStatus", "scope", "action", "selectedSkills", "ruleRefs", "requiredChecks", "targets", "ready", "executionStatus", "validationStatus", "stale", "planStale", "allowedExecutors"],
11
+ properties: {
12
+ contractVersion: { const: 1 }, packageName: { type: "string", minLength: 1 }, packageVersion: { type: "string", minLength: 1 },
13
+ projectRoot: { type: "string", minLength: 1 }, runId: nullableString,
14
+ decisionStatus: { enum: [...DECISIONS, null] }, scope: { oneOf: [{type:"null"}, {type:"object",required:["schemaVersion","status","reason","adopted","projectType","inherited"],
15
+ properties:{schemaVersion:{const:1},status:{enum:["applicable","not-applicable","needs-context"]},reason:{type:"string"},adopted:{type:"boolean"},projectType:{type:"string"},inherited:{const:false}},additionalProperties:true}] },
16
+ action: { oneOf: [{ type: "null" }, { type: "object", required: ["mode", "checksAllowed", "businessWritesAuthorized", "authorization"],
17
+ properties: { mode: { enum: ["explain", "inspect", "plan", "apply", "unspecified"] }, checksAllowed: { type: "boolean" }, businessWritesAuthorized: { const: false }, authorization: { type: "string" } }, additionalProperties: false }] },
18
+ selectedSkills: { type: "array", items: { type: "string" } }, requiredChecks: { type: "array", items: { type: "string" } },
19
+ targets: { type: "array", items: { type: "string" } }, allowedExecutors: { type: "array", items: { type: "string" } },
20
+ ruleRefs: { type: "array", items: { type: "object", required: ["packageName", "ruleId", "legacyId", "name", "ruleVersion", "source", "kind", "verification"],
21
+ properties: { packageName: nullableString, ruleId: { type: "string" }, legacyId: { type: "string" }, name: { type: "string" }, ruleVersion: nullableString, source: nullableString, kind: { type: "string" }, verification: { const: "unverified" } }, additionalProperties: false } },
22
+ ready: { type: ["boolean", "null"] }, executionStatus: nullableString, validationStatus: nullableString,
23
+ stale: { type: ["boolean", "null"] }, planStale: { type: ["boolean", "null"] },
24
+ },
25
+ };
26
+
27
+ function allowedExecutors(decision, executors) {
28
+ if (!["matched", "baseline"].includes(decision.status) || !decision.action?.checksAllowed) return [];
29
+ return executors.filter((executor) => {
30
+ const when = executor.when || {};
31
+ return strings(when.skills).some((id) => decision.selectedSkills?.includes(id))
32
+ || strings(when.checks).some((id) => decision.requiredChecks?.includes(id))
33
+ || strings(when.rulePrefixes).some((prefix) => decision.requiredRules?.some((id) => id.startsWith(prefix)));
34
+ }).map((executor) => executor.id);
35
+ }
36
+
37
+ function resultReadiness(result, decision, status) {
38
+ if (typeof result.ready === "boolean") return result.ready;
39
+ if (typeof decision.ready === "boolean") return decision.ready;
40
+ return status ? ["matched", "baseline"].includes(status) && !(decision.missingInputs || []).length : null;
41
+ }
42
+
43
+ function executionState(result) {
44
+ return {
45
+ runId: result.runId || null,
46
+ executionStatus: result.executionStatus || null,
47
+ validationStatus: result.validationStatus || null,
48
+ stale: typeof result.stale === "boolean" ? result.stale : null,
49
+ planStale: typeof result.planStale === "boolean" ? result.planStale : null,
50
+ };
51
+ }
52
+
53
+ function decisionReadout(decision) {
54
+ return {action:decision.action || null, selectedSkills:strings(decision.selectedSkills), ruleRefs:decision.ruleRefs || [], requiredChecks:strings(decision.requiredChecks)};
55
+ }
56
+ function targetReadout(result,input) { return strings(result.targets || input.targets || result.notice?.targets); }
57
+ function projectIdentity(scope,result,input) { return path.resolve(scope?.projectRoot || result.projectRoot || result.root || input.projectRoot || process.cwd()); }
58
+
59
+ function projectResult(result, input, config) {
60
+ const decision = result.decision || result;
61
+ const scope = result.currentScope || decision.scope || result.scope || null;
62
+ const status = DECISIONS.includes(decision.status) ? decision.status : null;
63
+ const ready = resultReadiness(result, decision, status);
64
+ return {
65
+ ...result,
66
+ integration: {
67
+ contractVersion: 1, packageName: config.packageName, packageVersion: config.packageVersion,
68
+ projectRoot: projectIdentity(scope,result,input),
69
+ ...executionState(result), decisionStatus: status, scope,
70
+ ...decisionReadout(decision), targets: targetReadout(result,input), ready,
71
+ allowedExecutors: ready && scope?.status === "applicable" ? allowedExecutors(decision, config.executors || []) : [],
72
+ },
73
+ };
74
+ }
75
+
76
+ module.exports = { SCHEMA, projectResult };
@@ -16,9 +16,9 @@ const { createProtocol } = require("./integration-protocol.cjs");
16
16
 
17
17
  const BIN = "wl-ui";
18
18
  const OPERATIONS = [
19
- { id: "route", summary: "只读任务判定:视觉/样式域技能、约束、歧义与缺口", readOnly: true, required: ["task"], optional: ["targets", "profile", "skill", "projectRoot"], sideEffects: "无写入", mapping: `${BIN} route --text "<task>"` },
20
- { id: "explain", summary: "解释本次任务判定(只读,不记录)", readOnly: true, required: ["task"], optional: ["targets", "projectRoot"], sideEffects: "无写入", mapping: `${BIN} explain --text "<task>"` },
21
- { id: "task", summary: "判定并持久化任务计划(尚未执行扫描或修复)", readOnly: false, required: ["task"], optional: ["runId", "targets", "profile", "projectRoot"], sideEffects: "写入 .wl-skills-ui/runs/ 下本包任务记录", mapping: `${BIN} task --text "<task>" [--run-id <id>]` },
19
+ { id: "route", summary: "只读任务判定:视觉/样式域技能、约束、歧义与缺口", readOnly: true, required: ["task"], optional: ["context", "targets", "profile", "skill", "projectRoot"], sideEffects: "无写入", mapping: `${BIN} route --text "<task>"` },
20
+ { id: "explain", summary: "解释本次任务判定(只读,不记录)", readOnly: true, required: ["task"], optional: ["context", "targets", "projectRoot"], sideEffects: "无写入", mapping: `${BIN} explain --text "<task>"` },
21
+ { id: "task", summary: "判定并持久化任务计划(尚未执行扫描或修复)", readOnly: false, required: ["task"], optional: ["context", "runId", "targets", "profile", "projectRoot"], sideEffects: "写入 .wl-skills-ui/runs/ 下本包任务记录", mapping: `${BIN} task --text "<task>" [--run-id <id>]` },
22
22
  { id: "status", summary: "读取本包执行/校验记录与新鲜度", readOnly: true, required: [], optional: ["runId", "projectRoot"], sideEffects: "无写入", mapping: `${BIN} status [--run-id <id>]` },
23
23
  { id: "doctor-host", summary: "宿主入口静态诊断(不证明宿主已加载)", readOnly: true, required: [], optional: ["host", "projectRoot"], sideEffects: "无写入", mapping: `${BIN} doctor-host [--host <host>]` },
24
24
  ];
@@ -31,7 +31,7 @@ function buildInventory() {
31
31
  { name: "clean", args: "[--project <path>] [--dry-run]", summary: "清理本包安装文件", execution: "programmatic", sideEffects: "删除本包登记文件" },
32
32
  { name: "scan", args: "[--project <path>] [--profile <id>]", summary: "R 规则扫描", execution: "programmatic", sideEffects: "无(报告输出)" },
33
33
  { name: "audit", args: "[--project <path>]", summary: "审计汇总", execution: "programmatic", sideEffects: "无" },
34
- { name: "check", args: "[--project <path>]", summary: "扫描+回执记录", execution: "programmatic", sideEffects: "写 .wl-skills-ui/runs/" },
34
+ { name: "check", args: "[--project <path>]", summary: "接入完整性检查+回执记录(样式规则使用 scan)", execution: "programmatic", sideEffects: "写 .wl-skills-ui/runs/" },
35
35
  { name: "fix", args: "--run-id <id> --rule <id> [--confirm]", summary: "确定性修复(快照/回滚)", execution: "programmatic", sideEffects: "写源文件(确认制+快照回滚)" },
36
36
  { name: "snapshot", args: "/ drift / exempt", summary: "漂移基线管理", execution: "programmatic", sideEffects: "写基线文件" },
37
37
  { name: "contract", args: "<extract|validate|match>", summary: "ui-contract 提取/校验/匹配", execution: "programmatic", sideEffects: "extract 写契约文件" },
@@ -82,6 +82,7 @@ export const protocol = createProtocol({
82
82
  capabilities: capabilitiesDocument.capabilities || [],
83
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
+ executors: require("./executors.json"),
85
86
  inventory: buildInventory(),
86
87
  });
87
88
 
@@ -11,6 +11,8 @@ For one user task reuse the same \`--run-id\` or \`WL_TASK_RUN_ID\` across insta
11
11
 
12
12
  先确认目标项目自身的接入证据(本包安装清单、直接依赖或 \`.wl-skills-scope.json\` 显式启用),再判定任务。只在目标已接入且平台适用时读取规则;父目录安装、兄弟包、Vue 文件或触发词不能证明接入。未接入的开源项目不套用规则、不建议自动安装;UniApp/小程序/App/PDA/移动 H5 不适用。跨项目目标分别判定,沿最近项目边界停止,不能越过未接入子项目找父清单。工具返回 \`scope\` 作为静态范围证据,宿主加载仍需实际事件。
13
13
 
14
+ 动作边界:显示真实回执中的 \`action.mode\`、\`checksAllowed\` 和 \`ruleRefs\`。历史引用与明确禁止的动作不参与技能评分。只解释或只列计划时可以读取相关规范,不能因为命中了生成/修复技能就自动检查或改写业务文件。检查许可仅表示任务动作;业务写入仍遵循用户授权和本包原有流程。非适用项目不加载规范。
15
+
14
16
  多项目工作区先沿目标路径向上找到本包安装清单与项目 AGENTS,切到该项目根再调用工具;不要以聚合工作区根代替 projectRoot,也不要在聚合根安装。不同项目分别保存项目身份,同一用户任务复用 runId。
15
17
 
16
18
  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.
@@ -3,6 +3,7 @@ import { join, relative, resolve, isAbsolute } from "node:path";
3
3
  import { parseArgs } from "node:util";
4
4
  import core from "./task-observability.cjs";
5
5
  import projectScope from "./project-scope.cjs";
6
+ import taskIntent from "./task-intent.cjs";
6
7
  import { GATEWAY_PATH } from "./task-gateway.mjs";
7
8
  import { resolveProjectProfile } from "../standards/profiles-loader.mjs";
8
9
  import { getRules } from "../scanner/rules/index.mjs";
@@ -15,18 +16,7 @@ const POLICY = {
15
16
  baselineRules: ["R001", "R006", "R043"], baselineChecks: ["scan"], minimumScore: 1, minimumMargin: 1,
16
17
  unsupportedIntents: ["生成图像", "生成图片", "Figma设计稿导出"],
17
18
  };
18
- const TRIGGERS = {
19
- "element/el-table": ["表格", "table", "el-table"], "element/el-form": ["表单", "查询表单", "el-form"],
20
- "element/el-dialog": ["弹窗", "dialog", "el-dialog"], "element/el-tag": ["标签颜色", "状态标签", "el-tag"],
21
- "element/component-family": ["卡片", "页签", "抽屉", "el-tabs", "上传控件"],
22
- "runtime/design-tokens": ["设计令牌", "design tokens", "颜色硬编码", "tokens"],
23
- "runtime/style-align": ["样式统一", "风格对齐", "样式不生效"], "runtime/migration": ["runtime迁移", "样式迁移"],
24
- "ops/scan": ["UI扫描", "UI检查", "样式扫描"], "ops/audit": ["UI审计", "视觉审计"],
25
- "ops/fix": ["UI修复", "修复样式", "统一视觉"], "ops/migrate": ["UI迁移", "迁移tokens"],
26
- "layouts/list-page": ["列表布局"], "layouts/detail-page": ["详情布局"], "layouts/form-dialog": ["表单弹窗布局"], "layouts/tree-list": ["树列表布局"],
27
- "vendors/ag-grid": ["AG Grid", "ag-grid"], "vendors/base-table": ["BaseTable"], "vendors/jh-components": ["jh组件"],
28
- "vendors/c-components": ["C组件"], "vendors/custom-wrappers": ["自定义封装组件"], "vendors/unknown-wrapper": ["未知封装组件"],
29
- };
19
+ const TRIGGERS = JSON.parse(readFileSync(join(root, "bin/task-triggers.json"), "utf8"));
30
20
 
31
21
  function catalogFiles(dir, result = []) {
32
22
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -62,7 +52,7 @@ export function decideTask(input) {
62
52
  const catalog = taskCatalog();
63
53
  const decision = core.evaluateTask({ ...input, catalog, policy: POLICY });
64
54
  if (input.skill && !catalog.some((skill) => skill.id === input.skill)) return capabilityGap(decision, `本包未发布专项 Skill:${input.skill}`);
65
- if (POLICY.unsupportedIntents.some((intent) => String(input.task).includes(intent))) return capabilityGap(decision, "本包没有图像生成或设计稿导出的执行器");
55
+ if (POLICY.unsupportedIntents.some((intent) => taskIntent.phraseMatches(input.task, intent))) return capabilityGap(decision, "本包没有图像生成或设计稿导出的执行器");
66
56
  return decision;
67
57
  }
68
58
 
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Dependency-free task evidence snapshot. Domain routing remains in each package.
5
+ * Preserve the prompt; exclude explicit prohibitions and historical clauses from
6
+ * capability scoring. Intent never substitutes for business write authorization.
7
+ */
8
+ const HISTORY = /(?:旧(?:需求|文档|任务|方案)|历史(?:需求|任务|记录)|上(?:次|一轮|轮)(?:需求|任务|要求|说)|(?:用户|客户|之前|以前).{0,8}(?:曾|原先)(?:说|要求|提)|曾(?:说|要求|提到)|previous(?:\s+(?:request|task))?|previously|used to|historical)/iu;
9
+ const CURRENT = /(?:本次|这次|此次|现在|当前任务|实际要求|改为|而是|this time|now|instead)/iu;
10
+ const NEGATIVE = /^(?:(?:请|本次|这次|此次|暂时|暂|先|也|并且|且|同时|还)\s*)*(?:不要|不用|无需|禁止|别|不再|不需要|不必|不(?:做|用|画|生成|执行|运行|新增|实现|修改|改写|迁移|创建|编写|修复|自动)|do not\b|don't\b|without\b|skip\b)/iu;
11
+ const APPLY = /生成|绘制|新增|增加|实现|修改|改写|编写|修复|整改|创建|迁移|重构|同步|统一|调整|优化|完善|补充|补[^,。;\n]{0,12}注释|\b(?:generate|draw|create|implement|modify|write|fix|repair|migrate|refactor|adjust|optimize|improve)\b/iu;
12
+ const INSPECT = /检查|校验|验证|审查|审计|扫描|评审|核对|走查|梳理|\b(?:check|validate|verify|inspect|review|audit|scan)\b/iu;
13
+ const DISCUSSION = /(?:(?:只|仅)(?:需要)?(?:分析|讨论|解释|说明|了解|咨询)|(?:先|暂时)(?:分析|讨论|解释|说明)|\b(?:only|just)\s+(?:explain|discuss|describe|analy[sz]e)\b)/iu;
14
+
15
+ function normalized(text) { return String(text || "").normalize("NFKC").toLowerCase().trim(); }
16
+
17
+ function analyzeTask(task) {
18
+ const original = String(task || "").normalize("NFKC");
19
+ // Current-request markers and contrast conjunctions start a new evidence clause.
20
+ const clauses = original.replace(/(但(?:是)?|而是|改为|(?:本次|这次|此次|现在)(?=\s*(?:只|仅|请|要|需要|检查|审查|生成|修改|补充)))/gu, "\n$1")
21
+ .split(/[,,。;;!?!?\n]+/u).map((part) => part.trim()).filter(Boolean);
22
+ const excluded = [];
23
+ const active = [];
24
+ for (const raw of clauses) {
25
+ const clause = raw.replace(/^(?:但是|但|而是|改为)\s*/u, "").trim();
26
+ if (HISTORY.test(clause) && !CURRENT.test(clause)) {
27
+ excluded.push({ kind: "historical", text: clause });
28
+ } else if (NEGATIVE.test(clause)) {
29
+ excluded.push({ kind: "prohibited", text: clause });
30
+ } else active.push(clause);
31
+ }
32
+ const activeText = active.join(",");
33
+ return {schemaVersion:1,activeText,excluded,action:actionEvidence(activeText,excluded)};
34
+ }
35
+
36
+ function actionEvidence(activeText, excluded) {
37
+ const discussion = DISCUSSION.test(activeText);
38
+ const mode = discussion ? "explain" : /(?:只|仅|先)?(?:列|制定|提供|给出|做)(?:一个|一份)?[^,。;\n]{0,40}计划|\bplan\b/iu.test(activeText) ? "plan"
39
+ : APPLY.test(activeText) ? "apply" : INSPECT.test(activeText) ? "inspect"
40
+ : /解释|说明|讨论|咨询|\b(?:explain|discuss|describe)\b/iu.test(activeText) ? "explain" : "unspecified";
41
+ const genericStop = excluded.some((item) => item.kind === "prohibited" && /(?:暂|先|不要|不)(?:自动)?(?:执行|运行)(?:任何|所有)?(?:工具|命令|检查|校验)?\s*$|\b(?:do not|don't|skip)\s+(?:execute|run)\s+(?:(?:any|all)\s+)?(?:tools?|commands?|checks?)\s*$/iu.test(item.text));
42
+ return {
43
+ mode, checksAllowed: !genericStop && ["inspect", "apply"].includes(mode), businessWritesAuthorized: false,
44
+ authorization: "intent-is-not-business-write-authorization",
45
+ };
46
+ }
47
+
48
+ function phraseMatches(text, phrase) {
49
+ const haystack = normalized(analyzeTask(text).activeText);
50
+ const needle = normalized(phrase);
51
+ if (!needle) return false;
52
+ let at = haystack.indexOf(needle);
53
+ while (at !== -1) {
54
+ const before = haystack.slice(Math.max(0, at - 32), at);
55
+ const boundary = !/^[a-z0-9_.+-]+$/iu.test(needle)
56
+ || !/[a-z0-9_-]/iu.test((haystack[at - 1] || "") + (haystack[at + needle.length] || ""));
57
+ const negated = /(?:不要|不用|无需|禁止|别|不做|不|do not|don't|without)\s*(?:生成|创建|做|画|绘制|进行|设计|用|使用|执行|运行)?\s*$/iu.test(before);
58
+ if (boundary && !negated) return true;
59
+ at = haystack.indexOf(needle, at + needle.length);
60
+ }
61
+ return false;
62
+ }
63
+
64
+ module.exports = { analyzeTask, phraseMatches };
@@ -5,6 +5,7 @@ const fs = require("node:fs");
5
5
  const path = require("node:path");
6
6
  const crypto = require("node:crypto");
7
7
  const projectScope = require("./project-scope.cjs");
8
+ const taskIntent = require("./task-intent.cjs");
8
9
 
9
10
  const RECORD_VERSION = 1;
10
11
  const STORAGE = {
@@ -48,18 +49,7 @@ function digest(value) {
48
49
  }
49
50
 
50
51
  function phraseMatches(text, phrase) {
51
- const needle = normalized(phrase);
52
- if (!needle) return false;
53
- let index = text.indexOf(needle);
54
- while (index !== -1) {
55
- const before = text.slice(Math.max(0, index - 16), index);
56
- const after = text[index + needle.length] || "";
57
- const boundary = !/^[a-z0-9_-]+$/i.test(needle) || !/[a-z0-9_-]/i.test((text[index - 1] || "") + after);
58
- const negated = /(?:不要|不用|无需|禁止|别|不是|不做|不|do not|don't|without)\s*$/i.test(before);
59
- if (boundary && !negated) return true;
60
- index = text.indexOf(needle, index + needle.length);
61
- }
62
- return false;
52
+ return taskIntent.phraseMatches(text, phrase);
63
53
  }
64
54
 
65
55
  function matches(text, words) {
@@ -84,7 +74,7 @@ function targetRelevance(targets, policy) {
84
74
 
85
75
  function taskSignals(options) {
86
76
  const policy = options.policy || {};
87
- const text = normalized(options.task);
77
+ const text = normalized(taskIntent.analyzeTask(options.task).activeText);
88
78
  const context = options.context || {};
89
79
  const contextText = `${text} ${strings(options.targets).join(" ")} ${strings(context.signals).join(" ")}`;
90
80
  const relevant = matches(text, policy.domainKeywords).length > 0 || targetRelevance(options.targets, policy) || context.domainRelevant === true;
@@ -138,7 +128,8 @@ function evaluateTask(options = {}) {
138
128
  const signals = taskSignals(options);
139
129
  const candidates = (options.catalog || []).map((skill) => scoreSkill(skill, signals.text, signals.contextText))
140
130
  .filter((skill) => skill.score > 0).sort((left, right) => right.score - left.score || left.id.localeCompare(right.id));
141
- return selectDecision(candidates, signals, options) || fallbackDecision(candidates, signals, options);
131
+ const result = selectDecision(candidates, signals, options) || fallbackDecision(candidates, signals, options);
132
+ return { ...result, taskEvidence: taskIntent.analyzeTask(options.task), action: taskIntent.analyzeTask(options.task).action };
142
133
  }
143
134
 
144
135
  function assertRunId(value) {
@@ -565,10 +556,22 @@ function noticeScope(record, options, item) {
565
556
  unverified: strings(item.unverified || ["host-discovery", "model-read-canonical-files", "planned-actions-not-executed"]) };
566
557
  }
567
558
  function noticeReason(item) { return safeText(item.reason || strings(item.reasons).join(";")); }
568
- function attachNotice(record, options = {}) {
559
+ function prepareNoticeDecision(record,options) {
569
560
  const item = record.decision || record;
570
561
  record.packageName ||= options.packageName;
571
562
  record.packageVersion ||= options.packageVersion;
563
+ item.action ||= taskIntent.analyzeTask(record.task || item.task || "").action;
564
+ const details = new Map((item.ruleDetails || []).map((entry) => [entry.id, entry]));
565
+ item.ruleRefs ||= strings([...strings(item.requiredRules), ...details.keys()]).map((id) => {
566
+ const detail = details.get(id) || {};
567
+ return { packageName: record.packageName || null, ruleId: /^[a-zA-Z0-9_.:-]+$/.test(id) ? id : `constraint:${digest(id).slice(0, 16)}`,
568
+ legacyId: id, name: detail.name || id, ruleVersion: record.packageVersion || null,
569
+ source: detail.source || null, kind: detail.kind || "guidance", verification: "unverified" };
570
+ });
571
+ return item;
572
+ }
573
+ function attachNotice(record, options = {}) {
574
+ const item = prepareNoticeDecision(record,options);
572
575
  record.notice = {
573
576
  schemaVersion: 1, packageName: record.packageName || null, packageVersion: record.packageVersion || null,
574
577
  projectRoot: noticeProjectRoot(record, options), runId: record.runId || null, decision: item.status || "unverified",
@@ -576,6 +579,7 @@ function attachNotice(record, options = {}) {
576
579
  reason: noticeReason(item), missingInputs: strings(item.missingInputs),
577
580
  gaps: (item.gaps || []).map((gap) => ({ reason: safeText(gap.reason), suggestion: safeText(gap.suggestion) })),
578
581
  ...noticeScope(record, options, item), displayEvidence: "unverified",
582
+ action: item.action, ruleRefs: item.ruleRefs,
579
583
  };
580
584
  return record;
581
585
  }
@@ -594,7 +598,7 @@ function formatDecision(record) {
594
598
  const notice = record.notice || attachNotice({ ...record }).notice;
595
599
  const label = { baseline: "基础约束", ambiguous: "候选需确认", gap: "规则缺口", "not-applicable": "不适用", "needs-context": "缺少上下文" }[notice.decision] || "待判定";
596
600
  const identity = notice.packageName ? `${notice.packageName.replace("@agile-team/wl-skills-", "")}@${notice.packageVersion || "未知"};` : "";
597
- return `[WL ${notice.decision}] ${identity}${notice.skills.join("、") || label};${notice.reason}` + `\n适用规则:${noticeRuleText(notice)};范围:${notice.targets.slice(0, 3).join("、") || "未指定目标,检查范围待确认"}` + noticePlanText(notice) + noticeGapText(notice);
601
+ return `[WL ${notice.decision}] ${identity}${notice.skills.join("、") || label};${notice.reason}` + `\n动作=${notice.action.mode};允许自动检查=${notice.action.checksAllowed};业务写入遵循原授权契约` + `\n适用规则:${noticeRuleText(notice)};范围:${notice.targets.slice(0, 3).join("、") || "未指定目标,检查范围待确认"}` + noticePlanText(notice) + noticeGapText(notice);
598
602
  }
599
603
 
600
604
  function formatStatus(status) {
@@ -0,0 +1,93 @@
1
+ {
2
+ "element/el-table": [
3
+ "表格",
4
+ "table",
5
+ "el-table"
6
+ ],
7
+ "element/el-form": [
8
+ "表单",
9
+ "查询表单",
10
+ "el-form"
11
+ ],
12
+ "element/el-dialog": [
13
+ "弹窗",
14
+ "dialog",
15
+ "el-dialog"
16
+ ],
17
+ "element/el-tag": [
18
+ "标签颜色",
19
+ "状态标签",
20
+ "el-tag"
21
+ ],
22
+ "element/component-family": [
23
+ "卡片",
24
+ "页签",
25
+ "抽屉",
26
+ "el-tabs",
27
+ "上传控件"
28
+ ],
29
+ "runtime/design-tokens": [
30
+ "设计令牌",
31
+ "design tokens",
32
+ "颜色硬编码",
33
+ "tokens"
34
+ ],
35
+ "runtime/style-align": [
36
+ "样式统一",
37
+ "风格对齐",
38
+ "样式不生效"
39
+ ],
40
+ "runtime/migration": [
41
+ "runtime迁移",
42
+ "样式迁移"
43
+ ],
44
+ "ops/scan": [
45
+ "UI扫描",
46
+ "UI检查",
47
+ "样式扫描"
48
+ ],
49
+ "ops/audit": [
50
+ "UI审计",
51
+ "视觉审计"
52
+ ],
53
+ "ops/fix": [
54
+ "UI修复",
55
+ "修复样式",
56
+ "统一视觉"
57
+ ],
58
+ "ops/migrate": [
59
+ "UI迁移",
60
+ "迁移tokens"
61
+ ],
62
+ "layouts/list-page": [
63
+ "列表布局"
64
+ ],
65
+ "layouts/detail-page": [
66
+ "详情布局"
67
+ ],
68
+ "layouts/form-dialog": [
69
+ "表单弹窗布局"
70
+ ],
71
+ "layouts/tree-list": [
72
+ "树列表布局"
73
+ ],
74
+ "vendors/ag-grid": [
75
+ "AG Grid",
76
+ "ag-grid"
77
+ ],
78
+ "vendors/base-table": [
79
+ "BaseTable"
80
+ ],
81
+ "vendors/jh-components": [
82
+ "jh组件"
83
+ ],
84
+ "vendors/c-components": [
85
+ "C组件"
86
+ ],
87
+ "vendors/custom-wrappers": [
88
+ "自定义封装组件"
89
+ ],
90
+ "vendors/unknown-wrapper": [
91
+ "未知封装组件"
92
+ ]
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-ui",
3
- "version": "1.18.0",
3
+ "version": "1.19.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",