dsh-immune-system 1.0.0 → 1.0.1
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
CHANGED
|
@@ -55,8 +55,8 @@ dsh plugin --profile <profile> add dsh-immune-system
|
|
|
55
55
|
**tarball / git**(等价,无需 npm 账号):
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
cd dsh-immune-system && npm pack # → dsh-immune-system
|
|
59
|
-
dsh plugin --profile <profile> add ./dsh-immune-system
|
|
58
|
+
cd dsh-immune-system && npm pack # → dsh-immune-system-<version>.tgz
|
|
59
|
+
dsh plugin --profile <profile> add ./dsh-immune-system-<version>.tgz
|
|
60
60
|
dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -149,10 +149,13 @@ KPI(含预算)、搜索与清除、页签计数、排序、状态圆点、
|
|
|
149
149
|
|
|
150
150
|
> 诚实说明:客户端与路由的**契约**有测试(模拟 `__ModuleLoader__` 装载、假 ctx 验证路由、
|
|
151
151
|
> 跨源拒绝、无 web 服务时不注册、预算透传),布局则用真实 React 18 做过静态渲染自检
|
|
152
|
-
>
|
|
153
|
-
>
|
|
154
|
-
>
|
|
155
|
-
>
|
|
152
|
+
> (含深色模式、空态、窄屏)。
|
|
153
|
+
>
|
|
154
|
+
> **已在真实宿主里验证过**:0.9.8 补齐 `dsh.client` 声明后,`设置 → 插件` 里的 tab
|
|
155
|
+
> 确实出现了(此前 `/plugins/dsh-immune-system/client.js` 返回 404,只有宿主半在跑)。
|
|
156
|
+
> 0.9.12 进一步把它放进 **shadow root**——真实的设置弹窗会用自己的样式表重排裸
|
|
157
|
+
> `table`/`td`/`div`,导致行被压成几像素、下拉框空白;shadow root 双向隔离,
|
|
158
|
+
> 宿主 CSS 进不来、我们的样式也出不去。
|
|
156
159
|
|
|
157
160
|
### 同步与体检
|
|
158
161
|
|
|
@@ -346,9 +349,12 @@ node "<plugin>/dist/zcode/src/cli.js" doctor # 安装自检
|
|
|
346
349
|
|
|
347
350
|
- **模型端点默认拒绝环回**:本地 Ollama 类服务需显式设 `IMMUNE_ALLOW_PRIVATE_ENDPOINT=1`。
|
|
348
351
|
这是防 SSRF 的代价,不是疏漏。
|
|
349
|
-
- **外部 LLM 蒸馏未实测**:规则蒸馏器全流程有测试;接真实模型端点的路径只有 mock
|
|
352
|
+
- **外部 LLM 蒸馏未实测**:规则蒸馏器全流程有测试;接真实模型端点的路径只有 mock 覆盖
|
|
353
|
+
(含批量路径的降级链)。
|
|
350
354
|
- **真实 agent 端到端未验**:验证期间模型额度返回 402,turn 到不了工具阶段。同一条链路改为
|
|
351
355
|
向安装副本投喂真实事件载荷验证(含真 Cordis 宿主的 20 条断言)。
|
|
356
|
+
- **`IMMUNE_GUARD=warn` 仅 ZCode 有效**:DSH 的 `PreToolDecision` 没有携带上下文的字段,
|
|
357
|
+
那边只能"拦或不拦"。
|
|
352
358
|
|
|
353
359
|
---
|
|
354
360
|
|
|
@@ -376,7 +382,7 @@ dsh plugin --profile <profile> add dsh-immune-system
|
|
|
376
382
|
|
|
377
383
|
```bash
|
|
378
384
|
cd dsh-immune-system && npm pack
|
|
379
|
-
dsh plugin --profile <profile> add ./dsh-immune-system
|
|
385
|
+
dsh plugin --profile <profile> add ./dsh-immune-system-<version>.tgz
|
|
380
386
|
dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
|
|
381
387
|
```
|
|
382
388
|
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
* - paths are normalised to a project-relative form so a failure in
|
|
14
14
|
* `./src/a.ts` and a success in `<cwd>/src/a.ts` still match.
|
|
15
15
|
*/
|
|
16
|
-
import { basename, isAbsolute, relative, sep } from 'node:path';
|
|
17
16
|
/** Programs that never change state; their success proves nothing. */
|
|
18
17
|
const PROBE_PROGRAMS = new Set([
|
|
19
18
|
'ls',
|
|
@@ -58,22 +57,32 @@ function pickString(obj, keys) {
|
|
|
58
57
|
/**
|
|
59
58
|
* Normalise a path to a project-relative, forward-slash form.
|
|
60
59
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
60
|
+
* Works on both platforms: drive-letter paths are absolute everywhere (the
|
|
61
|
+
* check is explicit, because POSIX `isAbsolute` does not recognise `C:/…`), and
|
|
62
|
+
* a path inside `cwd` is made relative to it so the same file matches across
|
|
63
|
+
* machines and checkouts.
|
|
63
64
|
*/
|
|
64
65
|
export function normaliseTargetPath(rawPath, cwd) {
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
66
|
+
const p = rawPath.replace(/\\/g, '/').trim();
|
|
67
|
+
if (!p)
|
|
67
68
|
return '';
|
|
68
|
-
if (cwd
|
|
69
|
-
const
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
if (cwd) {
|
|
70
|
+
const base = cwd.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
|
|
71
|
+
if (base) {
|
|
72
|
+
const lower = p.toLowerCase();
|
|
73
|
+
if (lower === base)
|
|
74
|
+
return '';
|
|
75
|
+
if (lower.startsWith(`${base}/`))
|
|
76
|
+
return p.slice(base.length + 1);
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
if (/^[A-Za-z]:[\/]/.test(p) || p.startsWith('/')) {
|
|
80
|
+
// Outside the project: collapse to the basename (matches the original
|
|
81
|
+
// Windows behaviour and keeps signatures comparable across checkouts).
|
|
82
|
+
const parts = p.split('/').filter(Boolean);
|
|
83
|
+
return parts[parts.length - 1] ?? p;
|
|
84
|
+
}
|
|
85
|
+
return p.replace(/^\.\//, '');
|
|
77
86
|
}
|
|
78
87
|
/** Split a shell command into `program` and its first non-flag subcommand. */
|
|
79
88
|
export function parseCommand(command) {
|
|
@@ -83,11 +92,13 @@ export function parseCommand(command) {
|
|
|
83
92
|
.trim()
|
|
84
93
|
.split(/\s+/)
|
|
85
94
|
.filter(Boolean);
|
|
86
|
-
// Skip leading env assignments (FOO=bar cmd)
|
|
95
|
+
// Skip leading env assignments (FOO=bar cmd), then strip a leading path from
|
|
96
|
+
// the program token ("C:\tools\npm.cmd test" -> "npm.cmd") on any platform.
|
|
87
97
|
let index = 0;
|
|
88
98
|
while (index < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index] ?? ''))
|
|
89
99
|
index += 1;
|
|
90
|
-
const
|
|
100
|
+
const programToken = (tokens[index] ?? '').replace(/^["']|["']$/g, '');
|
|
101
|
+
const program = (programToken.split(/[\\/]/).pop() ?? programToken).toLowerCase();
|
|
91
102
|
let sub = '';
|
|
92
103
|
for (let i = index + 1; i < tokens.length; i += 1) {
|
|
93
104
|
const token = tokens[i] ?? '';
|
|
@@ -175,4 +186,3 @@ export function canCloseEpisode(failedSignature, incoming) {
|
|
|
175
186
|
}
|
|
176
187
|
return !failedTarget || failedTarget === '?' || !incomingTarget || incomingTarget === '?';
|
|
177
188
|
}
|
|
178
|
-
export { sep as pathSeparator };
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* identifies another clone of the plugin wherever it sits on disk.
|
|
17
17
|
*/
|
|
18
18
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
19
|
-
import {
|
|
19
|
+
import { resolve } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
21
|
/** Package names that mean "this is the immune system itself". */
|
|
22
22
|
const SELF_PACKAGE_NAMES = new Set(['dsh-immune-system', 'dsh-plugin-immune']);
|
|
@@ -43,14 +43,37 @@ function safeRealpath(path) {
|
|
|
43
43
|
function normalizePath(value) {
|
|
44
44
|
return value.replace(/\\\\/g, '\\').replace(/\\/g, '/').toLowerCase();
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Parent directory that treats BOTH separators as separators, on every platform.
|
|
48
|
+
*
|
|
49
|
+
* `path.dirname` uses the platform separator: on Linux a Windows path like
|
|
50
|
+
* `D:\work\app\src\main.ts` has no `/`, so dirname returns '.' and an upward
|
|
51
|
+
* walk would scan the current workspace instead of the named path — that exact
|
|
52
|
+
* false positive failed CI (the workspace root has a package.json whose name is
|
|
53
|
+
* this plugin's).
|
|
54
|
+
*
|
|
55
|
+
* A drive root (`D:`, `D:/`) has no parent and returns null: resolving against
|
|
56
|
+
* it would use the process's drive-relative cwd, which is how the CI false
|
|
57
|
+
* positive happened.
|
|
58
|
+
*/
|
|
59
|
+
function parentDir(path) {
|
|
60
|
+
const normalised = path.replace(/\\/g, '/');
|
|
61
|
+
const cut = normalised.lastIndexOf('/');
|
|
62
|
+
if (cut <= 0)
|
|
63
|
+
return null;
|
|
64
|
+
const parent = normalised.slice(0, cut);
|
|
65
|
+
if (/^[A-Za-z]:$/.test(parent))
|
|
66
|
+
return null; // drive root
|
|
67
|
+
return parent.length >= 2 ? parent : null;
|
|
68
|
+
}
|
|
46
69
|
/** Nearest directory at or above `start` that holds a package.json. */
|
|
47
70
|
function findPackageRoot(start) {
|
|
48
71
|
let dir = start;
|
|
49
72
|
for (let i = 0; i < MAX_UPWARD; i += 1) {
|
|
50
73
|
if (existsSync(resolve(dir, 'package.json')))
|
|
51
74
|
return dir;
|
|
52
|
-
const parent =
|
|
53
|
-
if (parent === dir)
|
|
75
|
+
const parent = parentDir(dir);
|
|
76
|
+
if (parent === null || parent === dir)
|
|
54
77
|
return null;
|
|
55
78
|
dir = parent;
|
|
56
79
|
}
|
|
@@ -77,7 +100,7 @@ export function moduleRoot() {
|
|
|
77
100
|
try {
|
|
78
101
|
const here = fileURLToPath(import.meta.url);
|
|
79
102
|
const real = safeRealpath(here) ?? here;
|
|
80
|
-
const root = findPackageRoot(
|
|
103
|
+
const root = findPackageRoot(parentDir(real) ?? real);
|
|
81
104
|
cachedModuleRoot = root ? normalizePath(root) : null;
|
|
82
105
|
}
|
|
83
106
|
catch {
|
|
@@ -124,7 +147,9 @@ export function isSelfEpisode(trace, markers = []) {
|
|
|
124
147
|
// Fingerprint: another clone of the plugin, wherever it lives.
|
|
125
148
|
for (const candidate of collectPaths(trace)) {
|
|
126
149
|
const real = safeRealpath(candidate) ?? candidate;
|
|
127
|
-
|
|
150
|
+
// Starting from the file path itself (not its dir) is deliberate: on POSIX
|
|
151
|
+
// a Windows-style path has no dirname, so parentDir would lose the walk.
|
|
152
|
+
const pkgRoot = findPackageRoot(real);
|
|
128
153
|
if (!pkgRoot)
|
|
129
154
|
continue;
|
|
130
155
|
const name = packageNameAt(pkgRoot);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { createInterface } from 'node:readline';
|
|
21
21
|
import { pathToFileURL } from 'node:url';
|
|
22
22
|
import { ImmuneStore } from '../../core/store/index.js';
|
|
23
|
+
import { renderSkillMarkdown } from '../../core/export.js';
|
|
23
24
|
const SERVER_INFO = { name: 'dsh-immune-system', version: '0.8.0' };
|
|
24
25
|
const TOOLS = [
|
|
25
26
|
{
|
|
@@ -39,6 +40,17 @@ const TOOLS = [
|
|
|
39
40
|
description: 'Counts: active/retired rules, total injections, queued distillation jobs, database path.',
|
|
40
41
|
inputSchema: { type: 'object', properties: {} },
|
|
41
42
|
},
|
|
43
|
+
{
|
|
44
|
+
name: 'immune_export',
|
|
45
|
+
description: 'Render the proven rules (hits >= threshold) as a SKILL.md document and return its text. Save the text to share the library with teammates or commit it.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
minHits: { type: 'number', description: 'Minimum hit count. Default 3.' },
|
|
50
|
+
title: { type: 'string', description: 'Skill title. Default "project-pitfalls".' },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
42
54
|
{
|
|
43
55
|
name: 'immune_learn',
|
|
44
56
|
description: 'Teach a rule directly, without waiting for a failed-then-fixed episode. Use for conventions the agent keeps getting wrong.',
|
|
@@ -116,6 +128,16 @@ export function callTool(store, name, args) {
|
|
|
116
128
|
`queued distill jobs: ${store.pendingJobCount()}\n` +
|
|
117
129
|
`database: ${store.dbPath}`);
|
|
118
130
|
}
|
|
131
|
+
case 'immune_export': {
|
|
132
|
+
const minHits = typeof args.minHits === 'number' && args.minHits > 0 ? args.minHits : 3;
|
|
133
|
+
const title = typeof args.title === 'string' && args.title.trim() ? args.title.trim() : 'project-pitfalls';
|
|
134
|
+
const rows = store.exportable(minHits);
|
|
135
|
+
if (rows.length === 0) {
|
|
136
|
+
return `No rule has reached ${minHits} hits yet; nothing worth sharing.`;
|
|
137
|
+
}
|
|
138
|
+
const md = renderSkillMarkdown(rows, { minHits, title });
|
|
139
|
+
return `Exported ${rows.length} rules as SKILL.md:\n\n${md}`;
|
|
140
|
+
}
|
|
119
141
|
case 'immune_learn': {
|
|
120
142
|
const trigger = typeof args.trigger === 'string' ? args.trigger : '';
|
|
121
143
|
const symptom = typeof args.symptom === 'string' ? args.symptom : '';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-immune-system",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Self-evolving pitfall immunity for coding agents: capture every failed-then-fixed tool episode, distil it into a reusable antibody, and inject the relevant ones before the agent repeats the mistake.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/dsh/src/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dsh": {
|
|
25
25
|
"id": "dsh-immune-system",
|
|
26
26
|
"name": "DSH Immune System",
|
|
27
|
-
"version": "1.0.
|
|
27
|
+
"version": "1.0.1",
|
|
28
28
|
"repo": "meat-daemon-404/dsh-plugin-immune",
|
|
29
29
|
"permissions": "读取工具调用结果与斜杠命令输入;在本机写 SQLite 抗体库(默认落在 $DSH_HOME/storages/dsh-immune-system/ 下按项目隔离);仅在显式配置 IMMUNE_LLM_BASE_URL 与 IMMUNE_LLM_MODEL 时才向该端点发起请求;不读取其它插件的配置,不执行任意命令",
|
|
30
30
|
"bundle": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-immune-system",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Self-evolving pitfall immunity for DeepSeek Harness: capture every failed-then-fixed tool episode, distil it into a reusable antibody stored in SQLite+FTS5, and inject the relevant ones before the agent repeats the mistake.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"dsh": {
|
|
25
25
|
"id": "dsh-immune-system",
|
|
26
26
|
"name": "DSH Immune System",
|
|
27
|
-
"version": "1.0.
|
|
27
|
+
"version": "1.0.1",
|
|
28
28
|
"repo": "meat-daemon-404/dsh-plugin-immune",
|
|
29
29
|
"permissions": "读取工具调用结果与斜杠命令输入;在本机写 SQLite 抗体库(默认落在 $DSH_HOME/storages/dsh-immune-system/ 下按项目隔离);仅在显式配置 IMMUNE_LLM_BASE_URL 与 IMMUNE_LLM_MODEL 时才向该端点发起请求;不读取其它插件的配置,不执行任意命令",
|
|
30
30
|
"bundle": {
|