dsh-error-atlas 0.11.2 → 0.11.26

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