dsh-immune-system 0.9.0 → 0.9.2

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
@@ -1,5 +1,10 @@
1
1
  # dsh-plugin-immune
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/dsh-immune-system)](https://www.npmjs.com/package/dsh-immune-system)
4
+ ![license](https://img.shields.io/badge/license-MIT-green)
5
+ ![node](https://img.shields.io/badge/node-%E2%89%A522.5-blue)
6
+ [![DSH plugin](https://img.shields.io/badge/DSH-plugin%20hub-4f9cf9)](https://dsh-plugin.org/plugins)
7
+
3
8
  **自进化避坑免疫系统** — 让 Agent 踩一次坑,就自己长记性。
4
9
 
5
10
  Agent 踩了坑、修好了,却没人写文档——这次的经验被自动提炼成一条**抗体**。
@@ -10,7 +15,7 @@ Agent 踩了坑、修好了,却没人写文档——这次的经验被自动
10
15
  > mistake can repeat.
11
16
 
12
17
  支持两个宿主:**DSH**(DeepSeek Harness,已按真实宿主契约实测)与 **ZCode**(按实测 hook 契约)。
13
- 两者共用同一份 `core/`。
18
+ 两者共用同一份 `core/`,npm 包同名发布,一次踩坑、处处免疫。
14
19
 
15
20
  [中文](#中文) · [English](#english)
16
21
 
@@ -39,29 +44,24 @@ Agent 踩了坑、修好了,却没人写文档——这次的经验被自动
39
44
 
40
45
  #### DSH
41
46
 
42
- 插件包就是 `dsh-immune-system/` 目录本身(`package.json` 带 `main`、`dsh.bundle.patch`
43
- 与 `permissions` 声明,`dist/` 随包提交,安装时无需构建)。
44
-
45
- **方式一:tarball(不需要 npm 账号,已验证)**
47
+ **npm(现在可用,最简)**:
46
48
 
47
49
  ```bash
48
- cd dsh-immune-system && npm pack # → dsh-immune-system-0.3.8.tgz
49
- dsh plugin add ./dsh-immune-system-0.3.8.tgz
50
+ dsh plugin --profile <profile> add dsh-immune-system
50
51
  ```
51
52
 
52
- 冒烟验证过:装进空项目后 `import('dsh-immune-system')` 能解析到 `apply()`,
53
- `cordis.patch.yml` 随包到位。
53
+ 包发布在 https://www.npmjs.com/package/dsh-immune-system ,`dist/` 预构建随包。
54
54
 
55
- **方式二:npm(需先发布)**
55
+ **tarball / git**(等价,无需 npm 账号):
56
56
 
57
57
  ```bash
58
- cd dsh-immune-system && npm publish # 需要你自己的 npm 账号
59
- dsh plugin add dsh-immune-system
58
+ cd dsh-immune-system && npm pack # → dsh-immune-system-0.9.0.tgz
59
+ dsh plugin --profile <profile> add ./dsh-immune-system-0.9.0.tgz
60
+ dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
60
61
  ```
61
62
 
62
- **方式三:git** —— `dsh plugin add github:owner/repo` 会在安装时用 `prepare` 构建,
63
- 而 pnpm 默认阻止依赖的构建脚本,需要把 pnpm 打印的 key 加进 profile 的
64
- `pnpm-workspace.yaml → allowBuilds` 才能装。除非你就要这个形态,否则用前两种。
63
+ `git:` 安装会在安装时跑 `prepare` 构建,被 pnpm 默认拦截——需要把 pnpm 打印的 key
64
+ 加进 profile 的 `pnpm-workspace.yaml → allowBuilds`。除非你就要这个形态,否则用前两种。
65
65
 
66
66
  装完**重启 DSH**(bundle 是启动时装配的,运行中的进程不会自己加载)。
67
67
  `<profile>` 就是 `~/.dsh/profiles/` 下你实际在用的那个(桌面端通常是 `desktop`)。
@@ -99,9 +99,11 @@ node "<zcode.cjs 路径>" plugins install dsh-immune-system@dev-dsh-plugin-immun
99
99
  | `/dsh-immune-system:immune` | ZCode | 同上(加 `--all`) |
100
100
  | `/dsh-immune-system:immune-export` | ZCode | 同上 |
101
101
  | `/dsh-immune-system:immune-forget` | ZCode | 同上(`<id>` / `--last` / `--prune` / `--purge`) |
102
+ | `/dsh-immune-system:immune-learn` | ZCode | 手动教一条规则 |
102
103
  | `/dsh-immune-system:immune-ui` | ZCode | 启动本地仪表盘,给出地址 |
103
104
  | `cli.js ui [--port N]` | 两者 | 同上(前台运行,默认 7788,端口占用自动顺延) |
104
105
  | `cli.js learn <trigger> [symptom] <prescription>` | 两者 | 手动教一条规则(借鉴 pro-workflow 的 `/learn-rule`) |
106
+ | `cli.js search <query>` | 两者 | 只读检索——与仪表盘/MCP 同一条不计数通道 |
105
107
  | `cli.js export-json <path>` / `import-json <path>` | 两者 | 库快照导出/导入——跨机器同步、git 提交、团队共享 |
106
108
  | `cli.js doctor` | 两者 | 安装健康自检:node 版本、FTS5、数据库、marker、MCP 脚本 |
107
109
 
@@ -301,19 +303,25 @@ against the real host contract) and **ZCode** (verified against its hooks).
301
303
 
302
304
  ### Install
303
305
 
304
- **DSH** — the plugin is a bundle: a directory carrying `package.json` (with
305
- `dsh.bundle.patch`) plus `cordis.patch.yml`, listed in the profile's
306
- `dsh.profile.bundles`. From a local checkout (verified path):
307
-
308
- ```powershell
309
- New-Item -ItemType Junction `
310
- -Path "$env:USERPROFILE\.dsh\profiles\<profile>\node_modules\dsh-immune-system" `
311
- -Target "<repo>\dsh-immune-system"
312
- # then in that profile's package.json:
313
- # dependencies["dsh-immune-system"] = "link:<repo>/dsh-immune-system"
314
- # append "dsh-immune-system" to dsh.profile.bundles
306
+ **npm** — published as [`dsh-immune-system`](https://www.npmjs.com/package/dsh-immune-system),
307
+ prebuilt, no toolchain needed:
308
+
309
+ ```bash
310
+ dsh plugin --profile <profile> add dsh-immune-system
315
311
  ```
316
312
 
313
+ **tarball / git** (equivalent, no npm account required):
314
+
315
+ ```bash
316
+ cd dsh-immune-system && npm pack
317
+ dsh plugin --profile <profile> add ./dsh-immune-system-0.9.0.tgz
318
+ dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
319
+ ```
320
+
321
+ `git:` installs build at install time via `prepare`, which pnpm blocks until
322
+ allowlisted — see the Chinese section above for the exact fix. Prefer npm or the
323
+ tarball.
324
+
317
325
  **Restart DSH** afterwards — bundles are composed at startup.
318
326
 
319
327
  **ZCode** — this repository *is* a marketplace source:
@@ -87,7 +87,7 @@ export function importLibrary(store, snapshot) {
87
87
  // status has no numeric setter; go through setStatus when it changed.
88
88
  if (mergedStatus !== existing.status)
89
89
  store.setStatus(existing.id, mergedStatus);
90
- applyTallies(store, existing.id, mergedHits, mergedFails);
90
+ applyTallies(store, existing.id, mergedHits, mergedFails, incoming.created_at);
91
91
  }
92
92
  report.merged += 1;
93
93
  continue;
@@ -104,7 +104,7 @@ export function importLibrary(store, snapshot) {
104
104
  continue;
105
105
  }
106
106
  if (incoming.hit_count > 0 || incoming.fail_count > 0) {
107
- applyTallies(store, id, incoming.hit_count, incoming.fail_count);
107
+ applyTallies(store, id, incoming.hit_count, incoming.fail_count, incoming.created_at);
108
108
  }
109
109
  if (incoming.status === 'retired')
110
110
  store.setStatus(id, 'retired');
@@ -112,8 +112,14 @@ export function importLibrary(store, snapshot) {
112
112
  }
113
113
  return report;
114
114
  }
115
- /** Set tallies directly; used by import, which owns merge policy. */
116
- function applyTallies(store, id, hitCount, failCount) {
115
+ /** Set tallies (and optionally creation time) directly; import owns merge policy. */
116
+ function applyTallies(store, id, hitCount, failCount, createdAt) {
117
117
  const db = store.db;
118
+ if (createdAt && createdAt > 0) {
119
+ // Snapshot time is preserved so zombie aging counts from the original
120
+ // lesson, not from when this machine imported it.
121
+ db.prepare('UPDATE antibodies SET hit_count = ?, fail_count = ?, created_at = ? WHERE id = ?').run(hitCount, failCount, createdAt, id);
122
+ return;
123
+ }
118
124
  db.prepare('UPDATE antibodies SET hit_count = ?, fail_count = ? WHERE id = ?').run(hitCount, failCount, id);
119
125
  }
@@ -367,19 +367,30 @@ function startPlugin(ctx) {
367
367
  });
368
368
  ctx.commands.register({
369
369
  name: 'immune-learn',
370
- description: 'Teach a rule directly: <trigger> [symptom] <prescription>',
371
- input: { hint: '<trigger> [symptom] <prescription>' },
370
+ description: 'Teach a rule directly: <trigger> | [symptom] | <prescription> (| separated; without |, trigger = first token and prescription = the rest)',
371
+ input: { hint: '<trigger> | [symptom] | <prescription>' },
372
372
  handler: ({ rawInput }) => {
373
- const parts = rawInput.trim().split(/\s+/);
374
- // A 3-part split cannot work on free text, so the rule is: trigger is the
375
- // first token, prescription is the last, symptom is whatever is between.
376
- const trigger = parts[0] ?? '';
377
- const prescription = parts.length >= 2 ? parts[parts.length - 1] ?? '' : '';
378
- const symptom = parts.length >= 3 ? parts.slice(1, -1).join(' ') : '';
379
- if (!trigger || !prescription) {
373
+ const text = rawInput.trim();
374
+ // Free text cannot be split by whitespace: "用 pnpm 不是 npm" would put
375
+ // "npm" in the prescription slot. Pipe-separated when given; the
376
+ // separator-free form keeps trigger = first token, prescription = the rest.
377
+ const hasPipes = text.includes('|');
378
+ const segments = hasPipes
379
+ ? text.split('|').map((part) => part.trim())
380
+ : (() => {
381
+ const tokens = text.split(/\s+/);
382
+ const first = tokens[0] ?? '';
383
+ const rest = tokens.slice(1).join(' ');
384
+ return rest ? [first, rest] : [first];
385
+ })();
386
+ const trigger = segments[0] ?? '';
387
+ const prescription = segments[segments.length - 1] ?? '';
388
+ const symptom = segments.length >= 3 ? segments.slice(1, -1).join(' | ') : '';
389
+ if (!trigger || !prescription || segments.length > 3) {
380
390
  return {
381
391
  kind: 'error',
382
- text: `${t.notFound} — usage: <trigger> [symptom] <prescription>\nexample: pnpm-lock.yaml 这个仓库用 pnpm 不是 npm`,
392
+ text: `${t.notFound} — expected: <trigger> | [symptom] | <prescription>\n` +
393
+ `example: pnpm-lock.yaml 这个仓库 | npm 装依赖报错 | 用 pnpm 不要用 npm`,
383
394
  };
384
395
  }
385
396
  const id = store.insert({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-immune-system",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
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": "0.9.0",
27
+ "version": "0.9.2",
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": "0.9.0",
3
+ "version": "0.9.2",
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": "0.9.0",
27
+ "version": "0.9.2",
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": {