dsh-error-atlas 0.11.2 → 0.11.25

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 ADDED
@@ -0,0 +1,139 @@
1
+ # Changelog
2
+
3
+ All notable changes to **dsh-error-atlas**. Versions follow `0.11.x` while the plugin
4
+ is in the DSH marketplace pipeline; the panel's `V##` chip is the **client bundle** build
5
+ stamp, which only moves when `client.js` changes — a `V51 · 0.11.24` panel is expected.
6
+
7
+ ## 0.11.25
8
+
9
+ - Client tests no longer depend on the checkout's line endings: the test seam matches the
10
+ slot list with `\r?\n`, and `.gitattributes` normalises the repository to LF. First green
11
+ Windows CI run (it was failing on a CRLF checkout).
12
+
13
+ ## 0.11.24
14
+
15
+ - **CI**: GitHub Actions runs the full suite on Ubuntu and Windows, Node 20 and 22
16
+ (`.github/workflows/ci.yml`), plus a manifest installability check.
17
+ - `react` / `react-dom` are dev-dependencies now, so the client tests render outside DSH
18
+ (the panel still borrows the Harness copy at runtime).
19
+ - First `CHANGELOG.md`.
20
+
21
+ ## 0.11.23
22
+
23
+ - Ambient signatures no longer mark lessons as failed: a signature referenced by ≥4
24
+ retrospectives is the environment (a shell quirk), not the mistake any single lesson
25
+ was about. Bare exit codes never count as recurrence evidence either.
26
+ - Live effect on a real store: 26 "failed lessons" → 5.
27
+
28
+ ## 0.11.22
29
+
30
+ - Command fingerprints: an exit is recorded as `exit code N · <exe + script> · <stderr line>`
31
+ instead of the whole command line, so the same script failing in different directories is
32
+ one signature.
33
+ - Noise muting: bare exits (≥5 occurrences, no stderr) leave the graph and are counted in
34
+ `stats.noise`; `showNoise` brings them back.
35
+ - `essence` on every retrospective — one ≤96-char line (`if <this> → <cause> → <next time>`)
36
+ that is what the model actually gets injected; derived automatically when the writer
37
+ omits it.
38
+ - Prompt briefing now sends at most three essence lines, skips auto skeletons, and only
39
+ promotes a failed lesson when it really pointed at an error.
40
+ - Store v3 one-time migration: fingerprints recomputed, duplicate signatures merged, lesson
41
+ links remapped to the new ids, `essence` backfilled — one write.
42
+
43
+ ## 0.11.21
44
+
45
+ - Recurrence is judged against the records the graph actually shows: muting a signature no
46
+ longer makes its lessons look more broken.
47
+
48
+ ## 0.11.20 (V51)
49
+
50
+ - Failed-lesson wires get their own colour chip (`#ff5b4d`); before this the red chip
51
+ controlled core spokes, which were nearly invisible.
52
+ - Core spokes `0.07 → 0.16`, hub spokes `0.2 → 0.26`, chips 10px with dashed off-state,
53
+ per-row "lit/total" count (`4/5`) and `✓/✕` titles.
54
+
55
+ ## 0.11.19 (V50)
56
+
57
+ - Colour chips made legible: bigger targets, visible off-state, counts.
58
+
59
+ ## 0.11.17 (V48)
60
+
61
+ - Node-kind and wire-role colour filters, left of Refresh, persisted in `localStorage`.
62
+
63
+ ## 0.11.16 (V47)
64
+
65
+ - Spacing dial: scales node positions only — not the camera, not the diameters.
66
+
67
+ ## 0.11.15 (V46)
68
+
69
+ - Appearance popover: spin, size, pulse size, glow — and pulses now ride the wire itself.
70
+ - Bulb-style double-layer glow (`HALO_FS` `pow(1-r, 2.1)` + white-tinted core).
71
+
72
+ ## 0.11.14 (V45)
73
+
74
+ - Dedicated halo pass with its own glow dial (0–400%).
75
+
76
+ ## 0.11.13 (V44)
77
+
78
+ - Node size decoupled from the camera: `uRefDist` keeps the dial's meaning stable.
79
+
80
+ ## 0.11.12 (V43)
81
+
82
+ - The learning pulses follow the size dial.
83
+
84
+ ## 0.11.11 (V42)
85
+
86
+ - The size dial really reaches 10%; point size is capped at 44px so zoom-in cannot turn
87
+ nodes into plates.
88
+
89
+ ## 0.11.10 (V41)
90
+
91
+ - Nodes are points again: the solid core fills ~57% of the sprite and base sizes halve.
92
+
93
+ ## 0.11.9 (V40)
94
+
95
+ - Size dial for the whole map; solid anti-aliased node cores.
96
+
97
+ ## 0.11.8 (V39)
98
+
99
+ - Zoom goes further and faster: wheel 1.75×, floor 12 world units.
100
+
101
+ ## 0.11.7 (V38)
102
+
103
+ - Program satellites park on the side of their own failures and sit closer in.
104
+
105
+ ## 0.11.6 (V37)
106
+
107
+ - Clicking a node closes the camera in on it; smaller halos.
108
+
109
+ ## 0.11.5 (V36)
110
+
111
+ - Orbit-any-node camera: the chosen node becomes the centre of rotation.
112
+ - Calmer sprite brightness (additive blending was over-exposing the islands).
113
+
114
+ ## 0.11.3 / 0.11.4 (V34 / V35)
115
+
116
+ - Program satellites orbit outside the cloud.
117
+ - **Session islands**: each session window is its own ball with its own failures; DSH sits
118
+ at the centre; programs orbit outside. `maxNodes` 240 → **2400**, with grid repulsion,
119
+ dynamic cloud radius, adaptive warm-up and island anchors.
120
+ - Bench (fake GL): 240 / 900 / 2400 nodes → 0.9 / 5.7 / 14.5 ms per frame.
121
+
122
+ ## 0.11.2
123
+
124
+ - `atlas-digest` exits 0 on a fresh install with an empty summary instead of failing.
125
+ - README gains the npm install path (`plugin_manager install_bundle target="dsh-error-atlas"`).
126
+
127
+ ## 0.11.1
128
+
129
+ - README screenshot points at the raw GitHub URL so npm renders it.
130
+
131
+ ## 0.11.0
132
+
133
+ - First release: runtime error capture (`agent/error`, `api-session/error`,
134
+ `agent-loop/config-start-failed`, tool failures), signature dedupe, `atlas_retro` tool,
135
+ automatic skeleton retrospectives, global prompt section, `atlas_lessons` read tool,
136
+ lesson links, recurrence flags, program hubs, projection/search/trends, Markdown export,
137
+ lesson-effectiveness board, two memory switches, GPU knowledge graph panel.
138
+
139
+ [0.11.24]: https://github.com/chaos-miku/dsh-error-atlas/compare/8f92a73...HEAD
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Error Atlas · 错误星图
2
2
 
3
+ [![CI](https://github.com/chaos-miku/dsh-error-atlas/actions/workflows/ci.yml/badge.svg)](https://github.com/chaos-miku/dsh-error-atlas/actions/workflows/ci.yml)
4
+
3
5
  把 DSH 自己的运行错误和每个编程任务的复盘,沉淀成一张可以被**人看见**、也可以被**模型读到**的记忆图谱。
4
6
 
5
7
  ![错误星图面板](https://raw.githubusercontent.com/chaos-miku/dsh-error-atlas/main/docs/screenshot.jpg)
@@ -28,6 +30,17 @@ DSH 报过的错、踩过的坑,默认会随会话结束消失。这个插件
28
30
  - 每次请求动态注入简报:**失效教训优先**(写完又复发的),再加几条最近的规则;没有教训时整段不出现,不产生固定开销;
29
31
  - 只读工具 `atlas_lessons`,可按 `query` / `program` / `limit` 查询全部教训。
30
32
 
33
+ ## 噪声与本质
34
+
35
+ 原始记录里最没用的部分是「同一条命令换个目录再失败一次」,最有用的是「一句话说清下次怎么做」。所以入库和注入两头都做了收窄:
36
+
37
+ - **命令指纹**:失败不再记整条命令行,而是记 `可执行 + 脚本/子命令`。`cd A; node build.mjs` 与 `cd B; node build.mjs` 是同一条 `node build.mjs`;`node -e "…"` 记成 `node -e`;`@' … '@` 这类脚本片段记成 `shell`,不会碎成 `node const` 这种垃圾签名。stderr 的最后一行留下来做「详情」,所以第二次看到它时还认得出是什么错。
38
+ - **噪声静音**:退出码干巴巴出现 ≥5 次、又没有 stderr 详情的那种,默认不进图谱(`stats.noise` 会告诉你静音了几条)。想看就打开 `showNoise`,`GET|POST /error-atlas/prefs` 或者面板里的过滤都能改。
39
+ - **本质规则 `essence`**:每条复盘都会有一行 ≤96 字符的本质——`如果 <情形> → <根因> → 下次 <做法>`。模型写复盘时可以自己给 `essence`,没给就由宿主从教训里压一行出来。
40
+ - **注入只发本质**:上下文简报最多 3 行、跳过自动骨架、失效教训必须真的关联过错误才置顶;每行只发 `essence`,不再把整段教训塞进每次请求。
41
+ - **环境噪声不算「教训失效」**:一个签名如果被 ≥4 条复盘同时引用,它就是环境(比如这个 shell 的 stderr 习惯),不是哪条教训要防的错——它的复发不再把整排教训判红,裸退出码的复发也不算数。
42
+ - **一次性迁移**:旧库(`version < 3`)在 `load()` 时把 exit 记录重算成指纹、合并重复签名、把复盘的 `errorIds` 重映射到新 id(连边不会断),并补写 `essence`;迁移和派生都只落盘一次。
43
+
31
44
  **图谱只是这些数据的一个视图**——它不是装饰,但也不是产品本身:价值在于「教训能被下次会话读到」。
32
45
 
33
46
  ## 使用方式
@@ -51,20 +64,39 @@ DSH 报过的错、踩过的坑,默认会随会话结束消失。这个插件
51
64
  - 新会话开始后,宿主会自动把「失效教训 + 最近几条规则」注入上下文,模型不用查就知道;
52
65
  - 你也可以让它主动查:模型可以调用 `atlas_lessons`(按程序 / 关键词 / 条数),面板上的「导出」则给人看。
53
66
 
54
- ### 5. 维护
67
+ ### 5. 会话分岛
68
+
69
+ 中心是 DSH 本体,往外每一圈是一个**会话窗口**:这个窗口里出过的错误围着它转,程序卫星再挂在整张图的外层轨道上。所以一眼能回答「哪个窗口在出错」「这个错误属于哪个程序」,而不是所有节点挤在一个球里。
70
+
71
+ 同一签名在多个窗口出现时仍然只画一个节点(这是签名图,不是回合日志),它挂在最先出事的那个窗口上——`sessions` 里记录的其余窗口仍会在详情卡里列出来。
72
+
73
+ ### 6. 维护
55
74
 
56
75
  - **噪声太多**:卡片上点「忽略」,把这条签名加入前缀规则,永久静音;顶部「过滤 ≥N」按命中次数裁剪视图;
76
+ - **程序卫星**:程序枢纽被摆到**外层轨道**而不是云团中心——同一程序的所有复盘文件落在同一目录,它就悬在云外,连线像缆绳一样从错误节点拉出去,一眼能看出「是哪一团错误属于这个程序」,而不是所有东西挤成一个球;
57
77
  - **找东西**:搜索框输入标签、程序名、错误文本,命中高亮、其余压暗;点教训有效性榜单里的任意一行,图谱会过滤到那一类;
58
78
  - **太久之前**:点「清空」删除运行时错误记录(青色开发错误与紫色复盘是保留的);点「导出」把整份数据写成 Markdown。
59
79
 
80
+ ### 3.5 看得清
81
+
82
+ - **一个「外观」放四个滑条**:点顶部「外观」弹出面板,里面有自转速度、节点大小、光点大小、光晕四个滑条,调节即时生效并各自记住上次的值。
83
+ - **节点大小**:「大小」滑条缩放整张图的节点(10%–300%),与相机距离解耦——缩放只改变位置,不改变点的直径。
84
+ - **光点大小**:「光点」滑条(20%–400%)只调沿教训线流动的那批小球,与节点的粗细互不影响。
85
+ - **间距**:「间距」滑条(35%–240%)缩放整张图的**位置**,连线的长短、岛屿之间的距离一起变;节点与光点自身的直径不变,所以「拉开看清楚」和「点变大」是两件事。
86
+ - **颜色筛选**:顶栏「刷新」左边有两排小圆点——左边一排是节点类型(AGENT/TOOL/EXIT/CONFIG/SESSION/PROJECT/DEV/RETRO),右边一排是连线类型(核心辐条/枢纽辐条/程序系绳/教训线/失效教训)。每排前面有个「亮着/总数」的计数,`3/4` 这种数字就是还有四个里有三个在显示。点一下关掉那一类,再点一下开回来,选择会被记住。
87
+ - **光晕大小**:「光晕」滑条(0%–400%)控制每个节点外层的辉光,走的是电灯式两层叠加——紧致的一圈亮边加一圈更宽更淡的散射光;0% 是完全清晰的点阵,100% 是默认的星云感。
88
+ - **贴着线走**:光点严格画在连线上(不做弧线抬升),不会浮在线旁边。
89
+ - **点击 = 对准**:点任意节点,相机会把它转到屏幕中心并拉近;点空白或「重排」回退到全景。
90
+ - **节点是实的**:每个节点是带抗锯齿边缘的亮球(中心亮、边缘暗),外面只留一层很淡的光晕——密集区不再糊成一片白。
91
+
60
92
  ## 面板上都有什么
61
93
 
62
94
  | 区域 | 说明 |
63
95
  |---|---|
64
96
  | 顶部统计 | 累计错误 / 去重条目 / 图谱节点 / 会话 / 工具 / 程序 / 开发错误 / 任务复盘 / 教训失效 |
65
- | 顶部操作 | 刷新、搜索、暂停、自转速度、过滤 ≥N、重排、导出、清空、标题开关 |
97
+ | 顶部操作 | 刷新、搜索、暂停、外观(自转速度 / 节点大小 / 间距 / 光点大小 / 光晕)、过滤 ≥N、重排、导出、清空、标题开关 |
66
98
  | 左侧图例 | 见下表 |
67
- | 中央星图 | 拖拽旋转、滚轮缩放、点节点看详情卡、双击带会话的节点跳回该会话 |
99
+ | 中央星图 | 拖拽旋转、滚轮缩放、点节点看详情卡,**视野拉近到该节点并以它为中心旋转**(点空白处回到整体)、双击带会话的节点跳回该会话 |
68
100
  | 底部「记忆」 | 两枚开关:收尾提示 / 注入教训 |
69
101
  | 教训有效性 | 按标签与程序统计复盘数、失效数、失效率;可收起;行可点击过滤 |
70
102
  | 最近事件 | 最新 400 条事件的表格视图(次数、类型、工具、消息、首次/最近) |
@@ -76,11 +108,11 @@ DSH 报过的错、踩过的坑,默认会随会话结束消失。这个插件
76
108
  | `AGENT` | 亮红 `#ff4453` | 模型自身在某一轮/步失败 |
77
109
  | `TOOL` | 橙 `#ffb570` | 工具调用返回了错误 |
78
110
  | `EXIT` | 杏黄 `#ffd68f` | 命令非零退出码 |
79
- | `SESSION` | 粉 `#ff859d` | 会话枢纽(出过错的那个窗口) |
111
+ | `SESSION` | 粉 `#ff859d` | 会话枢纽(出过错的那个窗口):每个窗口是一座岛,自己的错误围在它周围 |
80
112
  | `CONFIG` | 紫 `#d89eff` | 会话/插件配置启动失败 |
81
113
  | `DEV` | 青 `#7ff3e0` | 插件自己的开发错误(内置 12 条,随版本更新) |
82
114
  | `RETRO` | 紫 `#c7a4ff` | 一条任务复盘 |
83
- | `PROJECT` | 浅蓝 `#7fd4ff` | 程序枢纽:同一项目里的错误额外连向它 |
115
+ | `PROJECT` | 浅蓝 `#7fd4ff` | 程序卫星:停在错误云外层轨道,缆绳般的连线拉到它犯过的错误上 |
84
116
  | `STALE` | 红 `#ff5b4d` | 教训失效(复盘之后同一错误又出现) |
85
117
 
86
118
  ## 安装
@@ -93,7 +125,7 @@ DSH 报过的错、踩过的坑,默认会随会话结束消失。这个插件
93
125
  plugin_manager install_bundle target="dsh-error-atlas"
94
126
  ```
95
127
 
96
- 指定版本:`target="dsh-error-atlas@0.11.2"`。
128
+ 指定版本:`target="dsh-error-atlas@0.11.4"`。
97
129
 
98
130
  本地目录安装(开发用):
99
131
 
@@ -148,14 +180,16 @@ npm test # 157 项断言:宿主事件/路由/存储 + 客户端渲染
148
180
  npm run smoke # 干净 DSH_HOME 下的冷启动冒烟:空库 → 写复盘 → 读回 → CLI 可读
149
181
  ```
150
182
 
151
- 客户端为零依赖手写 bundle(`window.__ModuleLoader__`),宿主为纯 ESM。面板标题旁的 `V30`/`V31`… 是 BUILD 号,用来确认页面加载的是哪一代客户端代码。
183
+ 客户端为零依赖手写 bundle(`window.__ModuleLoader__`),宿主为纯 ESM。面板标题旁的 `V30`/`V35`… 是 BUILD 号,用来确认页面加载的是哪一代客户端代码。
152
184
 
153
185
  ## 已知边界
154
186
 
155
187
  - 只看得见**工具层失败与非零退出**,模型在「成功路径」上的逻辑错误不会自动入库——那部分要靠复盘自己写;
156
188
  - 教训注入是全局的(每轮一次),不按工作区精确匹配;
157
189
  - 程序枢纽只从复盘的 `files` + `cwd` 推导,错误记录本身不存路径,所以没写过复盘的目录不会出现在图里;
158
- - 图谱上限 240 个节点,超出时 `stats.truncated` 会如实标记,且被复盘引用的错误不会被裁掉;
190
+ - 图谱上限 2400 个节点(约 2400 条去重签名):超出时 `stats.truncated` 会如实标记,且被复盘引用的错误不会被裁掉;
191
+ - 规模变大后布局换成网格斥力(只算近邻)+ 岛屿锚定,2400 节点首次落位约几十毫秒、稳态每帧十几毫秒;240 节点以下仍是精确力模型;
192
+ - 会话岛采用「唯一归属」:同一签名只挂在最先出事的窗口上,其余窗口在详情卡里列出,避免一个节点被复制成多份;
159
193
  - 需要 Node ≥ 20;客户端需要 WebGL2(不可用时面板退化为提示)。
160
194
 
161
195
  ## 相关链接
@@ -133,16 +133,28 @@ const GRACE_MS = 90 * 1000
133
133
  const byId = new Map(records.map((r) => [r.id, r]))
134
134
  const signature = (entry) => entry.kind + '\u0000' + entry.name + '\u0000' + entry.message
135
135
 
136
+ // A signature many lessons point at is ambient environment noise (a shell quirk), not the
137
+ // mistake a single lesson was about; and a bare repeat without stderr carries no evidence.
138
+ const AMBIENT_REFS = 4
139
+ const refCount = new Map()
140
+ for (const retro of retros) {
141
+ for (const id of retro.errorIds || []) refCount.set(id, (refCount.get(id) || 0) + 1)
142
+ }
143
+ const bareExit = (entry) => entry.kind === 'exit' && (entry.count || 0) >= 5 && String(entry.message || '').split(' · ').length < 3
144
+
136
145
  function recurrenceOf(retro) {
137
146
  const ids = Array.isArray(retro.errorIds) ? retro.errorIds : []
138
147
  if (ids.length === 0) return null
139
148
  const cutoff = (Number(retro.at) || 0) + GRACE_MS
140
149
  const keys = new Set()
150
+ const used = []
141
151
  let lastAt = 0
142
152
  for (const id of ids) {
153
+ if ((refCount.get(id) || 0) >= AMBIENT_REFS) continue
143
154
  const entry = byId.get(id)
144
- if (!entry) continue
155
+ if (!entry || bareExit(entry)) continue
145
156
  keys.add(signature(entry))
157
+ used.push(entry)
146
158
  if ((entry.last || 0) > cutoff) lastAt = Math.max(lastAt, entry.last)
147
159
  }
148
160
  if (keys.size === 0) return null
@@ -156,10 +168,7 @@ function recurrenceOf(retro) {
156
168
  // The log is capped; an aggregate `last` beyond the cutoff still proves a relapse.
157
169
  if (times === 0 && lastAt > 0) times = 1
158
170
  if (times === 0) return null
159
- const errors = ids
160
- .map((id) => byId.get(id))
161
- .filter(Boolean)
162
- .map((entry) => ({ kind: entry.kind, name: entry.name, message: entry.message, count: entry.count }))
171
+ const errors = used.map((entry) => ({ kind: entry.kind, name: entry.name, message: entry.message, count: entry.count }))
163
172
  return { times, lastAt, errors }
164
173
  }
165
174
 
@@ -75,7 +75,7 @@ function list() {
75
75
 
76
76
  function bodyFrom(args2) {
77
77
  const body = {}
78
- for (const key of ['title', 'done', 'cause', 'fix', 'lesson']) {
78
+ for (const key of ['title', 'done', 'cause', 'fix', 'lesson', 'essence']) {
79
79
  const value = flag(key)
80
80
  if (typeof value === 'string' && value.trim()) body[key] = value
81
81
  }
@@ -97,7 +97,7 @@ function patchFile(id, body) {
97
97
  const retro = data.retros.find((item) => item.id === id)
98
98
  if (!retro) return { ok: false, error: 'no retrospective ' + id }
99
99
  retro.auto = false
100
- for (const key of ['title', 'done', 'cause', 'fix', 'lesson']) {
100
+ for (const key of ['title', 'done', 'cause', 'fix', 'lesson', 'essence']) {
101
101
  if (typeof body[key] === 'string') retro[key] = body[key].slice(0, 420)
102
102
  }
103
103
  if (Array.isArray(body.tags)) retro.tags = body.tags.slice(0, 8)
@@ -150,7 +150,9 @@ const USAGE = `atlas-retro — write a retrospective into the error atlas by han
150
150
  node bin/atlas-retro.mjs --port 65203 ... # override the published host port
151
151
  node bin/atlas-retro.mjs --fields retros.json # one record or an array of records
152
152
 
153
- Fields: title, done, cause, fix, lesson (text); tags (a,b); ids / errorIds (e1,e2).
153
+ Fields: title, done, cause, fix, lesson, essence (text); tags (a,b); ids / errorIds (e1,e2).
154
+ An essence is the one injected line: "<if this> -> <root cause> -> <next time>"; without it
155
+ the host derives one from the lesson on the next load.
154
156
  A --fields record needs "id" to upgrade an existing one, or title+lesson to create one.`
155
157
 
156
158
  async function main() {