@perrylink/dsh-plugin-doctor 0.1.2 → 0.1.4
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/README.md +8 -2
- package/lib/checks-cordis.mjs +15 -1
- package/lib/checks-package.mjs +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,5 +63,11 @@ SURVEY.md 全渠道检测方法梳理 + 判据出处
|
|
|
63
63
|
|
|
64
64
|
## 状态
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
正式仓库:GitHub `PerryLink/dsh-plugin-doctor`(MIT 外 Apache-2.0),npm `@perrylink/dsh-plugin-doctor`
|
|
67
|
+
(latest=0.1.3,2026-09-07)。CI 用法:
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
npx --yes @perrylink/dsh-plugin-doctor@0.1.3 --repo . --no-smoke --only "静态·包结构,静态·cordis 契约扫描"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
35 个插件仓已内置 `.github/workflows/plugin-doctor.yml`(install→build→npx 静态 R/K 门禁)。
|
package/lib/checks-cordis.mjs
CHANGED
|
@@ -65,6 +65,8 @@ export function addChecks(doctor, ctx) {
|
|
|
65
65
|
}
|
|
66
66
|
const candidates = new Map()
|
|
67
67
|
for (const { f, text } of all) {
|
|
68
|
+
// 局部类型参数(如 createState(ctx: { credentials: ... }),非 cordis Context)——跳过该文件
|
|
69
|
+
if (/ctx\s*:\s*\{/.test(text)) continue
|
|
68
70
|
for (const m of text.matchAll(/ctx\.([A-Za-z_$][\w$]*)/g)) {
|
|
69
71
|
const name = m[1]
|
|
70
72
|
if (CTX_INTRINSIC.has(name) || injectList.has(name)) continue
|
|
@@ -100,8 +102,20 @@ export function addChecks(doctor, ctx) {
|
|
|
100
102
|
for (let i = 0; i < lines.length; i++) {
|
|
101
103
|
const line = lines[i]
|
|
102
104
|
if (!/(setInterval|setTimeout|setImmediate|process\.on|addEventListener\()/.test(line)) continue
|
|
105
|
+
// 变量级清理检测:同文件存在 clearTimeout/clearInterval/unref/removeEventListener 对应清理 → 自清理,跳过
|
|
106
|
+
// (变量声明可能在上一行,如三元表达式换行的 `const timer = cond\n ? setInterval(...)`)
|
|
107
|
+
let varMatch = line.match(/(?:const|let)\s+([A-Za-z_$][\w$]*)\s*=/)
|
|
108
|
+
if (!varMatch && i > 0) varMatch = lines[i - 1].match(/(?:const|let)\s+([A-Za-z_$][\w$]*)\s*=/)
|
|
109
|
+
const listenerCleaned = line.includes('addEventListener') && /removeEventListener\(/.test(text)
|
|
110
|
+
if (varMatch) {
|
|
111
|
+
const name = varMatch[1]
|
|
112
|
+
const timerCleaned = new RegExp(`clear(?:Timeout|Interval)\\(\\s*${name}\\b|${name}\\??\\.\\s*unref\\s*\\(`).test(text)
|
|
113
|
+
if (timerCleaned || listenerCleaned) continue
|
|
114
|
+
} else if (listenerCleaned) {
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
103
117
|
const before = lines.slice(Math.max(0, i - 3), i).join('\n')
|
|
104
|
-
if (!/ctx\.effect|ctx\.
|
|
118
|
+
if (!/ctx\.effect|ctx\.on\b/.test(before) && !/return\s*\(\)\s*=>/.test(before + line)) {
|
|
105
119
|
hits.push(`${short(f)}:${i + 1} ${line.trim()}`)
|
|
106
120
|
}
|
|
107
121
|
}
|
package/lib/checks-package.mjs
CHANGED
|
@@ -67,11 +67,13 @@ export function addChecks(doctor, ctx) {
|
|
|
67
67
|
const p = path.resolve(repoPath, patch)
|
|
68
68
|
if (!existsSync(p)) return fail(`patch 文件不存在: ${patch}`)
|
|
69
69
|
const text = readFileSync(p, 'utf8')
|
|
70
|
+
// YAML 注释行剔除后再做启发式(示例配置注释里常出现 name: xxx)
|
|
71
|
+
const active = text.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join('\n')
|
|
70
72
|
const problems = []
|
|
71
|
-
if (!/-\s+insert\s*:/.test(
|
|
72
|
-
const ids = [...
|
|
73
|
+
if (!/-\s+insert\s*:/.test(active)) problems.push('未找到 "- insert:" 结构')
|
|
74
|
+
const ids = [...active.matchAll(/(?:^|\n)\s*-?\s*id:\s*["']?([^"'\s]+)/g)].map((m) => m[1])
|
|
73
75
|
if (!ids.length) problems.push('未找到 id 行(行 id 必须存在,供上层整行替换定位)')
|
|
74
|
-
const names = [...
|
|
76
|
+
const names = [...active.matchAll(/(?:^|\n)\s*name:\s*["']?([^"'\s,]+)|[,\s]name:\s*["']?([^"'\s,]+)/g)]
|
|
75
77
|
.map((m) => m[1] ?? m[2]).filter(Boolean)
|
|
76
78
|
const badNames = names.filter((n) => n !== pkgName && !n.startsWith(`${pkgName}/`))
|
|
77
79
|
if (names.length && badNames.length) problems.push(`行 name 与包名不一致: ${[...new Set(badNames)].join(', ')}(name 必须经 profile node_modules 解析,应为包名)`)
|
|
@@ -121,7 +123,7 @@ export function addChecks(doctor, ctx) {
|
|
|
121
123
|
const importsCordis = srcFiles.some((f) => { try { return /from\s+['"]@deepseek-ai\/cordis['"]|require\(['"]@deepseek-ai\/cordis['"]\)/.test(readFileSync(f, 'utf8')) } catch { return false } })
|
|
122
124
|
if (!peer && !dep) {
|
|
123
125
|
if (importsCordis) problems.push('源码 import @deepseek-ai/cordis 但未声明任何依赖')
|
|
124
|
-
else
|
|
126
|
+
else return pass('未声明 @deepseek-ai/cordis 且源码无 import(纯 JS 仓可无 cordis 运行时依赖)')
|
|
125
127
|
} else if (!peer) {
|
|
126
128
|
problems.push('@deepseek-ai/cordis 只在 dependencies(官方口径:peerDependencies + devDependencies 同时声明)')
|
|
127
129
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perrylink/dsh-plugin-doctor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Zero-dependency static + sandbox smoke detector for DeepSeek Harness (dsh) plugins: package-structure gates (R), cordis contract scans (K), keyless-headless sandbox smoke (D), and ecosystem-listing checks (CC).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "doctor.mjs",
|