dsh-immune-system 0.9.0 → 0.9.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
@@ -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`)。
@@ -301,19 +301,25 @@ against the real host contract) and **ZCode** (verified against its hooks).
301
301
 
302
302
  ### Install
303
303
 
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
304
+ **npm** — published as [`dsh-immune-system`](https://www.npmjs.com/package/dsh-immune-system),
305
+ prebuilt, no toolchain needed:
306
+
307
+ ```bash
308
+ dsh plugin --profile <profile> add dsh-immune-system
315
309
  ```
316
310
 
311
+ **tarball / git** (equivalent, no npm account required):
312
+
313
+ ```bash
314
+ cd dsh-immune-system && npm pack
315
+ dsh plugin --profile <profile> add ./dsh-immune-system-0.9.0.tgz
316
+ dsh plugin --profile <profile> add github:meat-daemon-404/dsh-plugin-immune
317
+ ```
318
+
319
+ `git:` installs build at install time via `prepare`, which pnpm blocks until
320
+ allowlisted — see the Chinese section above for the exact fix. Prefer npm or the
321
+ tarball.
322
+
317
323
  **Restart DSH** afterwards — bundles are composed at startup.
318
324
 
319
325
  **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.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": "0.9.0",
27
+ "version": "0.9.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": "0.9.0",
3
+ "version": "0.9.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": "0.9.0",
27
+ "version": "0.9.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": {