@bushizhanghan/dsh-publish-gate 0.5.0 → 0.6.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 +74 -0
- package/README.md +2 -1
- package/lib/engine.js +42 -1
- package/package.json +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
- New rule `tool-undeclared-param`: a tool whose `execute` reads `args.x` that
|
|
6
|
+
`parameters.properties` never declares. The host renders `parameters` into the
|
|
7
|
+
prompt and passes the model nothing else, so that value is undefined on every
|
|
8
|
+
real call — the code path exists and never runs, and nothing throws.
|
|
9
|
+
Covers `args.x`, `args['x']` and `const { x } = args`.
|
|
10
|
+
|
|
11
|
+
## 0.5.0
|
|
12
|
+
|
|
13
|
+
- New layer: traceability. `git-uncommitted` (files that ship in the tarball are
|
|
14
|
+
not committed), `git-tag-missing` (no `v<version>` tag on HEAD),
|
|
15
|
+
`git-tag-elsewhere` (the tag exists but points at another commit).
|
|
16
|
+
- The incident behind it: a 1.2.7 went to npm while the GitHub release still
|
|
17
|
+
said 1.2.6 and the market entry still pointed at the old tarball.
|
|
18
|
+
|
|
19
|
+
## 0.4.0
|
|
20
|
+
|
|
21
|
+
- New layer: dependency truth. `undeclared-import` (lib/ imports a package
|
|
22
|
+
package.json never declares), `dep-in-dev-only` (only devDependencies satisfy
|
|
23
|
+
it — npm never installs those for the consumer), `unused-dependency` (info).
|
|
24
|
+
- New rule `version-behind-latest`: publishing under the live `latest` is
|
|
25
|
+
accepted by npm and reaches nobody.
|
|
26
|
+
|
|
27
|
+
## 0.3.4
|
|
28
|
+
|
|
29
|
+
- `tool-call-slow` split from `tool-call-throws`: a timeout is not a defect.
|
|
30
|
+
|
|
31
|
+
## 0.3.3
|
|
32
|
+
|
|
33
|
+
- `publish_gate_tools` accepts `call: ["name"]` to really invoke a tool, opt-in
|
|
34
|
+
because some tools open applications or write files.
|
|
35
|
+
|
|
36
|
+
## 0.3.2
|
|
37
|
+
|
|
38
|
+
- New rule `node-too-old`: `engines.node` says one thing, `lib/` uses an API from
|
|
39
|
+
a newer Node. npm only warns on `engines`, so the plugin installs and then
|
|
40
|
+
throws at import.
|
|
41
|
+
|
|
42
|
+
## 0.3.1
|
|
43
|
+
|
|
44
|
+
- `publish_gate_commands` emits commands that actually run on this machine
|
|
45
|
+
(`cmd /c "npm publish --access public"` on Windows) and pairs the publish step
|
|
46
|
+
with a `publish_gate_verify` shasum check.
|
|
47
|
+
|
|
48
|
+
## 0.3.0
|
|
49
|
+
|
|
50
|
+
- New layer: the browser half. `checkClientHalf` / `checkClientRuntime` load the
|
|
51
|
+
bundle against a fake `window.__ModuleLoader__` and report a bundle that never
|
|
52
|
+
loads, never returns `apply`, throws, registers nothing, or uses a service its
|
|
53
|
+
`inject` array does not declare.
|
|
54
|
+
- New layer: market readiness — the repo is already listed, another entry
|
|
55
|
+
publishes the same npm name, or the repo is under 24 hours old (the gate fails
|
|
56
|
+
and reruns itself).
|
|
57
|
+
- New rules: `client-without-platform`, `pack-omits-client`, `pack-ships-secret`,
|
|
58
|
+
`private-field-set`, `publish-config-registry`, `tool-bad-parameter-type`,
|
|
59
|
+
`entry-path-missing`, `bundle-patch-not-declared`, `inject-unused-service`.
|
|
60
|
+
- New tool `publish_gate_client` (9 tools).
|
|
61
|
+
|
|
62
|
+
## 0.2.0
|
|
63
|
+
|
|
64
|
+
- New layers: artifact (`npm pack --dry-run` — what npm will really ship) and
|
|
65
|
+
runtime (import the entry in an isolated child process, call `apply`, report a
|
|
66
|
+
throwing import, a throwing apply, missing execute, duplicate tool names, a
|
|
67
|
+
throwing `presentCall`, and card kinds the host does not render).
|
|
68
|
+
- New tools `publish_gate_tools` and `publish_gate_verify`.
|
|
69
|
+
- Fixed: `bad-repo-url` never fired for the object form of `repository`.
|
|
70
|
+
|
|
71
|
+
## 0.1.0
|
|
72
|
+
|
|
73
|
+
- First release: manifest rules, `cordis.patch.yml` insert checks, README and
|
|
74
|
+
test presence, npm name and registry state.
|
package/README.md
CHANGED
|
@@ -102,6 +102,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
|
|
|
102
102
|
| `tool-present-call-throws` | `presentCall` 抛——调用记录渲染时炸 |
|
|
103
103
|
| `tool-bad-card-kind` | `card: 'generic'` 的 `kind` 不在宿主认识的那一组里(`other` / `execute` / `search` / `read` / `edit` / `delete` / `fetch`)。注册不报错,卡片渲染坏掉——我五个插件一起踩过 |
|
|
104
104
|
| `tool-bad-parameter-type` | `parameters` 里某个 `type` 写错(`"strin"`)。宿主把 `parameters` 渲染进 prompt、但**从不校验**调用值,所以这个 typo 只是悄悄把类型提示弄丢 |
|
|
105
|
+
| `tool-undeclared-param` | `execute` 读了 `args.x`,但 `parameters.properties` 里没有 `x`。模型只能传 schema 声明过的参数,所以这个值在真实调用里永远是 `undefined`——代码在,永远不跑,也不报错。`args.x`、`args['x']`、`const { x } = args` 三种写法都认 |
|
|
105
106
|
|
|
106
107
|
### 浏览器半边:侧栏面板到底会不会出现
|
|
107
108
|
|
|
@@ -210,7 +211,7 @@ dsh plugin --profile desktop add @bushizhanghan/dsh-publish-gate
|
|
|
210
211
|
## 开发
|
|
211
212
|
|
|
212
213
|
```sh
|
|
213
|
-
node --test #
|
|
214
|
+
node --test # 83 个引擎单测
|
|
214
215
|
node test/integration.test.mjs # 26 条集成断言(9 个真实插件作 fixture)
|
|
215
216
|
```
|
|
216
217
|
|
package/lib/engine.js
CHANGED
|
@@ -1057,6 +1057,31 @@ const walkParams = (schema, path) => {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
if (schema.items) walkParams(schema.items, path + '[]')
|
|
1059
1059
|
}
|
|
1060
|
+
// The model only ever sends the arguments \`parameters\` declares — the host
|
|
1061
|
+
// renders that schema into the prompt and passes nothing else. So an execute
|
|
1062
|
+
// that reads args.dir while the schema never mentions dir is unreachable code:
|
|
1063
|
+
// every call sees undefined, and the tool looks broken with no error anywhere.
|
|
1064
|
+
const ARG_DOT = /\\bargs\\s*\\??\\.\\s*([A-Za-z_$][\\w$]*)/g
|
|
1065
|
+
const ARG_INDEX = /\\bargs\\s*\\[\\s*['"]([^'"]+)['"]\\s*\\]/g
|
|
1066
|
+
const ARG_DESTRUCTURE = /\\{\\s*([^{}]+?)\\s*\\}\\s*=\\s*args\\b/g
|
|
1067
|
+
const undeclaredArgs = (def) => {
|
|
1068
|
+
const props = def?.parameters?.properties
|
|
1069
|
+
if (!props || typeof props !== 'object') return []
|
|
1070
|
+
const declared = Object.keys(props)
|
|
1071
|
+
if (typeof def.execute !== 'function') return []
|
|
1072
|
+
let src = ''
|
|
1073
|
+
try { src = def.execute.toString() } catch { return [] }
|
|
1074
|
+
const reads = []
|
|
1075
|
+
for (const m of src.matchAll(ARG_DOT)) reads.push(m[1])
|
|
1076
|
+
for (const m of src.matchAll(ARG_INDEX)) reads.push(m[1])
|
|
1077
|
+
for (const m of src.matchAll(ARG_DESTRUCTURE)) {
|
|
1078
|
+
for (const part of m[1].split(',')) {
|
|
1079
|
+
const key = part.split(':')[0].split('=')[0].replace(/\\.\\.\\./, '').trim()
|
|
1080
|
+
if (/^[A-Za-z_$][\\w$]*$/.test(key)) reads.push(key)
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return [...new Set(reads)].filter((r) => !declared.includes(r))
|
|
1084
|
+
}
|
|
1060
1085
|
const noopService = () => new Proxy(function () {}, {
|
|
1061
1086
|
get: (target, prop) => {
|
|
1062
1087
|
// Own properties win, or ctx.tools would resolve to another no-op proxy
|
|
@@ -1086,6 +1111,7 @@ ctx.tools = {
|
|
|
1086
1111
|
hasExecute: typeof def?.execute === 'function',
|
|
1087
1112
|
hasOutputSchema: Boolean(def && def.output && def.output.schema),
|
|
1088
1113
|
cardKind: card,
|
|
1114
|
+
undeclaredParams: undeclaredArgs(def),
|
|
1089
1115
|
run: typeof def?.execute === 'function' ? def.execute : null,
|
|
1090
1116
|
})
|
|
1091
1117
|
walkParams(def?.parameters, typeof def?.name === 'string' ? def.name : '(unnamed)')
|
|
@@ -1229,6 +1255,21 @@ export async function checkToolRegistrations(dir, opts = {}) {
|
|
|
1229
1255
|
),
|
|
1230
1256
|
)
|
|
1231
1257
|
}
|
|
1258
|
+
|
|
1259
|
+
// The model only sends what `parameters` declares, so a read of anything
|
|
1260
|
+
// else is unreachable: args.dir is undefined on every real call, and the
|
|
1261
|
+
// tool looks broken while nothing throws.
|
|
1262
|
+
for (const name of Array.isArray(t.undeclaredParams) ? t.undeclaredParams : []) {
|
|
1263
|
+
out.push(
|
|
1264
|
+
F(
|
|
1265
|
+
'tool-undeclared-param',
|
|
1266
|
+
'warn',
|
|
1267
|
+
`tool "${label}" reads args.${name}, which parameters never declares`,
|
|
1268
|
+
'The host renders parameters into the prompt and passes the model nothing else, so this value is always undefined on a real call. The code path exists and never runs.',
|
|
1269
|
+
`Add "${name}" to parameters.properties, or stop reading it.`,
|
|
1270
|
+
),
|
|
1271
|
+
)
|
|
1272
|
+
}
|
|
1232
1273
|
}
|
|
1233
1274
|
const badParams = Array.isArray(payload.badParamTypes) ? payload.badParamTypes : []
|
|
1234
1275
|
for (const where of badParams) {
|
|
@@ -1918,7 +1959,7 @@ const RULE_ORDER = [
|
|
|
1918
1959
|
'inject-missing-tools', 'inject-missing-web', 'inject-missing-webServer', 'inject-missing-timer',
|
|
1919
1960
|
'inject-unused-service',
|
|
1920
1961
|
'tool-missing-name', 'tool-duplicate-name', 'tool-missing-execute', 'tool-present-call-throws',
|
|
1921
|
-
'tool-bad-card-kind', 'tool-missing-output-schema', 'tool-none-registered', 'tool-bad-parameter-type', 'tool-call-throws', 'tool-call-slow',
|
|
1962
|
+
'tool-bad-card-kind', 'tool-missing-output-schema', 'tool-none-registered', 'tool-bad-parameter-type', 'tool-undeclared-param', 'tool-call-throws', 'tool-call-slow',
|
|
1922
1963
|
'lib-debugger', 'lib-console-log', 'lib-hardcoded-path', 'lib-url-pathname', 'node-too-old', 'stale-build', 'hand-edited-build',
|
|
1923
1964
|
'undeclared-import', 'dep-in-dev-only', 'unused-dependency',
|
|
1924
1965
|
'pack-omits-lib', 'pack-omits-patch', 'pack-omits-client', 'pack-oversize', 'pack-omits-readme', 'pack-omits-license', 'pack-ships-secret',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bushizhanghan/dsh-publish-gate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pre-publish gate for DSH plugins in five layers: manifest, dependencies, artifact, runtime, browser half — plus traceability (can you point at the bytes you shipped?). 发布前门禁:清单、依赖、产物、运行时、浏览器半边,外加可追溯性。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"lib",
|
|
14
14
|
"cordis.patch.yml",
|
|
15
15
|
"README.md",
|
|
16
|
-
"LICENSE"
|
|
16
|
+
"LICENSE",
|
|
17
|
+
"CHANGELOG.md"
|
|
17
18
|
],
|
|
18
19
|
"keywords": [
|
|
19
20
|
"dsh",
|