@dsh-workbench/module-tools 0.3.10 → 1.0.0-beta.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/LICENSE +21 -0
- package/README.md +223 -177
- package/bin/dsh-wb-module.mjs +89 -34
- package/package.json +18 -3
- package/src/arch.mjs +158 -129
- package/src/build.mjs +6 -30
- package/src/changelog.mjs +167 -0
- package/src/check-flags.mjs +0 -7
- package/src/check.mjs +115 -53
- package/src/checks/alias.mjs +0 -9
- package/src/checks/arch.mjs +3 -25
- package/src/checks/bundles.mjs +2 -34
- package/src/checks/cards.mjs +26 -30
- package/src/checks/contracts.mjs +61 -222
- package/src/checks/kit.mjs +2 -42
- package/src/checks/lint.mjs +9 -82
- package/src/checks/markers.mjs +48 -17
- package/src/checks/primitives.mjs +0 -39
- package/src/checks/protocol.mjs +0 -18
- package/src/checks/source.mjs +163 -29
- package/src/checks/standalone.mjs +4 -14
- package/src/checks/style.mjs +71 -241
- package/src/css-injection.mjs +0 -11
- package/src/css-shell.mjs +3 -31
- package/src/datasource-file.mjs +104 -0
- package/src/declared-tools.mjs +0 -24
- package/src/design.mjs +116 -38
- package/src/discover.mjs +2 -10
- package/src/font-scale.mjs +1 -61
- package/src/init.mjs +2 -17
- package/src/lint.mjs +24 -144
- package/src/market-index.mjs +569 -0
- package/src/market-init.mjs +80 -0
- package/src/primitives-surface.mjs +2 -28
- package/src/publish.mjs +6 -24
- package/src/release-changelog.mjs +29 -0
- package/src/release-journal.mjs +131 -0
- package/src/release-report.mjs +99 -0
- package/src/release-util.mjs +80 -0
- package/src/release.mjs +1251 -0
- package/src/seed.mjs +0 -25
- package/src/shell-type-files.mjs +3 -30
- package/src/sync.mjs +41 -24
- package/template/AGENTS.md.tpl +33 -0
- package/template/CHANGELOG.md.tpl +7 -0
- package/template/README.md.tpl +6 -33
- package/template/package.json.tpl +2 -2
- package/template/src/client.tsx.tpl +1 -18
- package/template/src/host.ts.tpl +2 -15
- package/template/types/globals.d.ts +1 -14
- package/template-diy/AGENTS.md.tpl +33 -0
- package/template-diy/CHANGELOG.md.tpl +7 -0
- package/template-diy/README.md.tpl +4 -133
- package/template-diy/package.json.tpl +2 -2
- package/template-diy/src/client.tsx.tpl +1 -29
- package/template-diy/src/host.ts.tpl +1 -15
- package/template-diy/src/messages.ts.tpl +4 -4
- package/template-diy/src/plugin.test.ts.tpl +1 -1
- package/template-diy/types/globals.d.ts +1 -14
- package/template-diy/types/styles.d.ts +0 -10
package/src/checks/cards.mjs
CHANGED
|
@@ -19,9 +19,6 @@ const FORBIDDEN = [
|
|
|
19
19
|
|
|
20
20
|
const WORKSPACE = { path: '/tmp/repo', workspaceId: 'wb-card-probe' };
|
|
21
21
|
|
|
22
|
-
// 刷新那一帧的旧时间戳(`at`)。真壳在 `ready` 那一步写 `Date.now()`,同一个 identity 重读时
|
|
23
|
-
// 把它原样带进 loading 相(`use-api-resource.ts:102`);这里给一个固定的过去时刻,判据不依赖
|
|
24
|
-
// "现在几点"(同一个值每次跑都一样,负例才好断言)。
|
|
25
22
|
const STALE_AT = 1_700_000_000_000;
|
|
26
23
|
|
|
27
24
|
function anyStub(path = 'stub') {
|
|
@@ -103,51 +100,52 @@ const SNAPSHOTS = [
|
|
|
103
100
|
resource: { phase: 'error', error: { code: 'network', message: '宿主没有响应' } },
|
|
104
101
|
workspace: WORKSPACE,
|
|
105
102
|
},
|
|
106
|
-
|
|
107
|
-
// `at` 都在,而且是旧那份。`use-api-resource.ts:100-104` 在同一个 identity 重读时给的就是这一相
|
|
108
|
-
// (`{ phase: 'loading', value: prev.value, ...(prev.at === undefined ? {} : { at: prev.at }) }`,
|
|
109
|
-
// 而 `prev.at` 是 `ready` 那一步写的 `Date.now()`,所以真实这一相两格一起在)。卡片若把
|
|
110
|
-
// "loading 就没有值"写死,脏就出在这一帧;若把旧值当新值、或拿旧 `at` 当"刚读到"去算,脏也出在这一帧。
|
|
103
|
+
|
|
111
104
|
{
|
|
112
105
|
name: 'loading(刷新:保留旧值)',
|
|
113
106
|
resource: { phase: 'loading', value: containerStub('api', 2), at: STALE_AT },
|
|
114
107
|
workspace: WORKSPACE,
|
|
115
108
|
},
|
|
116
|
-
|
|
117
|
-
// `use-unit-board.ts:47` 对 `capability === null` 直接不算 `blockerOfCapability`,喂 null 就退化成
|
|
118
|
-
// P1 的 idle 那一屏、一格新代码都走不到。`gated` 由能力状态按 `gateOf`(`unit-board.ts:13-17`)
|
|
119
|
-
// 算出来(missing-plugin ⇒ false),`resource` 照 `ready` 一份 —— 于是它跟 `ready` 只差闸这一格,
|
|
120
|
-
// 阻断屏与正文同时在场时谁优先是模块的事。
|
|
109
|
+
|
|
121
110
|
{
|
|
122
111
|
name: 'unreadable(能力闸:缺插件)',
|
|
123
112
|
resource: { phase: 'ready', value: containerStub('api', 2) },
|
|
124
113
|
workspace: WORKSPACE,
|
|
125
114
|
capability: (unit) => ({
|
|
126
115
|
unit,
|
|
127
|
-
status: 'missing
|
|
128
|
-
|
|
129
|
-
|
|
116
|
+
status: 'missing',
|
|
117
|
+
needs: [
|
|
118
|
+
{
|
|
119
|
+
id: 'extension:demo-plugin',
|
|
120
|
+
kind: 'extension',
|
|
121
|
+
name: 'demo-plugin',
|
|
122
|
+
status: 'missing',
|
|
123
|
+
detail: '逐态探针:缺 demo-plugin',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
130
126
|
detail: '逐态探针:缺 demo-plugin',
|
|
131
127
|
}),
|
|
132
128
|
},
|
|
133
|
-
|
|
134
|
-
// 差的是两格:`status` 换掉,且 `fix` / `command` 都在(真壳 `probe-verdict.ts:109-116`:命令落后
|
|
135
|
-
// 的插件状态是 `outdated`,于是 `fix: 'update'`;`:158-160` 再带上那条升级命令)。这两格正好把
|
|
136
|
-
// `blockerOfCapability` 里 `install` 那一段走通:`fixAction`(`blocker.fix.update`)+ `copyAction`
|
|
137
|
-
// (`blocker.copy` + `command` 原文,`blocker-table.ts:109-123`),加上照旧的 retry —— 缺插件那一帧
|
|
138
|
-
// 两格都没给,install 是空的,只走到 retry。`sourceUnknown` 在 `fix` 在时不成立,所以不开市场那条。
|
|
129
|
+
|
|
139
130
|
{
|
|
140
131
|
name: 'unreadable(能力闸:缺命令)',
|
|
141
132
|
resource: { phase: 'ready', value: containerStub('api', 2) },
|
|
142
133
|
workspace: WORKSPACE,
|
|
143
134
|
capability: (unit) => ({
|
|
144
135
|
unit,
|
|
145
|
-
status: '
|
|
146
|
-
|
|
147
|
-
|
|
136
|
+
status: 'outdated',
|
|
137
|
+
needs: [
|
|
138
|
+
{
|
|
139
|
+
id: 'extension:demo-plugin',
|
|
140
|
+
kind: 'extension',
|
|
141
|
+
name: 'demo-plugin',
|
|
142
|
+
status: 'outdated',
|
|
143
|
+
missing: ['demo.command'],
|
|
144
|
+
detail: '逐态探针:demo-plugin 装在,缺 demo.command',
|
|
145
|
+
command: "'opencli' plugin update 'demo-plugin'",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
148
|
detail: '逐态探针:demo-plugin 装在,缺 demo.command',
|
|
149
|
-
fix: 'update',
|
|
150
|
-
command: 'opencli plugin update demo-plugin',
|
|
151
149
|
}),
|
|
152
150
|
},
|
|
153
151
|
];
|
|
@@ -403,9 +401,7 @@ export async function run() {
|
|
|
403
401
|
if (realKit === undefined) return fakeKit();
|
|
404
402
|
const hooks = { ...(realKit.hooks ?? {}) };
|
|
405
403
|
hooks.useApiResource = () => current.snapshot.resource;
|
|
406
|
-
|
|
407
|
-
// `capability` 才换成那一份,`gated` 照 `gateOf(capability, 'capability')` 算 —— 判据不当
|
|
408
|
-
// 第二份真相:状态的语义归外壳,这里只是把"外壳会给的那一格"摆出来。
|
|
404
|
+
|
|
409
405
|
hooks.useCapabilityGate = (unit) => {
|
|
410
406
|
const capability = current.snapshot.capability?.(unit) ?? null;
|
|
411
407
|
return {
|
package/src/checks/contracts.mjs
CHANGED
|
@@ -1,29 +1,8 @@
|
|
|
1
|
-
// 跨包的**模块契约**检查(S2 的第一块:门禁读包的自述清单,不扫源码)。
|
|
2
|
-
//
|
|
3
|
-
// 为什么需要它:dsh 的组合阶段**不校验** `dsh.client.external` —— 实测把一个不存在的
|
|
4
|
-
// 外部声明(`@dsh-workbench/core-nope/client`)写进模块包,它被原样收进启动图、
|
|
5
|
-
// 宿主侧一声不吭、程序照起;错的声明要等浏览器 `require` 那一刻才炸。
|
|
6
|
-
//
|
|
7
|
-
// 判据全部来自各包 package.json 里的 `dsh` 字段(**自述清单**):
|
|
8
|
-
// · `dsh.bundle.patch` 指的补丁文件真的在;
|
|
9
|
-
// · `dsh.client.platform` 是认识的平台;
|
|
10
|
-
// · `dsh.client` 的 inject / external 每一条都能落到图里的一行(seed 词 / 平台包 / workspace 包);
|
|
11
|
-
// · `dsh.workbench`(**这是个模块**):
|
|
12
|
-
// - `unit` 形状合法、全局唯一(撞了就是装上去当场抛);
|
|
13
|
-
// - `dsh.workbenchFaces.host` / `.client` 指的源码入口文件真的在 ——
|
|
14
|
-
// 它是**工具链字段**,不在 `dsh.workbench` 协议里(ADR-0038 §7 第 7 条):
|
|
15
|
-
// `source` 判据靠这两个入口做可达性分析,所以它必须是源码路径、必须声明;
|
|
16
|
-
// - 对壳有 peer 依赖(它跑在外壳交出来的服务上);
|
|
17
|
-
// - 有自己的接线自检脚本(模块侧也必须机器判据,不能只靠人眼)。
|
|
18
|
-
//
|
|
19
|
-
// 形状(所有判据统一):导出 `title` 与 `run()`,`run()` 返回三态结果;
|
|
20
|
-
// 打印与退出码归 `../check.mjs` 那一层。
|
|
21
|
-
|
|
22
1
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
23
2
|
import { createRequire } from 'node:module';
|
|
24
3
|
import { join, relative } from 'node:path';
|
|
25
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
|
-
import { readDeclaration } from '@dsh-workbench/shared/host';
|
|
5
|
+
import { buildSupplyIndex, readDeclaration, recipeProblemOf } from '@dsh-workbench/shared/host';
|
|
27
6
|
import {
|
|
28
7
|
DECLARED_TOOLS_FILE,
|
|
29
8
|
declaredToolsProblemOf,
|
|
@@ -31,11 +10,12 @@ import {
|
|
|
31
10
|
declaredToolsApplies,
|
|
32
11
|
} from '../declared-tools.mjs';
|
|
33
12
|
import { discoverPackages, workspaceRootOf } from '../discover.mjs';
|
|
13
|
+
import { readDesign } from '../design.mjs';
|
|
14
|
+
import { datasourceProblemOf } from '../datasource-file.mjs';
|
|
34
15
|
import { SEED_SHORT } from '../seed.mjs';
|
|
35
16
|
|
|
36
17
|
const ROOT = process.env.DSH_WB_ROOT ?? fileURLToPath(new URL('../../../../', import.meta.url));
|
|
37
18
|
|
|
38
|
-
/** dsh 浏览器侧的固定 seed 词表(模块表里天生就有的那几个)—— 唯一真相在 `../seed.mjs`。 */
|
|
39
19
|
const SEED = SEED_SHORT;
|
|
40
20
|
|
|
41
21
|
const PLATFORMS = new Set(['web']);
|
|
@@ -50,10 +30,6 @@ function text(value) {
|
|
|
50
30
|
return typeof value === 'string' ? value.trim() : '';
|
|
51
31
|
}
|
|
52
32
|
|
|
53
|
-
// 把模块的**宿主产物**挂起来,拿到它的面(清单 + needsApproval / approvalDetail)。
|
|
54
|
-
// 与模块自己的 smoke 用的是同一条路:apply() 要一个 workbenchUnits,register 里把面接住。
|
|
55
|
-
// 挂不起来(产物缺失、apply 抛、mount 需要真的 store)一律返回 undefined —— 那是别的判据的事,
|
|
56
|
-
// 这里只负责"清单说它会写,那它有没有认领审批"。
|
|
57
33
|
async function unitFaceOf(dir) {
|
|
58
34
|
const entry = join(dir, 'dist/host.js');
|
|
59
35
|
if (!existsSync(entry)) return undefined;
|
|
@@ -82,58 +58,6 @@ async function unitFaceOf(dir) {
|
|
|
82
58
|
}
|
|
83
59
|
}
|
|
84
60
|
|
|
85
|
-
function isRegistry(value) {
|
|
86
|
-
return /^(https?|file):\/\//.test(value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function envProblemsOf(name, env) {
|
|
90
|
-
const problems = [];
|
|
91
|
-
const libs = env.libs ?? [];
|
|
92
|
-
const plugins = env.plugins ?? [];
|
|
93
|
-
if (text(env.command) === '' && libs.length === 0 && plugins.length === 0) {
|
|
94
|
-
problems.push(`${name}: env 是空的 —— 要么删掉它,要么写清这个模块靠什么才跑得起来(command / libs / plugins)`);
|
|
95
|
-
}
|
|
96
|
-
libs.forEach((need, index) => {
|
|
97
|
-
const at = `env.libs[${index}]`;
|
|
98
|
-
if (text(need?.name) === '') problems.push(`${name}: ${at} 没写 name`);
|
|
99
|
-
if (text(need?.registryEnv) === '') {
|
|
100
|
-
problems.push(`${name}: ${at} 没写 registryEnv —— 用户的内网源没法覆盖它,换源时只能改代码`);
|
|
101
|
-
}
|
|
102
|
-
if (!isRegistry(text(need?.registryDefault))) {
|
|
103
|
-
problems.push(
|
|
104
|
-
`${name}: ${at}.registryDefault 不是 http(s):// 或 file:// 的源地址 —— ${JSON.stringify(need?.registryDefault)}`,
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
if (text(need?.purpose) === '') {
|
|
108
|
-
problems.push(`${name}: ${at} 没写 purpose —— "装它干嘛"就是给用户看的那半句,不能空`);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
111
|
-
plugins.forEach((need, index) => {
|
|
112
|
-
const at = `env.plugins[${index}]`;
|
|
113
|
-
if (text(need?.name) === '') problems.push(`${name}: ${at} 没写 name`);
|
|
114
|
-
for (const command of need?.commands ?? []) {
|
|
115
|
-
if (text(command) === '') problems.push(`${name}: ${at}.commands 里有空字符串`);
|
|
116
|
-
}
|
|
117
|
-
const source = need?.installSource;
|
|
118
|
-
if (source === undefined) return;
|
|
119
|
-
if (source.kind === 'npm') {
|
|
120
|
-
if (text(source.package) === '') problems.push(`${name}: ${at}.installSource 是 npm 却没写 package`);
|
|
121
|
-
if (text(source.registryEnv) === '') problems.push(`${name}: ${at}.installSource 没写 registryEnv`);
|
|
122
|
-
} else if (source.kind === 'git') {
|
|
123
|
-
if (text(source.url) === '') problems.push(`${name}: ${at}.installSource 是 git 却没写 url`);
|
|
124
|
-
} else if (source.kind === 'local') {
|
|
125
|
-
if (text(source.env) === '') problems.push(`${name}: ${at}.installSource 是 local 却没写 env`);
|
|
126
|
-
} else {
|
|
127
|
-
problems.push(
|
|
128
|
-
`${name}: ${at}.installSource.kind 不认识 —— ${JSON.stringify(source.kind)}(只有 npm / git / local)`,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
return problems;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// opencli 的隔离运行时(唯一真相在 packages/shared/src/host/opencli-runtime.ts;module-tools 不依赖
|
|
136
|
-
// shared,所以这里按同一套约定重算一遍 —— 认同一组环境变量,测试与 selfcheck 靠它注入 fixture)。
|
|
137
61
|
const OPENCLI_CATALOG_ENV = 'DSH_WB_OPENCLI_CATALOG';
|
|
138
62
|
const OPENCLI_NPM_PACKAGE = '@jackwener/opencli';
|
|
139
63
|
|
|
@@ -216,10 +140,6 @@ function sourceFilesOf(dir) {
|
|
|
216
140
|
return found;
|
|
217
141
|
}
|
|
218
142
|
|
|
219
|
-
// `plugin: '<字面量>'` 与同一行里 `?? '<字面量>'` 的兜底:都该是**清单里声明过**的那个插件名。
|
|
220
|
-
// 插件名只该有一处真相(能拿到 `deps.manifest` 的地方就用它),但模块级代码拿不到 manifest ⇒ 只能写字面量。
|
|
221
|
-
// 这条判据拦的是"改了清单没改代码"(或反过来):那样外壳会按错的名字去装 / 去探那个插件,而用户只看到
|
|
222
|
-
// "缺命令"这种没头没尾的状态。只查 `plugin:` 那几行,别的字面量不管(不猜)。
|
|
223
143
|
function pluginLiteralsOf(line) {
|
|
224
144
|
if (!/\bplugin\s*:/.test(line)) return [];
|
|
225
145
|
const found = [];
|
|
@@ -251,8 +171,7 @@ function fakeLongTasks(created) {
|
|
|
251
171
|
return {
|
|
252
172
|
create(options) {
|
|
253
173
|
created.push(options);
|
|
254
|
-
|
|
255
|
-
// 把注册表原样返回的模块就会被这条判据冤枉成"缺方法"——所以 `stop` / `live` 也在这里。
|
|
174
|
+
|
|
256
175
|
return {
|
|
257
176
|
start: () => ({}),
|
|
258
177
|
snapshot: () => null,
|
|
@@ -266,10 +185,6 @@ function fakeLongTasks(created) {
|
|
|
266
185
|
};
|
|
267
186
|
}
|
|
268
187
|
|
|
269
|
-
// 契约里 mount 一定拿得到 deps.store(外壳给的是 WorkspaceStore)。假的 deps 少给这一样,
|
|
270
|
-
// 用到 store 的模块(存偏好是最常见的用法)就会在这里抛、被当成"mount 坏了",
|
|
271
|
-
// 然后在审批那条判据上被冤枉成"没给出合法口径"。所以假 store 也给它一个:
|
|
272
|
-
// for / machine / scan 都回同一个内存文件区,写进去的东西后面读得回来。
|
|
273
188
|
function fakeStore() {
|
|
274
189
|
const files = new Map();
|
|
275
190
|
const one = () => ({
|
|
@@ -286,11 +201,6 @@ function fakeStore() {
|
|
|
286
201
|
return { for: () => one(), machine: () => one(), scan: () => [one()] };
|
|
287
202
|
}
|
|
288
203
|
|
|
289
|
-
// mount 也一定拿得到 shell 与 workspaceRegistry(外壳给的是 dsh 交出来的服务)。
|
|
290
|
-
// 少给这两样,模块的**降级写法**就会生效 —— 而"拿不到 shell 就只交 routes + registerTools"
|
|
291
|
-
// 是合法写法(实测 pduiv2 的 coding-issues / coding-analysis 就这么写),降级后的面本来就不带
|
|
292
|
-
// 审批钩子,判据却会把这读成"没认领审批":红的是判据自己的环境,不是模块。
|
|
293
|
-
// (与上面 `store` 那笔账同一条纪律:假 deps 少给一样,模块就在这条判据上被冤枉。)
|
|
294
204
|
function fakeShell() {
|
|
295
205
|
return {
|
|
296
206
|
resolve: () => undefined,
|
|
@@ -316,7 +226,7 @@ function fakeWorkspaceRegistry() {
|
|
|
316
226
|
|
|
317
227
|
function mountOf(face, created, manifest, tools) {
|
|
318
228
|
if (face === undefined || typeof face.mount !== 'function') return undefined;
|
|
319
|
-
|
|
229
|
+
|
|
320
230
|
const shell = fakeShell();
|
|
321
231
|
const workspaceRegistry = fakeWorkspaceRegistry();
|
|
322
232
|
const toolRegistry = {
|
|
@@ -355,16 +265,6 @@ const LONG_TASK_METHODS = ['start', 'snapshot', 'active', 'current', 'live', 'st
|
|
|
355
265
|
|
|
356
266
|
const LONG_TASK_RENDERERS = ['useLongTask', 'LongTaskRow', 'longTaskDot'];
|
|
357
267
|
|
|
358
|
-
// 审批探针要用的**合成调用**。
|
|
359
|
-
//
|
|
360
|
-
// 为什么不是 `{ arguments: {} }`:外壳调 needsApproval 时拿到的是 agent 真传的参数,
|
|
361
|
-
// 而"这次要不要点头"本来就允许依赖参数 —— "带 confirm: true 才真的写"是最常见的合法口径。
|
|
362
|
-
// 空参数探会把这类模块判成"没认领审批"(实测 pduiv2 那 4 条审批报错就是这么来的,
|
|
363
|
-
// ADR-0038 §6.15)。所以能拿到工具的 `parameters`(JSON Schema)时,按**必填项**造一个
|
|
364
|
-
// 形状合法的调用:枚举取第一个值,string / number / boolean / array / object 各给一个占位值。
|
|
365
|
-
//
|
|
366
|
-
// 拿不到(registerTools() 没注册出这把工具、或注册时没带 parameters)就退回空参数,
|
|
367
|
-
// 并在判红时把这件事写进那句话 —— 那种情况下判据只能验到"钩子在不在"这一层。
|
|
368
268
|
function placeholderOf(schema) {
|
|
369
269
|
if (typeof schema !== 'object' || schema === null) return null;
|
|
370
270
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
@@ -389,36 +289,12 @@ function probeCallOf(tool) {
|
|
|
389
289
|
return { arguments: args, shaped: true };
|
|
390
290
|
}
|
|
391
291
|
|
|
392
|
-
// 审批头行(dsh 那张卡的 `.headline`)的预算。
|
|
393
|
-
//
|
|
394
|
-
// 为什么要有这条:`.headline` 是**纯文本的一格**,没有 `white-space` —— 换行会被塌成空格,
|
|
395
|
-
// 长句就不是"多几行"而是"一大坨"。三段都会落进那一格(外壳按 `明细 —— 问句 —— 附注` 拼):
|
|
396
|
-
// · `approvalDetail` 的 zh/en = 明细(模块写的"这次要动什么",本该是一句短标题)
|
|
397
|
-
// · `question` 的 zh/en = 问句(外壳拼在明细后面的那句"允许吗")
|
|
398
|
-
// · `fallback` 的 zh/en = 兜底整句(明细缺失时顶上来)
|
|
399
|
-
// 长内容(文件路径 / diff / 列表 / 正文 / 多字段)该走模块的 `approval.render`:
|
|
400
|
-
// 那是卡上**另一格**(`conversation.approval.detail`),有排版、能换行、能画 diff。
|
|
401
|
-
// 实测(2026-10-16 一条真实审批卡):明细里塞了一整条 commit message、问句 77 字、附注 75 字,
|
|
402
|
-
// 屏幕上 5 行 300 字。
|
|
403
292
|
export const HEAD_BUDGETS = { detail: 20, question: 40, fallback: 40 };
|
|
404
293
|
|
|
405
|
-
// 英文那一侧只用来抓"真正的一坨":按中文的 3 倍算。长度主要看中文那侧(用户读的是它),
|
|
406
|
-
// 实测同一句的英文是中文的 2.2–2.8 倍(machine 直译的风格),按 2 倍算会把正常英文问句全判红。
|
|
407
294
|
const EN_RATIO = 3;
|
|
408
295
|
|
|
409
|
-
// 长度预算是**两轮**的:
|
|
410
|
-
// · 第 1 轮(占位短值,见 probeCallOf):紧预算 —— 头行本来就该是一句短句;
|
|
411
|
-
// · 第 2 轮(长值,见 budgetProbeOf):宽 3 倍 —— 专抓"把可变长字段塞进头行"。
|
|
412
|
-
// 宽 3 倍是故意的:对象名、文件名这类标识符本来就有长有短,不该因为"名字长"判红;
|
|
413
|
-
// 而一条 commit message、一段正文(60 字)塞进标题,3 倍也照样兜不住 —— 那才是要抓的。
|
|
414
296
|
const LONG_SCALE = 3;
|
|
415
297
|
|
|
416
|
-
// 只按必填项造的探针(`probeCallOf`)给的是**占位短值**(`probe` / `1` / `true`)——
|
|
417
|
-
// 于是"把可变长字段塞进头行"这件事它是**瞎的**:实测那条真实审批卡的明细是
|
|
418
|
-
// `改对象文件:对象「diep-dify」的「tencent-test-values.yaml」 仓库:helm 提交信息:<一整条 commit message>`(157 字),
|
|
419
|
-
// 而探针上只有 20 字(`probe`)⇒ 判据照绿。所以第 2 轮把**自由文本字段**换成真长度(60 字):
|
|
420
|
-
// 名字里带 message / note / reason / body / text / content / comment / title / description / summary
|
|
421
|
-
// 的字段(这些就是"人可以写一段话"的地方),其余 string(标识符)给 12 字的正常长度。
|
|
422
298
|
const FREE_TEXT = /message|note|reason|body|text|content|comment|title|description|summary/i;
|
|
423
299
|
const LONG_TEXT = '这里是一段真实长度的正文用来量头行预算不要把这种内容放进标题里'.repeat(2);
|
|
424
300
|
const LONG_ID = '真实长度的名字十二个字';
|
|
@@ -488,11 +364,6 @@ function installed(specifier, from) {
|
|
|
488
364
|
}
|
|
489
365
|
}
|
|
490
366
|
|
|
491
|
-
/**
|
|
492
|
-
* 这个包是不是"裸"的:上面没有任何 workspace(`pnpm-workspace.yaml` 或 `packages/`),
|
|
493
|
-
* 自己也没装依赖。裸目录里 `init` 出来的骨架就是这样 —— 它报的 inject/external 落不到图里
|
|
494
|
-
* **不是声明写错**,而是"它压根不在一个 workspace 里",而原文把这句话说成了"声明有问题"。
|
|
495
|
-
*/
|
|
496
367
|
function barePackage(pkg) {
|
|
497
368
|
return workspaceRootOf(pkg.dir) === pkg.dir && !existsSync(join(pkg.dir, 'node_modules'));
|
|
498
369
|
}
|
|
@@ -502,7 +373,6 @@ function packageOf(specifier) {
|
|
|
502
373
|
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];
|
|
503
374
|
}
|
|
504
375
|
|
|
505
|
-
/** 深比较两份 JSON:对象**键序无关**,数组**顺序有关**(工具那一串的顺序是声明的一部分)。 */
|
|
506
376
|
function reconcileTools(name, manifest, toolNames) {
|
|
507
377
|
const declared = manifest.tools.map((tool) => tool.name).sort();
|
|
508
378
|
const actual = [...new Set(toolNames.filter((one) => one !== ''))].sort();
|
|
@@ -519,10 +389,11 @@ export async function run() {
|
|
|
519
389
|
const packages = discoverPackages({ root: process.cwd() });
|
|
520
390
|
const known = new Set(packages.map((pkg) => pkg.manifest.name));
|
|
521
391
|
const problems = [];
|
|
392
|
+
const warnings = [];
|
|
522
393
|
const unresolvedIn = new Set();
|
|
523
394
|
const units = new Map();
|
|
524
|
-
|
|
525
|
-
|
|
395
|
+
const supplyDeclarations = [];
|
|
396
|
+
|
|
526
397
|
const probeNotes = [];
|
|
527
398
|
const headlineNotes = [];
|
|
528
399
|
let declared = 0;
|
|
@@ -534,10 +405,6 @@ export async function run() {
|
|
|
534
405
|
if (dsh === undefined) continue;
|
|
535
406
|
declared += 1;
|
|
536
407
|
|
|
537
|
-
// `dsh.bundle.patch` 允许是**一个字符串,也允许是一个数组**(dsh 自己的 dsh-web-app 就是数组:
|
|
538
|
-
// 它的 cordis.patch.yml + 四个 preset 文件全挂在这一处)。这里原来只按字符串处理,
|
|
539
|
-
// 于是第一个用数组的包会把判据**打崩**(ERR_INVALID_ARG_TYPE,堆栈指在 path.join 上,
|
|
540
|
-
// 而真正的问题是"声明形状没接住")—— 外壳加 DIY 的 preset 时踩到。
|
|
541
408
|
const patches = dsh.bundle?.patch;
|
|
542
409
|
for (const patch of patches === undefined ? [] : Array.isArray(patches) ? patches : [patches]) {
|
|
543
410
|
if (!existsSync(join(pkg.dir, patch))) {
|
|
@@ -589,10 +456,6 @@ export async function run() {
|
|
|
589
456
|
}
|
|
590
457
|
}
|
|
591
458
|
|
|
592
|
-
// 清单里的工具名与「代码里那份名字」的关系(ADR-0038 的 `tools[].key` 一节):
|
|
593
|
-
// 给了 key 就走生成物那一档 —— `src/declared-tools.ts` 必须存在、且与清单一致(过期 = 红),
|
|
594
|
-
// 并且源码里不许再有工具名的字面量(名字只写在清单里,代码引用 `DECLARED_TOOLS.<key>.name`,
|
|
595
|
-
// 键写错是编译错)。一个 key 都没写的模块不适用(那是不用生成物那一档)。
|
|
596
459
|
const declaredProblem = declaredToolsProblemOf(pkg.dir, dsh.workbench);
|
|
597
460
|
if (declaredProblem !== undefined) {
|
|
598
461
|
problems.push(`${name}: ${declaredProblem}`);
|
|
@@ -660,17 +523,21 @@ export async function run() {
|
|
|
660
523
|
}
|
|
661
524
|
}
|
|
662
525
|
|
|
663
|
-
// 外部供给:清单里声明了 env,就要把"靠什么跑起来"和"没装好时怎么办"都说清。
|
|
664
|
-
// 外壳只按 env 去装(app/host.ts 把 plugins/libs 交给隔离运行时跑 probe/install),
|
|
665
|
-
// 它不知道这个能力叫什么 —— 所以"照做"那句安装命令必须由模块自己给。
|
|
666
526
|
const read = readDeclaration(name, dsh.workbench);
|
|
667
527
|
const manifest = read.manifest;
|
|
668
528
|
const created = [];
|
|
669
|
-
|
|
529
|
+
|
|
670
530
|
const tools = [];
|
|
671
531
|
const face = await unitFaceOf(pkg.dir);
|
|
672
532
|
const mounted = face === undefined ? undefined : mountOf(face, created, manifest, tools);
|
|
673
|
-
const
|
|
533
|
+
const needs = manifest?.needs;
|
|
534
|
+
warnings.push(...read.warnings);
|
|
535
|
+
if (manifest === undefined) {
|
|
536
|
+
for (const one of read.problems) problems.push(one);
|
|
537
|
+
}
|
|
538
|
+
if (manifest !== undefined) {
|
|
539
|
+
supplyDeclarations.push({ id: manifest.id, needs: needs ?? [] });
|
|
540
|
+
}
|
|
674
541
|
|
|
675
542
|
if (manifest !== undefined) {
|
|
676
543
|
if (face === undefined) {
|
|
@@ -694,15 +561,12 @@ export async function run() {
|
|
|
694
561
|
}
|
|
695
562
|
}
|
|
696
563
|
|
|
697
|
-
if (
|
|
698
|
-
problems.push(...envProblemsOf(name, env));
|
|
564
|
+
if (needs !== undefined) {
|
|
699
565
|
const products = productsTextOf(pkg.dir);
|
|
566
|
+
const extensions = needs.filter((need) => need?.kind === 'extension');
|
|
700
567
|
|
|
701
|
-
// 声明的插件命令必须真的存在:拿 opencli 自带目录(按 site 归组,别名也算)与这台机器上
|
|
702
|
-
// 已装的插件包对一遍。**插件两头都找不到的就不管**(那说明这台机器还没装它,没得对,别误伤)——
|
|
703
|
-
// 拦的是"插件明明在,命令名却写错了":那样用户那边的"缺命令"永远补不上。
|
|
704
568
|
const catalog = catalogBySite();
|
|
705
|
-
|
|
569
|
+
extensions.forEach((plugin, index) => {
|
|
706
570
|
const pluginName = text(plugin?.name);
|
|
707
571
|
const declared = (plugin?.commands ?? []).map((one) => text(one)).filter((one) => one !== '');
|
|
708
572
|
if (pluginName === '' || declared.length === 0) return;
|
|
@@ -714,15 +578,13 @@ export async function run() {
|
|
|
714
578
|
if (missing.length === 0) return;
|
|
715
579
|
const knownList = [...known].sort();
|
|
716
580
|
problems.push(
|
|
717
|
-
`${name}:
|
|
581
|
+
`${name}: needs 里第 ${index} 条 extension(${pluginName})声明的命令 ${missing.join(' / ')} 在插件里找不到 —— ` +
|
|
718
582
|
`它真有这些:${knownList.slice(0, 8).join(' / ')}${knownList.length > 8 ? ' …' : ''}。` +
|
|
719
583
|
`命令名写错的话,用户那边的"缺命令"永远补不上(用 \`opencli ${pluginName} --help\` 看原名)`,
|
|
720
584
|
);
|
|
721
585
|
});
|
|
722
586
|
|
|
723
|
-
const declaredPlugins = new Set(
|
|
724
|
-
(env.plugins ?? []).map((one) => text(one?.name)).filter((one) => one !== ''),
|
|
725
|
-
);
|
|
587
|
+
const declaredPlugins = new Set(extensions.map((one) => text(one?.name)).filter((one) => one !== ''));
|
|
726
588
|
if (declaredPlugins.size > 0) {
|
|
727
589
|
for (const file of sourceFilesOf(pkg.dir)) {
|
|
728
590
|
readFileSync(file, 'utf8')
|
|
@@ -732,7 +594,7 @@ export async function run() {
|
|
|
732
594
|
if (declaredPlugins.has(literal)) continue;
|
|
733
595
|
problems.push(
|
|
734
596
|
`${name}: ${relative(pkg.dir, file)}:${index + 1} 里的插件名「${literal}」不在清单的 ` +
|
|
735
|
-
`
|
|
597
|
+
`needs(kind=extension)里(清单里是 ${[...declaredPlugins].join(' / ')})—— 改了清单没改代码` +
|
|
736
598
|
'(或反过来)会让外壳按错的名字去装 / 去探那个插件。插件名只有一处真相:' +
|
|
737
599
|
'能拿到 deps.manifest 的地方就用它,拿不到的地方至少与清单保持一致',
|
|
738
600
|
);
|
|
@@ -741,17 +603,14 @@ export async function run() {
|
|
|
741
603
|
}
|
|
742
604
|
}
|
|
743
605
|
|
|
744
|
-
if ((
|
|
606
|
+
if (needs.some((need) => need?.kind === 'lib') && !products.includes('libInstallCommand')) {
|
|
745
607
|
problems.push(
|
|
746
|
-
`${name}: 声明了
|
|
608
|
+
`${name}: 声明了 lib 类 need 却没用 libInstallCommand(@dsh-workbench/shared/host 里现成的)—— ` +
|
|
747
609
|
`lib 没装好时用户只看到"没装",没有能照做的那条安装命令`,
|
|
748
610
|
);
|
|
749
611
|
}
|
|
750
612
|
}
|
|
751
613
|
|
|
752
|
-
// 「他能自己配什么」有两种:视图内选择(route 参数,刷新回默认)与偏好(prefs + store,刷新还在)。
|
|
753
|
-
// 设计表里写了哪一种就得真的接上哪一种 —— 否则用户改完刷新回到默认,而表上写着"能配"。
|
|
754
|
-
// (**与 env 无关**:没有 env 的模块一样要查,所以放在 env 块外面。)
|
|
755
614
|
const configRows = designRowsOf(pkg.dir, 'config');
|
|
756
615
|
const hostProduct = hostProductOf(pkg.dir);
|
|
757
616
|
const wiredPrefs = /\bprefs\s*:/.test(hostProduct);
|
|
@@ -769,9 +628,25 @@ export async function run() {
|
|
|
769
628
|
);
|
|
770
629
|
}
|
|
771
630
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
631
|
+
const design = readDesign(pkg.dir);
|
|
632
|
+
const datasourceProblem = datasourceProblemOf(pkg.dir, design);
|
|
633
|
+
if (datasourceProblem !== undefined) problems.push(`${name}: ${datasourceProblem}`);
|
|
634
|
+
|
|
635
|
+
const reads =
|
|
636
|
+
design !== undefined && Array.isArray(design.reads) && design.reads.length > 0
|
|
637
|
+
? design.reads
|
|
638
|
+
: design !== undefined && design.datasource !== undefined
|
|
639
|
+
? [design.datasource]
|
|
640
|
+
: [];
|
|
641
|
+
for (const read of reads) {
|
|
642
|
+
const recipe = read?.recipe;
|
|
643
|
+
if (recipe === undefined) continue;
|
|
644
|
+
const problem = recipeProblemOf(recipe);
|
|
645
|
+
if (problem !== undefined) {
|
|
646
|
+
problems.push(`${name}: design.json 的配方不合法(${problem})—— 跑 \`dsh-wb-module design check\` 看点名`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
775
650
|
const capabilityRows = designRowsOf(pkg.dir, 'capabilities');
|
|
776
651
|
if (capabilityRows !== undefined && manifest !== undefined) {
|
|
777
652
|
const inDesign = capabilityRows.map((one) => text(one?.name)).filter((one) => one !== '');
|
|
@@ -792,22 +667,6 @@ export async function run() {
|
|
|
792
667
|
}
|
|
793
668
|
}
|
|
794
669
|
|
|
795
|
-
// 长任务:跑一会儿的事,注册了就得让用户看得见那一行。
|
|
796
|
-
//
|
|
797
|
-
// 外壳按 mount 结果里的 longTasks 投影出 `${unit}/runs`(kit 的 useLongTask 读的就是它)。
|
|
798
|
-
// 契约里的状态是**五种**:running / needs-login / stopped / ok / failed(`longTaskDot` 把它们映成
|
|
799
|
-
// 进行中 / 要登录 / 灰点 / 完成 / 出错 —— `stopped` 落进默认分支,是"用户按停的",不是成功也不是出错)。
|
|
800
|
-
// 契约里也**有"停止"**:注册表多一个 `stop(id)`,`run` 多收一个 `signal`。
|
|
801
|
-
// `start` 还可以带一个**可选**的 `session`(起这一趟的那个会话 id,模块从 `sessionIdOf(exec)` 拿):
|
|
802
|
-
// 不记 = 用户在界面上按的 —— 外壳那两把 `workbench_long_tasks_*` 按它判"谁的"(自己起的免审批)。
|
|
803
|
-
// 判据**不查**这一条(它只查形状;"起的时候记没记来源"是形状之外的事)。
|
|
804
|
-
// 但"停止"在注册表这一侧的射程只有两件:外壳把 `signal` abort 掉、把这一趟标成 `stopped`;
|
|
805
|
-
// **真停不停得了看生产方认不认这个 signal** —— 那是模块的知识,判据查不了这一条
|
|
806
|
-
// (内部是 opencli 子进程那类编排,今天最多做到"我们不再等它")。判据能查的是形状与"画得出来":
|
|
807
|
-
// · 注册表形状要全(少了方法,外壳那条 runs 路由 / 那颗停止钮就按不动);
|
|
808
|
-
// · create 的 key 非空、run 与 view 都是函数(没有 view,卡片上那行就没有可渲染的快照);
|
|
809
|
-
// · 真的注册了长任务,客户端产物里就得出现 kit 那套(useLongTask / LongTaskRow / longTaskDot),
|
|
810
|
-
// 否则用户点完只看到"没反应"。
|
|
811
670
|
if (created.length > 0 || mounted?.longTasks !== undefined) {
|
|
812
671
|
const registry = mounted?.longTasks;
|
|
813
672
|
if (registry !== undefined) {
|
|
@@ -844,11 +703,6 @@ export async function run() {
|
|
|
844
703
|
}
|
|
845
704
|
}
|
|
846
705
|
|
|
847
|
-
// 联动层:agent 动完 / 读完,面板那一格怎么知道要重读。
|
|
848
|
-
//
|
|
849
|
-
// 只查**形状**:`context.render` 不能拿假参数去调 —— 成熟模块在参数形状不对时是**故意 throw** 的
|
|
850
|
-
// (coding-issues 的 render 就是"缺陷引用的形状不对 → throw"),拿假参数调会误伤正确的模块。
|
|
851
|
-
// `factsOf` / `warmReads` 可以安全地拿空参数调一次,看它回的东西形状对不对。
|
|
852
706
|
if (mounted !== undefined) {
|
|
853
707
|
const factsOf = mounted.factsOf;
|
|
854
708
|
if (factsOf !== undefined && typeof factsOf !== 'function') {
|
|
@@ -909,21 +763,6 @@ export async function run() {
|
|
|
909
763
|
}
|
|
910
764
|
}
|
|
911
765
|
|
|
912
|
-
// 声明会写的工具,必须同时认领"这次要不要点头"。
|
|
913
|
-
//
|
|
914
|
-
// 外壳每次调用前都会问套件:writes:true 的工具,needsApproval 不给 verdict.kind==='ask'
|
|
915
|
-
// 就**当场 deny**(原文:"在套件的 needsApproval 里补一条审批(含"这次会发生什么"),别让写静默过去")——
|
|
916
|
-
// 那条错误只有真跑一次才看得见,用户看到的是一次莫名其妙的拒绝。所以门禁在出包前先看一遍。
|
|
917
|
-
//
|
|
918
|
-
// **这条判据只能验到哪一层**(原样写给以后改它的人):
|
|
919
|
-
// · 硬事实:一个 writes:true 的工具,mount() 有没有把 needsApproval 交出来;
|
|
920
|
-
// · 判据能造出合成调用时:对那次调用给的 verdict 形状对不对;verdict 是 ask 时
|
|
921
|
-
// approvalDetail 说没说清"这次会发生什么";
|
|
922
|
-
// · **验不了的**:真实写入调用会不会真的 ask。答案依赖参数的模块("带 confirm: true 才真的写")
|
|
923
|
-
// 在合成调用上依法回 `{ kind: 'read' }` —— 外壳那条路(shared/host/tool-approval.ts)在 read
|
|
924
|
-
// 时**根本不会问 approvalDetail**,所以这里也不再逼它给。这类模块列进 notes 让作者自己确认。
|
|
925
|
-
// · 空参数探会误报:合成调用按声明里的 `parameters` 必填项造(见 probeCallOf),
|
|
926
|
-
// 造不出来时退回空参数并把这件事写进判红那句。
|
|
927
766
|
const writeTools = (manifest?.tools ?? []).filter((one) => one?.writes === true);
|
|
928
767
|
if (writeTools.length > 0) {
|
|
929
768
|
if (mounted === undefined) {
|
|
@@ -960,10 +799,10 @@ export async function run() {
|
|
|
960
799
|
);
|
|
961
800
|
continue;
|
|
962
801
|
}
|
|
963
|
-
|
|
964
|
-
// `{ kind: 'ask', reason, question{zh,en}, fallback{zh,en} }`(要问,且问得清)。
|
|
965
|
-
// 别的返回值(**包括 undefined**)外壳会当场 deny —— 那才是要拦的。
|
|
802
|
+
|
|
966
803
|
const read = verdict?.kind === 'read';
|
|
804
|
+
|
|
805
|
+
const deny = verdict?.kind === 'deny' && text(verdict.reason) !== '';
|
|
967
806
|
const ask =
|
|
968
807
|
verdict?.kind === 'ask' &&
|
|
969
808
|
text(verdict.reason) !== '' &&
|
|
@@ -971,19 +810,18 @@ export async function run() {
|
|
|
971
810
|
text(verdict.question?.en) !== '' &&
|
|
972
811
|
text(verdict.fallback?.zh) !== '' &&
|
|
973
812
|
text(verdict.fallback?.en) !== '';
|
|
974
|
-
if (!read && !ask) {
|
|
813
|
+
if (!read && !deny && !ask) {
|
|
975
814
|
problems.push(
|
|
976
815
|
`${name}: 工具 ${tool.name} 的清单里写着 writes:true,但 needsApproval 对合成调用 ` +
|
|
977
816
|
`${JSON.stringify(call.arguments)} 没给出合法口径 —— ` +
|
|
978
|
-
`要么 { kind: 'read' }(这件事不用问),要么 { kind: '
|
|
817
|
+
`要么 { kind: 'read' }(这件事不用问),要么 { kind: 'deny', reason }(这一通根本跑不成,` +
|
|
818
|
+
`理由原样交给 agent),要么 { kind: 'ask', reason, question: {zh,en}, ` +
|
|
979
819
|
`fallback: {zh,en} }(要问,且问得清)。别的返回值外壳会当场 deny${cannotShape}`,
|
|
980
820
|
);
|
|
981
821
|
continue;
|
|
982
822
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
// 拿它逼模块给"这次会发生什么"就是让判据替外壳问一个外壳不会问的问题。
|
|
986
|
-
if (read) {
|
|
823
|
+
|
|
824
|
+
if (read || deny) {
|
|
987
825
|
readOnProbe.push(tool.name);
|
|
988
826
|
continue;
|
|
989
827
|
}
|
|
@@ -999,8 +837,7 @@ export async function run() {
|
|
|
999
837
|
? text(facts.blocked) !== ''
|
|
1000
838
|
: text(facts.zh) !== '' && text(facts.en) !== '');
|
|
1001
839
|
problems.push(...headlineProblemsOf(name, tool.name, verdict, facts));
|
|
1002
|
-
|
|
1003
|
-
// 专抓"标题里带了可变长字段"—— 第一轮用的是占位短值,对这件事是瞎的(见 budgetProbeOf)。
|
|
840
|
+
|
|
1004
841
|
const longArgs = budgetProbeOf(registered.get(tool.name));
|
|
1005
842
|
if (longArgs !== undefined) {
|
|
1006
843
|
const longCall = { ...call, arguments: longArgs };
|
|
@@ -1046,8 +883,8 @@ export async function run() {
|
|
|
1046
883
|
}
|
|
1047
884
|
if (readOnProbe.length > 0) {
|
|
1048
885
|
probeNotes.push(
|
|
1049
|
-
`写工具 ${readOnProbe.join(' / ')} 按合成调用回了"这次不用问"(kind: 'read')—— ` +
|
|
1050
|
-
`判据只能验到这一步:它是不是"参数里带 confirm 才真的写"那种**合法**口径,` +
|
|
886
|
+
`写工具 ${readOnProbe.join(' / ')} 按合成调用回了"这次不用问"(kind: 'read' 或 'deny')—— ` +
|
|
887
|
+
`判据只能验到这一步:它是不是"参数里带 confirm 才真的写" / "这一通真跑不成"那种**合法**口径,` +
|
|
1051
888
|
`合成调用看不出来(必填项给的是占位值,不是真实写入参数)。作者自己确认一次:` +
|
|
1052
889
|
`真的调用会不会 ask、ask 时 approvalDetail 说没说清`,
|
|
1053
890
|
);
|
|
@@ -1073,7 +910,7 @@ export async function run() {
|
|
|
1073
910
|
}
|
|
1074
911
|
for (const file of suspects) {
|
|
1075
912
|
const text = readFileSync(file, 'utf8');
|
|
1076
|
-
const hit =
|
|
913
|
+
const hit = /(?:@[a-z0-9-]+\/)?dsh-workbench-module-[a-z0-9-]+/.exec(text);
|
|
1077
914
|
if (hit !== null) {
|
|
1078
915
|
problems.push(
|
|
1079
916
|
`${SHELL}: ${relative(ROOT, file)} 里点名了模块包 ${hit[0]} —— ` +
|
|
@@ -1083,10 +920,11 @@ export async function run() {
|
|
|
1083
920
|
}
|
|
1084
921
|
}
|
|
1085
922
|
|
|
923
|
+
problems.push(...buildSupplyIndex(supplyDeclarations).problems);
|
|
924
|
+
|
|
1086
925
|
if (problems.length > 0) {
|
|
1087
926
|
const notes = [`${declared} 个带 dsh 声明的包,其中 ${modules} 个模块`, ...probeNotes, ...headlineNotes];
|
|
1088
|
-
|
|
1089
|
-
// 出来的骨架)。不点出来的话,屏幕上的话会把责任推给那两条声明,而真正该做的是换到工坊里跑。
|
|
927
|
+
|
|
1090
928
|
const bare = [...unresolvedIn].filter(barePackage);
|
|
1091
929
|
if (bare.length > 0) {
|
|
1092
930
|
notes.push(
|
|
@@ -1095,7 +933,7 @@ export async function run() {
|
|
|
1095
933
|
`做法:在工坊里造($DSH_HOME/workbench/diy/packages/module-<名字>),或者先在这个工作区里 pnpm install。`,
|
|
1096
934
|
);
|
|
1097
935
|
}
|
|
1098
|
-
return { status: 'fail', notes, problems };
|
|
936
|
+
return { status: 'fail', notes: [...notes, ...warnings.map((one) => `(警告)${one}`)], problems };
|
|
1099
937
|
}
|
|
1100
938
|
|
|
1101
939
|
return {
|
|
@@ -1107,6 +945,7 @@ export async function run() {
|
|
|
1107
945
|
`单元:${unitList}`,
|
|
1108
946
|
...probeNotes,
|
|
1109
947
|
...headlineNotes,
|
|
948
|
+
...warnings.map((one) => `(警告)${one}`),
|
|
1110
949
|
],
|
|
1111
950
|
problems: [],
|
|
1112
951
|
};
|