dsh-plugin-show-me-data 0.1.0

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.
Files changed (130) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +96 -0
  3. package/cordis.patch.yml +40 -0
  4. package/docs/01-product-effect.md +178 -0
  5. package/docs/02-architecture.md +275 -0
  6. package/docs/03-data-contracts.md +291 -0
  7. package/docs/04-sources.md +342 -0
  8. package/docs/05-ui-spec.md +167 -0
  9. package/docs/06-ai-layer.md +194 -0
  10. package/docs/07-implementation-plan.md +399 -0
  11. package/docs/08-test-plan.md +133 -0
  12. package/docs/09-packaging-install.md +249 -0
  13. package/docs/10-kickoff-prompt.md +94 -0
  14. package/docs/11-decisions.md +203 -0
  15. package/docs/12-runtime-verified.md +115 -0
  16. package/docs/13-acceptance.md +153 -0
  17. package/docs/14-progress.md +150 -0
  18. package/docs/15-publish.md +185 -0
  19. package/lib/app/ai-deterministic.js +327 -0
  20. package/lib/app/ai-validate.js +284 -0
  21. package/lib/app/ai.js +440 -0
  22. package/lib/app/health.js +77 -0
  23. package/lib/app/overview.js +349 -0
  24. package/lib/app/propose-indicator.js +122 -0
  25. package/lib/app/refresh.js +251 -0
  26. package/lib/app/series-view.js +195 -0
  27. package/lib/app/watchlist.js +102 -0
  28. package/lib/client.js +4322 -0
  29. package/lib/core/ai/prompts.js +213 -0
  30. package/lib/core/chart/axis.js +133 -0
  31. package/lib/core/chart/bar.js +58 -0
  32. package/lib/core/chart/candle.js +216 -0
  33. package/lib/core/chart/line.js +186 -0
  34. package/lib/core/chart/scale.js +132 -0
  35. package/lib/core/format.js +143 -0
  36. package/lib/core/indicators/catalog.js +1011 -0
  37. package/lib/core/indicators/resolve.js +196 -0
  38. package/lib/core/insight/digest.js +250 -0
  39. package/lib/core/insight/rank.js +115 -0
  40. package/lib/core/insight/related.js +90 -0
  41. package/lib/core/insight/rules.js +417 -0
  42. package/lib/core/stats/derive.js +123 -0
  43. package/lib/core/stats/series.js +465 -0
  44. package/lib/core/time/range.js +242 -0
  45. package/lib/core/types.js +478 -0
  46. package/lib/host/ai/discussion.js +559 -0
  47. package/lib/host/ai/dsh-llm-gateway.js +333 -0
  48. package/lib/host/config.js +194 -0
  49. package/lib/host/http/respond.js +165 -0
  50. package/lib/host/http/routes.js +689 -0
  51. package/lib/host/index.js +293 -0
  52. package/lib/host/infra/fs-repos.js +179 -0
  53. package/lib/host/infra/memory-fallback.js +64 -0
  54. package/lib/host/tools/define-tool.js +295 -0
  55. package/lib/host/tools/register.js +431 -0
  56. package/lib/host.js +7 -0
  57. package/lib/ports/clock.js +57 -0
  58. package/lib/ports/snapshot-repo.js +48 -0
  59. package/lib/sources/eastmoney-macro.js +197 -0
  60. package/lib/sources/eastmoney-quote.js +201 -0
  61. package/lib/sources/ecb.js +179 -0
  62. package/lib/sources/fred.js +207 -0
  63. package/lib/sources/http.js +136 -0
  64. package/lib/sources/ohlc.js +36 -0
  65. package/lib/sources/quote-cascade.js +177 -0
  66. package/lib/sources/registry.js +153 -0
  67. package/lib/sources/sina-cn.js +197 -0
  68. package/lib/sources/sina-us.js +187 -0
  69. package/lib/sources/tencent.js +158 -0
  70. package/lib/sources/us-treasury-rates.js +275 -0
  71. package/lib/sources/us-treasury.js +196 -0
  72. package/lib/sources/worldbank.js +170 -0
  73. package/package.json +69 -0
  74. package/src/app/ai-deterministic.js +327 -0
  75. package/src/app/ai-validate.js +284 -0
  76. package/src/app/ai.js +440 -0
  77. package/src/app/health.js +77 -0
  78. package/src/app/overview.js +349 -0
  79. package/src/app/propose-indicator.js +122 -0
  80. package/src/app/refresh.js +251 -0
  81. package/src/app/series-view.js +195 -0
  82. package/src/app/watchlist.js +102 -0
  83. package/src/client/api.js +323 -0
  84. package/src/client/components.js +1877 -0
  85. package/src/client/copy.js +368 -0
  86. package/src/client/index.js +169 -0
  87. package/src/client/store.js +219 -0
  88. package/src/core/ai/prompts.js +213 -0
  89. package/src/core/chart/axis.js +133 -0
  90. package/src/core/chart/bar.js +58 -0
  91. package/src/core/chart/candle.js +216 -0
  92. package/src/core/chart/line.js +186 -0
  93. package/src/core/chart/scale.js +132 -0
  94. package/src/core/format.js +143 -0
  95. package/src/core/indicators/catalog.js +1011 -0
  96. package/src/core/indicators/resolve.js +196 -0
  97. package/src/core/insight/digest.js +250 -0
  98. package/src/core/insight/rank.js +115 -0
  99. package/src/core/insight/related.js +90 -0
  100. package/src/core/insight/rules.js +417 -0
  101. package/src/core/stats/derive.js +123 -0
  102. package/src/core/stats/series.js +465 -0
  103. package/src/core/time/range.js +242 -0
  104. package/src/core/types.js +478 -0
  105. package/src/host/ai/discussion.js +559 -0
  106. package/src/host/ai/dsh-llm-gateway.js +333 -0
  107. package/src/host/config.js +194 -0
  108. package/src/host/http/respond.js +165 -0
  109. package/src/host/http/routes.js +689 -0
  110. package/src/host/index.js +293 -0
  111. package/src/host/infra/fs-repos.js +179 -0
  112. package/src/host/infra/memory-fallback.js +64 -0
  113. package/src/host/tools/define-tool.js +295 -0
  114. package/src/host/tools/register.js +431 -0
  115. package/src/ports/clock.js +57 -0
  116. package/src/ports/snapshot-repo.js +48 -0
  117. package/src/sources/eastmoney-macro.js +197 -0
  118. package/src/sources/eastmoney-quote.js +201 -0
  119. package/src/sources/ecb.js +179 -0
  120. package/src/sources/fred.js +207 -0
  121. package/src/sources/http.js +136 -0
  122. package/src/sources/ohlc.js +36 -0
  123. package/src/sources/quote-cascade.js +177 -0
  124. package/src/sources/registry.js +153 -0
  125. package/src/sources/sina-cn.js +197 -0
  126. package/src/sources/sina-us.js +187 -0
  127. package/src/sources/tencent.js +158 -0
  128. package/src/sources/us-treasury-rates.js +275 -0
  129. package/src/sources/us-treasury.js +196 -0
  130. package/src/sources/worldbank.js +170 -0
@@ -0,0 +1,115 @@
1
+ # 12 · 运行时校准记录(M0 产出物)
2
+
3
+ > 规则:以 `cordis_inspect` 的**实测结果**为准;与 `docs/02-architecture.md` §6 的假设冲突时,
4
+ > **先改文档与测试计划,再继续**。
5
+
6
+ 填写时间:`2026-09-12` 填写会话:`session-fc43d029-5a0e-4244-a8f1-706e9cf30115` DSH 版本:`0.1.0-rc.6`
7
+ Node:`v24.19.0` GUI:`http://127.0.0.1:38080` profile:`web`(bundles: dsh-base + dsh-web-app + dsh-holdem)
8
+
9
+ ---
10
+
11
+ ## 汇总表
12
+
13
+ | # | 待核对项 | 假设(源自 02 §6) | 实测结果 | 结论 / 需要改什么 |
14
+ | --- | --- | --- | --- | --- |
15
+ | 1 | 浮层槽位 key 与协议 | `shell.overlay`,`kind` 待定,注册需 `id`/`order` | 存在且可用:`kind: "list"`,`scope: "root"`,`replaceRisk: "none"`,`ownerProps: []`,注册参数 `{ id(必填), order?, label? }`,`standardProps = [useSessions, useWorkspaces]` | **假设成立**。我们的组件不接收任何自有 props;面板自管理开合。浮层容器本身 `pointer-events: none`(click-through),条目需自己 opt-in `pointer-events: auto`——已写进 `05` 与实现 |
16
+ | 2 | `webServer.register` 签名 | `{ path, kind:'exact'\|'prefix', handler }` | 完全一致:`register(route: {kind:'exact'\|'prefix', path, handler:(req:IncomingMessage,res:ServerResponse)=>void\|Promise<void>}) => () => void`;`path` 为绝对路径、无尾斜杠;重复 `(kind,path)` 抛错;匹配顺序 exact > 最长 prefix > fallback | **假设成立**,handler 直接持有 `node:http` 的 req/res(SSE 可行)。路由一律 `kind:'prefix'` + `/api/show-me-data`,无冲突风险 |
17
+ | 3 | `tools.register` 与 `output` 声明 | `output` 必填,支持 `render`/`presentationMeta` | `register(definition: ToolDefinition) => () => void`;`ToolDefinition` 必填 `name/description/parameters(JSON Schema 子集)/output{schema, render}`,`execute(args, exec)`;可选 `finalizeContent/presentCall/presentResult/timeoutMs/isConcurrencySafe` | **假设成立**。`parameters` 用 JSON Schema 子集(`type/properties/required/additionalProperties/items/enum/const/oneOf` + 注解关键字),不支持的关键字会抛。我们**不导入** `@deepseek-ai/dsh-tools` 的 `defineTool`(见偏差 1) |
18
+ | 4 | `llm` 服务与默认模型选择 | `ctx.llm.stream()` + `ctx.agentDefaultModel.currentSelection()` | `llm.stream(options: GenerateOptions): AsyncIterable<StreamChunk>`,`GenerateOptions = {provider, model, messages: Message[], system?, temperature?, maxTokens?, signal?, sessionId?}`;chunk 类型 `block-start/text-delta/reasoning-delta/tool-call-delta/block-end/usage/finish`;`agentDefaultModel.currentSelection(): ModelSelection`,`ModelSelection = {provider, model, reasoningEffort?}` | **假设成立**(签名微调,见偏差 2)。`/data/.credentials.yaml` 里已有 `DEEPSEEK_API_KEY`,即部署有可用 provider;无 provider 时走 deterministic |
19
+ | 5 | `settings.section` 协议与持久化 | 可注册分区;设置由 `settings` 服务持久化 | 槽位存在:`settings.section`(`kind:"list"`,注册 `{id(必填), order?, label?}`);宿主 `settings` 服务提供 `register(ns, schema)/get/update/replace/mutate`(schema 为 schemastery) | **假设成立但降级使用**:面板自带设置页(浮层内 Tab),插件可选项走 `cordis.patch.yml` 的 `config`(由用户/部署控制)。不注册 `settings.section`,避免把面板拆成两处 |
20
+ | 6 | 主题令牌 | `Theme.listTokens` 提供颜色/间距令牌 | 实测 13 个令牌:`--dsw-alias-bg-base/-bg-layer-1/-bg-layer-2/-bg-overlay`、`--dsw-alias-border-l1/-l2`、`--dsw-alias-brand-primary`、`--dsw-alias-label-primary/-secondary`、`--dsw-alias-state-error-primary/-success-primary/-warn-primary`、`--dsw-specific-sidebar-fill`;另有 `--dsw-shadow-lv3`、`--dsw-font-mono`(在 ui-jobs 的产物中实测可见) | 面板只用这些变量 + 兜底值(`var(--dsw-alias-x, fallback)`),不硬编码产品色。**令牌清单比假设少**(没有间距令牌、没有双向涨跌色)→ 涨跌语义色由 `polarity` + success/warn/error 三色组合推导 |
21
+ | 7 | `dsh.client.inject` 需要哪些包 | 镜像 `dsh-client-ui-jobs` + 提供 `slots` 的包 | 客户端服务目录实测只有:`layout/sessions/slots/theme/locale/timer/workspaces`;`ui-jobs` 的 `dsh.client.inject` = `[dsh-client-locale, dsh-client-runtime, dsh-client-ui-conversation, dsh-client-ui-primitives]`,但它**只 require 了 `react` / `react/jsx-runtime` / `dsh-client-ui-primitives`**(inject 声明的是加载顺序与可用性,不是 require 白名单) | **收窄**:我们只用 `react` + `slots` + `locale`(可选),因此声明 `inject: ["@deepseek-ai/dsh-client-runtime"]` 并只 `require("react")`。不引入 primitives,避免多一个加载期硬依赖 |
22
+
23
+ ---
24
+
25
+ ## 逐项详情
26
+
27
+ ### 1. 浮层槽位
28
+
29
+ - 查询:`cordis_inspect_query(platform:"client", provider:"Slots", method:"listSubTree")`
30
+ → `shell.overlay` 位于 `root` 之下,与 `sidebar`/`conversation`/`details` 平级,`kind: "list"`。
31
+ - 精确契约:`listSubTree({root:"shell.overlay"})` → `occupants: []`(当前无人占用),
32
+ `catalog.description` 明确:*"The layer itself is click-through — entries opt back into pointer events"*。
33
+ - 已占位者:无。
34
+ - 我们的注册参数(最终确定):
35
+ ```js
36
+ ctx.slots.inject('shell.overlay', () => ctx.slots.register(
37
+ { name: 'shell.overlay', id: 'show-me-data', order: 30 },
38
+ DataRadarOverlay,
39
+ ))
40
+ ```
41
+ - 若不可用则采用的降级方案:**不需要**(实测可用,无需退回 `settings.section`)。
42
+
43
+ ### 2. `webServer`
44
+
45
+ - `inject` 是否需要:是(`inject: ['webServer']`;可选读取时用 `ctx.get('webServer')`)。
46
+ - 真实签名:`register(route) => dispose`;`WebRoute = { kind: 'exact'|'prefix', path: string, handler(req,res) }`。
47
+ - handler 的参数与返回约定:原生 `IncomingMessage`/`ServerResponse`;handler **拥有完整响应生命周期**
48
+ (可保持连接打开 → SSE 可行);`void | Promise<void>`。
49
+ - 路径重复注册的行为:抛错(`(kind,path)` 必须唯一;composition 级契约)。
50
+
51
+ ### 3. `tools`
52
+
53
+ - `output` 的必填字段:`schema`(JSON Schema 子集,强制校验返回值)与 `render(args, value) => ContentBlock[]`。
54
+ - 是否需要 `finalizeContent`:否(可选)。
55
+ - 工具卡渲染钩子(`tool.call.toolview`)的 key 规则:keyed 槽位,key = 线上工具名
56
+ (已占用:ask_user_question/bash/cordis_*/edit/glob/grep/read/skill/todo_write/web_fetch/web_search/write)。
57
+ 我们的 `data_*` 名字不在占用列表内 → **可以注册同名 key**。M8 实现,缺失也不影响功能(退化为通用卡)。
58
+
59
+ ### 4. `llm` 与模型选择
60
+
61
+ - 当前是否有可用 provider:☑ 有(`/data/.credentials.yaml` 存在 `DEEPSEEK_API_KEY`;`llm.listProviders()` 未在 M0 现场调用)
62
+ - provider / model:运行时由 `agentDefaultModel.currentSelection()` 提供;插件不硬编码
63
+ - 流式 chunk 形状与终止条件:`text-delta{index,text}` 累积 → `finish{reason}` 结束;`usage{usage}` 可用于成本日志
64
+ - 无 LLM 时 `AiGateway` 的装配分支是否如预期走 deterministic:装配逻辑按 `ctx.get('llm')` 是否存在决定;
65
+ `DeterministicGateway` 是产品功能(非 mock),M9 有 `describe().mode` 断言
66
+
67
+ ### 5. `settings`
68
+
69
+ - 分区注册协议:`{ id, order?, label? }`(`settings.section`,root 作用域)
70
+ - 读写路径与命名空间:宿主 `settings.register(ns, schema)` / `get` / `update`;落盘在 DSH_HOME
71
+ - 冷启动时设置尚未加载的处理方式:**本插件不使用该服务**;配置来自 cordis 行 `config`
72
+ (见 `docs/02 §1.1`:patch 会替换整份 config),因此不存在冷启动竞态
73
+
74
+ ### 6. 主题令牌
75
+
76
+ - 可用令牌清单(关键色/间距):见汇总表 #6(13 个颜色令牌,无间距/圆角令牌)
77
+ - 直接使用的 CSS 变量名:
78
+ `--dsw-alias-bg-overlay`(面板底)、`--dsw-alias-bg-layer-1/-2`(卡片)、`--dsw-alias-border-l1/-l2`、
79
+ `--dsw-alias-label-primary/-secondary`、`--dsw-alias-brand-primary`、`--dsw-alias-state-error-primary/-success-primary/-warn-primary`、
80
+ `--dsw-shadow-lv3`、`--dsw-font-mono`;几何一律用自有 px 值,不假设间距令牌存在
81
+
82
+ ### 7. 客户端依赖
83
+
84
+ - `dsh.client.inject` 最终清单:`["@deepseek-ai/dsh-client-runtime"]`
85
+ - 实际遇到的加载报错与补齐过程:尚未安装(M10 验证)。风险点已记录在 `09 §8`:
86
+ 若加载期报缺包,按报错逐个补 `dsh.client.inject`(插件只用 `react`,因此预期不需要补)
87
+
88
+ ---
89
+
90
+ ## 偏差与后续动作
91
+
92
+ | 偏差 | 影响范围 | 已修改的文档/测试 | 是否已验证 |
93
+ | --- | --- | --- | --- |
94
+ | **1. 宿主半不导入 `@deepseek-ai/dsh-tools` 的 `defineTool`**:插件安装在工作区(`/root/show_me_data`),解析不到 profile 的 `node_modules`;若把它写进 dependencies,pnpm 会再装一份 `dsh-tools`,造成两套实现(ToolRuntime 的 `instanceof`/symbol 会分叉) | `src/host/tools/register.js` | `09 §4` 增加纪律说明;`src/host/tools/define-tool.js` 手写 `ToolDefinition`(含自实现的参数校验与 `render`);`test/host/tools.test.js` 断言 schema 与渲染行为 | 是(M7 测试) |
95
+ | **2. `llm.stream` 的 `messages` 要求 `Message[]`(带 `id`/`source`)而非裸 `{role,content}`**,而构造器 `createUserMessage` 在 `dsh-llm` 包内(同样存在偏差 1 的解析问题) | `src/host/ai/dsh-llm-gateway.js` | 网关构造最小合法消息对象;M9 用真网/真 provider 冒烟确认(失败则自动降级 deterministic,功能不受影响) | M9 |
96
+ | **3. `shell.overlay` 是 click-through 容器** | 面板根节点 | `05 §1` 补充:根节点必须 `pointer-events:auto`,否则面板可见但不可点 | 是(M8 CSS) |
97
+ | **4. `node --test test/` 在 Node 24 不把目录当测试入口** | 运行命令 | `package.json` scripts 改为 `node --test "test/**/*.test.js"`;文档中的等价命令同步 | 是(`npm test` 全绿) |
98
+
99
+ ---
100
+
101
+ ## 附:本次会话的源可用性巡检(`scripts/probe-sources.mjs`)
102
+
103
+ | 源 | HTTP | 耗时 | CORS | 结论 |
104
+ | --- | --- | --- | --- | --- |
105
+ | FRED CSV | 200 | 884ms | 缺失 | 主干源可用 |
106
+ | FRED 多系列 | 200 | 2007ms | 缺失 | 可用 |
107
+ | 东方财富行情 | 200 | 76ms | 回显 Origin | 可用 |
108
+ | 东方财富宏观 | 200 | 126ms | `*` | 可用 |
109
+ | 美国财政部 fiscaldata | 200 | 2619ms | `*` | 可用 |
110
+ | World Bank | 200 | 1211ms | `*` | 可用 |
111
+ | ECB SDW | 200 | 1026ms | `*` | 可用 |
112
+ | BLS API | 403 | 1519ms | — | 不可用(预期,改用 FRED 镜像) |
113
+ | Yahoo Finance | 403 | 709ms | — | 不可用(预期) |
114
+
115
+ > 巡检退出码 0:**六个适配器源在本次会话全部实测可用**,M3 的 fixtures 可全部录制。
@@ -0,0 +1,153 @@
1
+ # 13 · 验收记录
2
+
3
+ > 逐条实测并记录证据。**不要写"通过"而不给证据。**
4
+
5
+ 验收环境:GUI URL `http://127.0.0.1:38080` 插件版本 `0.1.0`(`lib/host.js` 49 个模块 / `lib/client.js` 91 538 B)
6
+ 验收时间:`2026-09-12`(对线上实例复验通过) 验收方式:**对挂载后的真实 GUI 实例发 HTTP/SSE 请求**(`http://127.0.0.1:38080`,即当前正在运行的 web profile)
7
+ 执行脚本:`node /tmp/accept.mjs`(本文件每条"证据"列给出等价请求);`docs/14` 记录了本次验收发现并修复的 7 个缺陷。
8
+
9
+ > **验收姿势的诚实说明**:A 段绝大多数条目通过**真实 HTTP/SSE 请求**验证(面板数据、AI 流式、
10
+ > 溯源、自选增删、降级路径),这些是"面板是否工作"里可机械核对的部分。**纯视觉部分**(骨架屏
11
+ > 动画、hover tooltip、数据表滚动定位、颜色观感、DOM 节点数)无法由 HTTP 验证,我用 DOM 结构
12
+ > 断言代替,并在文件末尾单列成"待人工确认"清单,不冒充已验收。
13
+
14
+ ---
15
+
16
+ ## A. 交互清单(来自 `01` §3)
17
+
18
+ **验收实例 = 线上实例本身**:`http://127.0.0.1:38080`,11 个适配器全部加载,
19
+ `us.dgs10=4.96` / `us.dgs2=4.63`(证明缓存键修复生效),52 张卡 0 张失败,
20
+ `/ai/propose` 返回 200(证明用例注入修复生效)。下表逐条来自对该实例的真实请求。
21
+
22
+ | # | 交互 | 期望 | 实测结果 | 证据 | 结论 |
23
+ | --- | --- | --- | --- | --- | --- |
24
+ | 1 | 打开面板 | 有缓存立即显示 + 更新时间 | 52 张卡、`generatedAt` 存在;冷缓存 2 548ms,命中缓存 73ms | `GET /overview?range=1Y` | ✅ |
25
+ | 2 | 切换时间范围 | 全部重算;切回命中缓存 | `range=6M` 生效;同范围第二次 73ms 返回 | `GET /overview?range=6M` ×2 | ✅ |
26
+ | 3 | 点击来源徽章 | 打开原始 URL | **52/52** 张卡都带 `https://` 溯源 | 遍历 `metrics[].sourceRef.url` | ✅ |
27
+ | 4 | 点击卡片 | 主图 + 统计带 + 数据表 + AI | 250 个点 + 完整统计(mean=4.29);来源指向财政部收益率曲线数据集页 | `GET /series?indicator=us.dgs10&range=1Y` | ✅ |
28
+ | 5 | AI 解析 | 流式;可回溯;无 LLM 时标注确定性 | SSE:`text` 事件 + `done` 事件;`usedPoints=2`;`mode=deterministic` | `POST /ai/explain {stream:true}` | ✅ |
29
+ | 6 | 时段 AI 总结 | 4 段结构 + 引用 | 378 字、引用 102 个数据点、`mode=deterministic` | `POST /ai/summary` | ✅ |
30
+ | 7 | AI 问答 | 只用面板数据;超范围答"数据不足" | 范围内引用 6 点;问"明天油价会涨吗"→ `insufficient=问题超出面板数据覆盖范围`、0 引用 | `POST /ai/ask` ×2 | ✅ |
31
+ | 8 | 添加指标(目录) | 检索 → 加入 → 落盘 → 出现在「我的」 | 检索"非农"命中 `us.payrolls.change`、`us.payrolls.3m.avg`;加入 `created`,再次加入 `exists`(幂等) | `GET /catalog/search`、`POST /watchlist` | ✅ |
32
+ | 9 | 添加指标(对话) | 自然语言 → 预览 → 不可行时说明原因 | 提"德国 Ifo 商业景气指数"→ `candidates=0`、`unsupported` 带原因与替代方案(**不编造 seriesRef**) | `POST /ai/propose` | ✅ |
33
+ | 10 | 移除/排序自定义项 | 立即生效并持久化 | `created → exists → updated(order=3) → removed` 全链路 | `POST /watchlist` ×4 | ✅ |
34
+ | 11 | 单个数据源失败 | 该卡降级 + 其余不受影响 | 52 张卡失败 0 张;26 张 `stale` 全部携带 `lastSuccessAt`,其余 `fresh`;封源路径返回 `status=error` 的卡片而非消失 | `GET /overview` | ✅ |
35
+ | 12 | 全部数据源失败 | 降级态 + 横幅 | 卡片全部保留(`status=error`)+ 顶部横幅渲染 | `test/app/overview-series.test.js`(全量 `failFor`)+ `08 §2` 第 9 类缺陷用例 | ✅(测试) |
36
+ | 13 | 数据陈旧 | 按频率黄点 + tooltip | 26 张 `stale` 全带 `lastSuccessAt`;阈值按频率(日 2/周 10/月 40/季 100/年 400 天)由 `STALE_DAYS` 固化并有单测 | `GET /overview` + `test/app/refresh.test.js` | ✅ |
37
+ | 14 | 单位与口径 | 单位 + SA/NSA 标注 | 返回 `%/SA`、`指数/SA`、`万人/SA`、`pp`、`十亿美元/SA`、`百万美元/SA`、`指数/NSA` | `metrics[].unit` / `seasonal` | ✅ |
38
+ | 15 | 无网络 / 模型不可用 | 读本地快照;AI 仍可用并标注模式 | AI 三入口均有实际内容:explain/summary 自动降级为 `deterministic`(本部署模型返回空),ask 走到 `llm` 但有引用;数据侧 27 张 `stale` 卡读快照 | `POST /ai/{explain,summary,ask}` | ✅ |
39
+
40
+ **A 段合计 15/15**:14 条由对线上实例的 HTTP/SSE 实测,1 条(全源失败横幅)由单元测试 + DOM 断言证明。
41
+ 自动验收脚本:`node /tmp/accept.mjs` → **14/14 通过**(第 12 条为单元测试覆盖)。
42
+
43
+ ---
44
+
45
+ ## B. 性能预算(来自 `05` §7)
46
+
47
+ 环境同上;"冷"= 该范围首次取数,"热"= 命中缓存。
48
+
49
+ | 指标 | 预算 | 实测 | 结论 |
50
+ | --- | --- | --- | --- |
51
+ | 首屏(有缓存)→ 可见骨架 | < 300ms | 客户端骨架为同步首帧;服务端命中缓存 73ms | ✅ |
52
+ | 首屏(有缓存)→ 完整渲染 | < 800ms | 命中缓存 73ms(不含浏览器渲染耗时) | ✅(HTTP 部分) |
53
+ | 范围切换(命中缓存) | < 150ms | 73ms | ✅ |
54
+ | 范围切换(冷缓存 10 指标) | < 4s | 全量 52 指标冷启动 2 548ms | ✅ |
55
+ | 单指标详情(含图) | < 200ms | 缓存路径 < 100ms;冷取数取决于上游 | ✅(缓存路径) |
56
+ | SVG 路径(1 万点) | < 30ms | 单测实测 < 10ms,断言 < 30ms | ✅ |
57
+ | 面板 DOM 节点 | < 3000 | 未测量(需浏览器 devtools) | ⏳ 待人工 |
58
+
59
+ ---
60
+
61
+ ## C. 测试与门禁(来自 `08` §8)
62
+
63
+ | # | 条件 | 命令 / 方式 | 结果 |
64
+ | --- | --- | --- | --- |
65
+ | 1 | 全绿 | `node --test "test/**/*.test.js"` | ✅ **683 通过 / 0 失败**(3 个门控跳过:`RUN_NET` 网络用例、`RUN_BOOT` 启动门禁 2 例,后者含「打包后的 tarball 能真启动」) |
66
+ | 2 | 覆盖率 | `node --test --experimental-test-coverage "test/core/*.test.js" "test/app/*.test.js"` | ✅ core 行覆盖 ≥95%(`types` 95.7 / `rules` 99.5 / `series` 96.7 / `range` 100 / `chart/*` 100);app:`overview` 100 / `series-view` 100 / `watchlist` 100 / `refresh` 95.6 |
67
+ | 3 | 分层静态断言 | `test/core/layering.test.js` | ✅ core/app 无 IO·无时钟·无 DSH 依赖;sources 不碰 fs;client 无 JSX 且只经 `/api/show-me-data` |
68
+ | 4 | 扩展演练 ×2 ≤30 分钟且未改 core | 见 `07` §11 与 `docs/14` | ✅ 加指标只改目录数据;加数据源只加 1 文件 + 1 行注册(本次实测新增 4 个源,全部是该形态) |
69
+ | 5 | 关掉 LLM 后三入口可用 | 沙箱 `ai.mode: deterministic` | ✅ explain/summary/ask 均 `deterministic` 且有引用 |
70
+ | 6 | 伪造引用被拦下 | `test/app/ai-validate.test.js`、`test/app/ai-gateway.test.js` | ✅ 6 类违规 + "编造 seriesRef → unsupported"用例 |
71
+ | 7 | 卸载无残留 | `test/host/plugin-shape.test.js` | ✅ disposer 断言:路由/工具/定时器/样式全部撤销 |
72
+ | 8 | 真网冒烟 | `RUN_NET=1 node --test "test/net/*.test.js"` | ✅ 8/8 适配器至少 1 个 seriesRef 返回 ≥5 点(上游封锁期会失败,属环境而非代码) |
73
+ | 9 | 真实启动门禁 | `RUN_BOOT=1 node --test "test/net/boot.test.js"` | ✅ 3/3:发布树自洽、profile 真实启动并服务数据、坏配置给出可读错误而非 TypeError |
74
+ | 10 | `docs/12`、`13`、`14` 已填写 | 人工 | ✅ |
75
+ | 11 | 讨论会话端到端(真模型、真会话) | `node scripts/diag-discuss.mjs <indicator\|group:US\|watch:id> [question] [port]` | ✅ 三种主体都实测:单指标(37s 得带引用回答)、`group:US`(46s 得 1546 字分组回答)、`watch:<id>`(带规则命中的快速提问);`/discuss/answer` 轮询皆拿到文本 |
76
+ | 12 | 总体分析端到端 | 同上(脚本会顺带探测 `/ai/overview`) | ✅ `mode=llm`、52 指标、5 条关注、**2181 字、0 违规**、带 `[id@date, value]` 引用;并复验不再出现"把推理当回答" |
77
+ | 13 | K线数据真实可用 | `GET /series?indicator=us.idx.spx` | ✅ 250 个点中 **250 个带 OHLC**(新浪美股本来就返回开高低收,此前被解析器丢弃) |
78
+ | 14 | 日/周/月 K 线 | `GET /series?indicator=cn.idx.sse&freq=week\|month` | ✅ 上证指数:日 726 点 → 周 154 → 月 37,且全部带 OHLC;标普 500 的 `barSizes=false`(新浪美股无重采样参数,面板据此隐藏控件) |
79
+ | 15 | 新接入数据真实可用 | `GET /series?indicator=<新 id>` | ✅ 8/8 有数据:新房价格同比 97.7、二手 95.5(2026-07,东财/统计局)、出生率 6.77‰、生育率 1.013、城镇化 66.34%、出口 401,440,957 / 进口 282,355,763(万美元,2026-08)、顺差(现算) |
80
+ | 16 | 设置与迭代端点 | `GET /settings`、`POST /iterate` | ✅ `/settings` 返回生效配置(ai.mode/model、routes=15、indicators=60);`/iterate` 200 并真的开出一个在插件工作区运行的会话 |
81
+ | 17 | 讨论上下文完整性(用户报告 8) | `POST /discuss` 单体指标 | ✅ 上下文 757 字 → **3257 字**:主体明细 + **同主题相关指标**(CPI 同比的会话现在带 `us.core.cpi.yoy`、`us.cpi`)+ 其余指标清单(一行一个,含 latest/Δ/status)+ 可用工具说明;实测真模型据此用「整体 − 核心」拆出剪刀差,不再回答"面板里没有核心 CPI" |
82
+
83
+ ---
84
+
85
+ ## D. 数据质量抽查(人工,抽 5 个指标)
86
+
87
+ "来源接口值"是我在本次会话中直接请求原始接口得到的,而非逐字阅读网页,因此标注为接口值。
88
+
89
+ | 指标 | 面板值 | 来源接口值 | 是否一致 | 备注(口径/时间差解释) |
90
+ | --- | --- | --- | --- | --- |
91
+ | `us.dgs10` 美国 10Y | 4.96%(2026-09-11) | 财政部收益率曲线 CSV `10 Yr` = 4.96 | ✅ | 与面板同源(财政部),FRED 的 DGS10 只是转发布 |
92
+ | `us.dgs2` 美国 2Y | 4.63%(2026-09-11) | 同表 `2 Yr` = 4.63 | ✅ | 修复缓存键后与 10Y 正确分离(见 `docs/14` 偏差日志 #4) |
93
+ | `cn.pmi.mfg` 中国制造业 PMI | 49.8(2026-08) | 东财 `RPT_ECONOMY_PMI.MAKE_INDEX` = 49.8 | ✅ | 月频;8 月值在 9 月表现为 stale 属正常 |
94
+ | `us.idx.spx` 标普 500 | 7656.98(2026-09-11) | 新浪美股日线 `.INX` = 7656.98 | ✅ | 与腾讯 `usIXIC`/`usDJI` 交叉核对一致 |
95
+ | `ecb.exr.usd` 欧元/美元 | 1.1592(2026-09-11) | ECB SDW `EXR/D.USD.EUR.SP00.A` = 1.1592 | ✅ | ECB 每日参考汇率,不是实时报价 |
96
+ | `ust.avg.rate.notes` 国债平均利率 | 3.345%(2026-08-31) | fiscaldata `security_desc:eq:Treasury Notes` = 3.345 | ✅ | 必须带 filter,否则混入 Bills/Bonds/TIPS |
97
+
98
+ ---
99
+
100
+ ## E. 遗留问题
101
+
102
+ | # | 问题 | 影响 | 处置 |
103
+ | --- | --- | --- | --- |
104
+ | 1 | **上游封锁**:FRED/BLS 曾按出口 IP 返回 403(数小时后自行恢复);东财 `push2his` 主动断连;腾讯 kline 501 | 相关卡片显示「数据源暂不可用」或读快照 | 已加**多源级联**(新浪 A 股 → 腾讯 → 东财)与 4 个替代源(财政部官方曲线、新浪 A 股、新浪美股、腾讯);无源可用的 8 个指标标 `unsupported` 并记录调查过程(`docs/03 §3.6`)。属环境问题,不是插件缺陷 |
105
+ | 2 | `cn.bond.corp.index`(上证企债指数)最后观测为 2020-12-25 | 该卡长期显示 2020 年数值 | 上游本身不再更新该序列;已按"陈旧"标注,待找到活跃序列后替换 |
106
+ | 3 | 恒生指数与 7 个海外指数无可用源 | 面板不显示这些卡片(不显示空卡) | 记入 `unsupported` 清单,含逐条实测过程 |
107
+ | 4 | 纯视觉项未由 HTTP 验证 | 可能与人眼预期有差异 | 见下方"待人工确认"清单 |
108
+ | 5 | `ai.mode: auto` 时本部署模型返回空/非 JSON | AI 输出走"校验不通过 → 降级确定性摘要"(产品行为正确,但用户看不到模型内容) | 已修"空回答被当作通过"的缺陷;模型侧需确认 provider/key 可用性 |
109
+ | 6 | **`/discuss` 首轮必定失败**:`InboxTarget` 传了对象而非字符串联合 | 独立会话打开后永远没有回答,面板只得显示"回答在会话里" | 已修(`'next-turn'`),并用真模型端到端复验:`/discuss` 68ms 返回 → 轮询 `/discuss/answer` 第 8 次拿到 30 字回答 |
110
+ | 7 | **8 个模型工具的返回值全被宿主拒绝**(`value is not lossless JSON`) | 会话里的模型拿不到任何面板数据,只能凭摘要作答,并主动报告工具报错 | 已修:工具值在交给宿主前剔除 `undefined` 键;新增自查函数报出具体路径;真模型复验 `data_series` 返回真实点数与来源并据此作答 |
111
+ | 8 | **面板数字自相矛盾**:`/series` 的 `stats.count`(=13) 与返回点数(=10) 不一致;CPI 卡片 `Δ=0.0` 与 `Δ%=1.5` 不一致 | 真模型两次据此判定"面板有 bug",并拒绝为数字背书 | 已修:`count` 一律等于返回点数;`changeAbs` 与 `changePct` 同源同差值。**这两条是真模型读面板读出来的** |
112
+ | 9 | **AI 输出随机回退确定性摘要**("未通过溯源校验,已回退为确定性摘要") | 面板 AI 时好时坏;用户按"10 项违规"看到降级 | 已修三处:摘要自己印出的数字(标签 `标普 500`、`missing`、`importance`)现在可溯源;两个已发布观测值之差(`4.5−4.1=0.4`)不再算杜撰;`attentionScore` 不再出现在摘要里。真模型 6 指标 × 2 轮 = 12 次全部 `mode=llm / violations=0` |
113
+ | 10 | **摘要里统计量与点列表口径不一致**:`mean=2616.73 points=250 missing=2577` 配近一年 250 个点 | 真模型花大半篇幅讨论这个矛盾而不是分析指标 | 已修:摘要按面板区间裁剪并重算统计量,发布出去的每个数字都描述同一窗口 |
114
+ | 11 | **切换指标后仍显示上一个指标的 AI 回答** | 读者看到 A 的回答配 B 的数字 | 已修:切换指标即清空 AI 与讨论态(同一指标不重置,避免打断正在生成的回答) |
115
+ | 12 | **确定性摘要输出 `区间:undefined .. undefined`** | 降级摘要缺少时间范围 | 已修:网关收到的是解析后的 `{ from, to }`,不再被请求里的预设字符串覆盖 |
116
+
117
+ ### 待人工确认(需要人眼看一下;我无法用 HTTP 断言)
118
+
119
+ 1. 浮层触发器在右下角、点开后不遮挡输入框(`pointer-events` 正确)。
120
+ 2. 卡片 sparkline 与详情主图的视觉呈现(我验证了路径字符串与数值,未看渲染)。
121
+ 3. 涨跌颜色符合 `polarity`:CPI 上行不应是绿色、失业率上行不应是绿色。
122
+ 4. hover tooltip 与「查看数据表」按钮的实际交互。
123
+ 5. 面板 DOM 节点数 < 3000(devtools 计数)。
124
+
125
+ ---
126
+
127
+ ## F. 本次修复的复验方式(部署后需重启进程)
128
+
129
+ 插件在 profile 启动时加载,**改完代码必须重启 web profile 进程**(容器重启或 profile 重启均可),浏览器再硬刷新一次。
130
+
131
+ | # | 复验项 | 期望 |
132
+ | --- | --- | --- |
133
+ | 1 | 面板内提问 | 点击「提问」后有模型文本(不再是"未配置模型,本回答由本地规则生成");`ai.mode=llm`、`violations=0` |
134
+ | 2 | 点「开独立会话讨论」 | 面板先显示"已在新会话中打开…",随后同一区块出现「会话里的回答」,内容引用面板数字;GUI 同时切到新会话,可继续追问 |
135
+ | 3 | 会话上下文 | 新会话首轮就已带上面板摘要(讨论"美国 CPI 同比"直接问数字即可,无需重复粘贴数据) |
136
+ | 4 | 面板数字自洽 | 详情里的 `Δ` 与 `Δ%` 互相印证(如 3.3 → 3.4 为 +0.1 / +3.0%,旧版显示 +0.0 / +1.5%);`/series` 返回 N 个点即 `stats.count=N` |
137
+ | 5 | 会话内工具 | 在讨论会话里说"调用 data_series 取数",工具返回真实点数与来源(旧版报 `value is not lossless JSON`) |
138
+ | 6 | 切换数据卡 | 选中另一张卡后,AI 问答区**不应**再显示上一个指标的答案或"已在新会话中打开"提示;回到原卡片时该卡片自己的回答也不应残留(每次进入都是干净的) |
139
+ | 7 | AI 分析稳定性 | 连续对 3–4 张卡点「AI 解析」,都应显示模型文本(标题为 `### …`、引用形如 `[us.cpi.yoy@2026-08-01, 3.4]`),不再出现"未通过溯源校验,已回退为确定性摘要" |
140
+ | 8 | 关注项快速讨论 | 「今日值得关注」每行有「讨论」按钮;点击后该行按钮显示"正在开启会话…",随后面板出现「会话里的回答」,内容提到了**触发该条的具体原因**;GUI 同时切到新会话 |
141
+ | 9 | 总体分析 | 切到「总体分析」页签 → 选「美国」→「生成总体分析」;应返回五段结构(总体态势/关键变化/相互印证/背离/下一个该盯的数据),带 `[id@date, value]` 引用;点「深入讨论」开一个带整体上下文的独立会话 |
142
+ | 10 | 图表(K线) | 打开股市类指标(如标普 500、上证指数)详情:默认为 **K线**,应看到红/绿(按 polarity)蜡烛;切换「面积/折线/柱状」立即生效;移动指针出现竖向十字线、吸附到最近的 bar、顶部读数随指针变化,提示框显示日期与 开/高/低/收 |
143
+ | 11 | 图表(宏观) | 打开非价格指标(如 CPI):**不应**出现「K线」按钮;移动指针仍能读数;均值/高/低三条虚线参考线在右侧标注数值 |
144
+ | 12 | K线周期 | 打开上证指数详情:应出现「日K/周K/月K」,切换后点数明显减少(实测 726 → 154 → 37);打开标普 500(新浪美股为纯日线源)时**不应**出现该控件 |
145
+ | 13 | 讨论可见反馈 | 任一处点「讨论/深入讨论」:按钮立刻变「正在开启会话…」,随后出现状态卡片(会话进行中 → 会话里的回答),失败时显示原因而不是静默 |
146
+ | 14 | 设置 | 切到「设置」:每行都有当前值(如 接口前缀 `/api/show-me-data`、模型名、`30 分钟`、分组 `US / CN / GLOBAL / CUSTOM`),并注明"行配置,面板内不可改"与要编辑的 `cordis.patch.yml` |
147
+ | 15 | 分组与多选 | 「核心」页签的卡片按 美国/中国/全球/我的 分节且可折叠;勾选多张卡片后出现「已选 N / 分析选中 / 清除选择」,点「分析选中」得到一份合并分析 |
148
+ | 16 | 新数据 | 搜索「楼市/房价/生育率/进出口」应能找到 `cn.houseprice.new`、`cn.population.fertility`、`cn.trade.exports` 等 8 个新指标,且卡片有真实数值 |
149
+ | 17 | 设置-数据源测试 | 设置页每个源右侧有「测试连接」,右上角有「全部测试」;点击后显示"测试中…",结束显示"连接正常(本次测试指标数 N)"或点名失败的源。备用上游(从未被请求过)显示"未检查",属正常 |
150
+ | 18 | 迭代会话 | 「我的」里输入一个没有来源的指标(如"中国结婚登记数")→ 显示无法添加 + 「开迭代会话接入数据源」;点击后新会话已带需求与失败原因并在插件工作区开始工作 |
151
+ | 19 | 空问题也能开讨论(用户报告 8) | 不输入任何问题,直接点关注项/卡片上的「讨论」 | 按钮立刻变「正在开启会话…」,随后出现状态卡片并显示**面板代提的开场问题**("面板已代你提出开场问题:…"),会话里能看到上下文 + 问题 + 回答;不再出现"会话已结束但没有文本回答" |
152
+ | 20 | 讨论会话的工具面 | 在讨论会话里问"用 data_series 取 us.core.cpi.yoy",或直接让它读工作区文件 | 会话已加入组合:可见**技能目录**、沙箱策略为 workspace-write、`data_*` 工具返回真实数据(此前会话只挂本插件的 10 个工具,模型报告"我的工具面里没有文件系统") |
153
+ | 21 | 会话归还(失败时) | 让模型请求失败(如临时改坏凭据)后点讨论 | 面板显示"会话已创建,但本轮提问失败:…"并提供「打开会话」,而不是只留一句无处可去的错误 |
@@ -0,0 +1,150 @@
1
+ # 14 · 进度记录(每个里程碑结束时更新)
2
+
3
+ > 规则:勾选完成项,**并记录与计划的偏差**。偏差比进度更重要——它决定下一步要不要调整计划。
4
+
5
+ 图例:☐ 未开始 ▶ 进行中 ✅ 完成 ⚠ 完成但有偏差 ✖ 受阻
6
+
7
+ ---
8
+
9
+ ## 总览
10
+
11
+ | 里程碑 | 内容 | 状态 | 测试数 | 覆盖率 | 完成时间 | 偏差摘要 |
12
+ | --- | --- | --- | --- | --- | --- | --- |
13
+ | M0 | 运行时校准 + 骨架 + 契约冻结 | ✅ | 34 | core 94%(全量口径) | 2026-09-12 | 7 项假设全部核对;4 处偏差见 `12` |
14
+ | M1 | 时间与统计(纯函数) | ✅ | 42 | core 行 94.5% | 2026-09-12 | `missingCount` 增加显式 cadence 入参 |
15
+ | M2 | 图表几何(纯函数) | ✅ | 19 | core 行 94.5% | 2026-09-12 | 折线 x 轴按声明索引跨度(NaN 断点不压缩序列) |
16
+ | M3 | 数据源适配器(6 个)+ fixtures | ✅ | 65(+契约 94) | sources 离线全绿 | 2026-09-12 | 4xx 统一归 unsupported;负样本改为可重现派生 |
17
+ | M4 | 指标目录(≥45 指标) | ✅ | 25(目录 12 + 检索 13) | core 95%+ | 2026-09-12 | 目录实测 60 条;新增 movingAvg 变换 |
18
+ | M5 | 值得关注规则引擎 + digest | ✅ | 39(规则 23 + 排序/摘要 16) | core 95%+ | 2026-09-12 | 8 条规则全部可复现;黄金基线已生成 |
19
+ | M6 | 应用层用例 | ✅ | 70 | app ≥ 95%(refresh 95.6/series-view 100/overview 100/watchlist 100) | 2026-09-12 | 派生指标自动带上操作数;app 层禁止定时器(sleep 改为注入) |
20
+ | M7 | 宿主半装配(路由/工具/仓储) | ✅ | 151(shape 13 + tools 29 + routes 12 + ai gateway 35 + build-client 22) | host 88–97% | 2026-09-12 | 手写 ToolDefinition(不依赖 dsh-tools);routes 前缀注册 |
21
+ | M8 | 浏览器半(面板/图表/溯源) | ▶ | 22(构建 + 组件结构) | client 已构建 | 2026-09-12 | `lib/client.js` 91KB;组件经 `initPanel(React)` 绑定后可测 |
22
+ | M9 | AI 层(端口/提示词/校验/SSE) | ✅ | 73(提示词/校验 22 + 网关/propose 16 + LLM 网关/SSE 35) | ai-validate 99% | 2026-09-12 | 校验失败一次重试后降级;AI 结果指纹缓存 |
23
+ | M10 | 安装联调打磨 + 验收 | ✅ | 598 通过 / 0 失败 | core ≥95% / app ≥95% | 2026-09-12 | 7 个缺陷(含 1 个致命)全部修复并补门禁 |
24
+
25
+ ---
26
+
27
+ ## 任务卡级进度
28
+
29
+ ### M0
30
+ - ✅ T0.1 运行时校准(`docs/12-runtime-verified.md` 已填写,7/7 项有实测结论)
31
+ - ✅ T0.2 骨架与契约(`test/core/types.test.js` 17 例绿;`package.json` + `dsh.client` 元数据)
32
+ - ✅ T0.3 测试基建(`test/helpers/{fixtures,fake-fetch,memory-repos,repo-contract,react-stub,client-shim}.js`)
33
+ - ➕ 追加:`test/core/layering.test.js`(分层静态断言,读注释剥离后的源码文本)
34
+
35
+ ### M1
36
+ - ✅ T1.1 `core/time/range.js`(12 例:跨年/闰年/月末钳制/YTD/不读系统时间)
37
+ - ✅ T1.2 `core/stats/series.js`(23 例:缺失标记/同月对齐 yoy/σ≥6/分位≥12/斜率/z)
38
+ - ✅ T1.3 `core/stats/derive.js`(7 例:内连接/重叠不足/除零跳过/avg 全operand)
39
+
40
+ ### M2
41
+ - ✅ T2.1 `core/chart/{scale,line,bar,axis}.js`(19 例,含空序列/单点/全等值/NaN 断点/1 万点抽稀 <30ms)
42
+
43
+ ### M3
44
+ - ✅ T3.1 契约测试 + 注册表非空断言(`test/contract/source-adapter.test.js`,94 例,遍历注册表)
45
+ - ✅ T3.2 `scripts/record-fixtures.mjs` + `scripts/derive-fixtures.mjs`(49 个 fixture,含 6 类异常样本)
46
+ - ✅ T3.3 六个适配器:fred / eastmoney-quote / eastmoney-macro / us-treasury / worldbank / ecb(65 例)
47
+ - ✅ T3.4(spike 结论)中国 10Y 国债收益率与 LPR **找不到可用源 → 标 unsupported**,调查记录见 `03 §3.6`
48
+ (实测:`RPT_ECONOMY_LPR` code 9501;`RPT_BOND_CB_YIELD` 不存在;`0.100303` rc=100;
49
+ chinabond `yield_main` 404;新浪债券 403;CFFEX 只有单日 XML 且是期货价格不是收益率。
50
+ 替代:World Bank `FR.INR.LEND`(中国贷款加权平均利率,年频)**可用**,作为 LPR 的近似口径记录)
51
+ - ✅ 真网冒烟 `test/net/sources-live.test.js`(RUN_NET=1,8/8 通过)
52
+
53
+ ### M4
54
+ - ✅ T4.1 目录完整性测试(含 fixture 覆盖断言、无循环派生、无猜测的 unsupported 清单)
55
+ - ✅ T4.2 检索/解析测试(中文关键词/英文别名/模糊/空查询/无命中给建议)
56
+
57
+ ### M5
58
+ - ✅ T5.1 八条规则(每条:命中 / 临界 / 数据不足)
59
+ - ✅ T5.2 `rank.js` + `test/fixtures/insight-golden.json`(`scripts/regen-golden.mjs` 可重现)
60
+ - ✅ T5.3 `digest.js`(60 指标下 ≤8000 字符,importance=5 全覆盖,省略说明可见)
61
+
62
+ ### M6
63
+ - ✅ T6.1 `app/refresh.js`(TTL/并发去重/降级/重试/部分失败)
64
+ - ✅ T6.2 `app/overview.js`(分组 Metric + Noteworthy + 来源健康度)
65
+ - ✅ T6.3 `app/series-view.js`(统计/区间/变换/对比/结构化错误码)
66
+ - ✅ T6.4 `app/watchlist.js` + 内存版与文件版**同一套契约测试**(含损坏文件恢复)
67
+
68
+ ### M7
69
+ - ✅ T7.1 `host/index.js`(插件形状 + 卸载干净 + 缺服务降级)
70
+ - ✅ T7.2 `host/http/routes.js`(10 条路由、结构化错误、不泄漏堆栈)
71
+ - ✅ T7.3 `host/infra/*-repo.js`(与内存版同契约 + 损坏文件恢复)
72
+ - ✅ T7.4 `host/tools/register.js`(8 个工具、schema 自校验、节流、幂等)
73
+
74
+ ### M8
75
+ - ✅ T8.1 `scripts/build-client.mjs` + 构建测试(零依赖、JSX 构建期失败、符号唯一性)
76
+ - ✅ T8.2 浏览器半纯函数与组件结构测试(client shim + React stub)
77
+ - ☐ T8.3 GUI 手动验收 15 条(→ `docs/13-acceptance.md`,M10 一起做)
78
+
79
+ ### M9
80
+ - ✅ T9.1 提示词与 digest(含空数据提示词)
81
+ - ✅ T9.2 `app/ai-validate.js`(引用/日期/数值/未溯源数字/预测措辞 + 伪造 seriesRef 测试)
82
+ - ✅ T9.3 三个 Gateway 适配器(deterministic / dsh-llm / propose 校验闸)
83
+ - ✅ T9.4 SSE 端点(text 分块 → done 携带校验后的 AiResult)
84
+
85
+ ### M10
86
+ - ✅ 安装(pnpm 12.4.1 + `dsh plugin --profile web add /root/show_me_data` + patch 行)
87
+ - ✅ 重启 web profile(`dsh web --host 127.0.0.1 --port 38080`),GUI 恢复并加载插件
88
+ - ✅ 性能预算实测(→ `13-acceptance.md` B 段)
89
+ - ✅ 真网冒烟(`RUN_NET=1`,8/8 适配器)+ 真实启动门禁(`RUN_BOOT=1`,3/3)
90
+ - ✅ 扩展演练:本次实际新增 4 个数据源(`us-treasury-rates`/`tencent`/`sina-cn`/`sina-us`),
91
+ 每个都是「1 个文件 + registry 1 行 + fixtures」,未改任何 core 逻辑文件——即验收标准所要求的形态
92
+ - ✅ 数据质量抽查 6 个指标(→ `13-acceptance.md` D 段,全部与上游一致)
93
+ - ⏳ 纯视觉 5 项待人工确认(列在 `13-acceptance.md` 末尾)
94
+
95
+ > `scripts/smoke.mjs` **未实现**:其职责已由 `test/net/sources-live.test.js`(真网冒烟)+ `test/net/boot.test.js`
96
+ > (真实启动 + 路由探测)覆盖,因此没有再写一个功能重复的脚本。这是与 `07` 计划的一处有意偏差。
97
+
98
+ ---
99
+
100
+ ## 偏差日志
101
+
102
+ | 时间 | 里程碑 | 计划 | 实际 | 原因 | 处置 | 是否已同步文档 |
103
+ | --- | --- | --- | --- | --- | --- | --- |
104
+ | 2026-09-12 | M10 | 按 `09` 文档挂载后即应可用 | **首次挂载导致 web profile 启动失败(整个进程退出)** | `Config` 导出的是普通默认值对象,而 Cordis 在 `apply` 之前会调用 `Config["~standard"].validate(config)`;该调用抛 TypeError,错误发生在 plugin tree 加载阶段,因此不是"本行失败"而是整个 profile 启动失败 | ①`src/host/config.js` 提供真正的 Standard Schema(永不抛错,非法配置返回 issues);②`apply` 改为读取 `resolveConfig().value`;③新增 `test/net/boot.test.js`:真实挂载到 profile 副本并以独立 `DSH_HOME`/端口启动,作为 M10 的回归门禁 | 是(`13`/`14` + `09` 待补) |
105
+ | 2026-09-12 | M10 | 挂载后 `/api/show-me-data/*` 应能取数 | 路由可用但**所有源头取数失败**(`errorKind=parse`,`Cannot read properties of undefined (reading 'fetch')`) | 两处依赖注入命名不一致:`sources/http.js` 声明 `fetchImpl`,六个适配器传的是 `deps.fetch`;且 `app/refresh.js` 把 `runtime.deps` 传下去,而调用方本来就是 deps bag | ①统一端口名为 `fetch`(`deps.fetch`);②`refresh` 直接下发 `runtime`;③`test/app/refresh.test.js` 增加断言:适配器收到的第三个参数必须含 `fetch` 与 `clock`,让同类问题离线即暴露 | 是 |
106
+ | 2026-09-12 | M10 | `lib/host.js` 可直接被 profile 加载 | 首次安装后 `main` 文件缺失(只有 `lib/client.js`) | 构建脚本只生成浏览器半;宿主半此前只被 `src/` 引用 | 新增 `scripts/build-host.mjs`:把 `src/{core,ports,app,sources,host}` 发布到 `lib/` 并生成 `lib/host.js` 再导出;构建时校验发布树内所有相对导入可解析 | 是(`09` 待补) |
107
+ | 2026-09-12 | M10 | 一个 seriesRef 一个缓存条目即可 | **同一系列的不同参数互相覆盖**:`us.dgs10`/`us.dgs2`/`us.dgs3m` 都显示 10Y 的值(4.96) | `cacheKey` 只取 `column/country/filter` 三个已知参数名,忽略了 `params.tenor`(以及任何未来新增的参数),于是三个期限共用一个缓存键与一个 in-flight promise | `paramsKey()` 改为**对所有参数按键排序后序列化**进缓存键;新增回归测试:同系列不同 tenor 必须各自取数,且同 tenor 第二次命中缓存 | 是 |
108
+ | 2026-09-12 | M10 | 8 个模型工具 + 10 条路由全部可用 | **`/ai/explain`、`/ai/summary`、`/ai/ask` 返回空回答且被判定为"通过"** | 本部署的模型路由可用但返回空内容;而校验器把空字符串当作"没有违规"从而接受 | `validateAnswer` 增加"空回答即违规",从而走"重试一次 → 降级确定性摘要"的正确路径;同时把**用户提问原文**从禁用措辞检查中豁免(回答会复述问题,不能因此判为模型在预测) | 是 |
109
+ | 2026-09-12 | M10 | propose 对无关需求应给出 unsupported | 弱模糊命中被当成候选,"德国 Ifo 商业景气指数"被匹配到"美元指数"(都含「指数」),失败后 unsupported 为空 | 确定性网关把低分命中直接作为候选;且被否掉的候选只进 `rejected` | 引入**每路径置信度下限**(目录检索 60 / 本地摘要匹配 3,两套评分量纲不同已在注释说明);每个 rejected 都转成带原因+替代方案的 unsupported;匹配分改用 `matchScore` 字段,避免与条目自身的关注分 `score` 混淆 | 是 |
110
+ | 2026-09-13 | M10(交付后修复) | 面板「提问」应立即作答 | **提问输入框完全没反应,AI 解析按钮也无输出** | `DetailDrawer` 只把 `ai`/`onAsk` 转发给 `AiPanel`,`onQuestionChange`/`onSubmitQuestion` 未传:输入第一个字符就调用 undefined,React 中止渲染且用户看不到任何错误 | 补齐三个 props;新增**接线测试**:渲染抽屉后断言输入框的 `value`/`onChange`/`onKeyDown` 都存在且可调用(这类"props 漏传"只有真的驱动组件才会暴露) | 是 |
111
+ | 2026-09-13 | M10(交付后修复) | 面板测试能发现组件内部问题 | **组件结构断言此前是"假绿"**:client shim 的 React stub 用自己的私有 `Symbol('react-stub.element')` 判定元素,而 src/client 用的是真 React,真元素不被识别 → `textOf`/`elementsOf` 走到 `String(type)` 分支返回垃圾文本,断言在错误的前提下通过 | `react-stub` 改用 `Symbol.for('react.element')`(与 React 同一个全局符号),并让遍历对**函数式组件**求值,从而真正走进组件内部 | 是 |
112
+ | 2026-09-13 | 新功能 | — | 新增「**开独立会话讨论**」:每次点击新建一个真正的 DSH 会话,把面板 digest 作为上下文注入,问题带过去,`wait:false` 让接口 ~100ms 返回后由 GUI 切换到该会话,回答在会话里流式生成(可继续追问、换模型) | 面板内部 AI 只能一问一答、无法追问与换模型;而 DSH 已有完整会话基建 | 新增 `host/ai/discussion.js`(会话创建/上下文注入/回复抽取/上限超时/dispose)、`POST /discuss` 路由、客户端按钮与错误态;实测:44s/14s 得到带引用的真实回答;`wait:false` 返回 99ms | 是 |
113
+ | 2026-09-12 | M10 | 派生指标失败时卡片仍需可溯源 | `cn.bond.credit.spread` 因两条序列无重叠而 `missing`,该卡 `sourceRef` 为 undefined(52 张卡里唯一一张没有溯源) | 派生分支只在成功时构造 sourceRef | 失败时也带上操作数的溯源并标注「(现算)」 | 是 |
114
+ | 2026-09-12 | M10 | 8 个模型工具 + 10 条路由全部可用 | **`/ai/propose` 返回 500**(`useCases.ai.propose is not a function`) | `createAiUseCases` 需要把 propose 用例作为参数注入,`host/index.js` 却漏传;`ai.propose` 于是落到内部 stub 分支并抛错 | 先在 `createAiUseCases` 之前构造 propose 用例再注入;新增 M7 回归测试直接打该路由(这类"参数漏传导致的路由 500"只有真正调用才会暴露) | 是 |
115
+ | 2026-09-12 | M10 | propose 对无关需求返回 unsupported | 弱模糊命中被当成候选(问"德国 Ifo 商业景气指数"却提出"美元指数",因为都含「指数」),失败后 unsupported 为空、用户看不到原因 | 确定性网关把 score=12 的弱命中直接作为候选;且被否掉的候选只进 `rejected`,没有转成用户可读的 unsupported | 网关加**置信度下限**(`WEAK_MATCH_SCORE=60`);`propose` 把每个 rejected 转成带原因的 unsupported;目录内的条目回显时跳过重复 schema 校验(目录测试已保证其合规) | 是 |
116
+ | 2026-09-12 | M10 | 浏览器半构建产物可嵌入任意模块 | **bundler 用模板字符串包裹模块体**:模块里本来就有模板字符串(图表、API 层都用),反引号会截断产物 | `build-client.mjs` 用模板字符串拼装整个 bundle,而 `src/core/chart/line.js`、`src/client/*.js` 的代码里本身含反引号 | 改为**字符串拼接**生成外层包装,模块内容不再参与模板求值;同时补一个回归门禁:产物必须能被 `new Function` 解析(已有测试覆盖) | 是 |
117
+ | 2026-09-12 | M10 | 单个源失败不应拖慢整块面板 | 被封锁的源会让面板刷新接近浏览器 8s 超时 | 每个上游请求最多 15s,而卡片是并发刷新的 | `refresh` 为每个源注入 `AbortSignal.timeout(6000)`(调用方自带 signal 时优先),单个源 6s 内必须给出结果或降级;新增测试断言信号传递 | 是 |
118
+ | 2026-09-12 | M10 | 上游源稳定可用 | 首次联调时 FRED/BLS 全线 403(Akamai,按出口 IP 拒绝),东方财富 `push2his` 主动断连,腾讯行情随后返回 501 | 三个上游各自按其 WAF 策略封锁本网络;**均非插件缺陷**(FRED 数小时后自行恢复,证明是限流而非封禁) | 新增替代源,并把行情改为**多源级联**(`quote`:腾讯 → 东方财富,记住上次成功的上游),新增 `us-treasury-rates`(财政部官方收益率曲线,代替 FRED 的 DGS*)与 `sina-us`(美股三大指数日线);无法取得日线的 7 个海外指数按纪律标 unsupported 并记录调查过程 | 是 |
119
+ | 2026-09-13 | 交付后修复(用户报告 3) | 面板内提问与 AI 解析都应有响应 | 面板按钮已有响应,但**独立会话永远得不到回答**:`/discuss` 返回 502 `session-turn-failed`(`Cannot read properties of undefined (reading 'length')`) | 网关把 `agent.send()` 的第二个参数写成 `{ kind: 'pre-turn' }`,而宿主契约里 `InboxTarget` 是**字符串联合** `'next-turn' \| 'next-step'`(`dsh-agent/lib/types/runtime-types.d.ts:8`):非法目标在投递时抛 TypeError | 两处改为 `'next-turn'`;`test/host/discussion.test.js` 断言收到的目标就是该字符串。**教训**:伪造的宿主入参形状只有真的跑一遍才会暴露——本次是先修好沙箱(见下一条)才第一次真正投递成功 | 是 |
120
+ | 2026-09-13 | 交付后修复 | `RUN_BOOT=1` 门禁能证明插件真的挂上了 | **启动门禁是"假绿"**:沙箱里指向本插件的相对软链少了一级(`../../../../../`),链是断的,启动日志里三行 `Cannot find package` 却被测试忽略——而 HTTP 表面之所以能通过,是因为**探针打到的是宿主里另一个已在运行的实例**,不是沙箱 | ①软链改用绝对目标,并在启动前断言 `lib/host.js` 可解析;②另发现 `dsh` 外壳脚本启动时 loader 拿不到 profile 的 `node_modules`,必须像门禁那样 `node --expose-internals /usr/local/bin/dsh`;③新增 `scripts/diag-discuss.mjs` 作为端到端(真模型、真会话)诊断 | 是 |
121
+ | 2026-09-13 | 交付后修复 | 模型失败时应能看出原因 | **模型请求失败被当成"成功但回答为空"**:凭据缺失/上游报错时 agent loop 记录失败并回到 idle,`whenIdle()` 正常 resolve,网关只读文本 → 面板显示"没有输出",用户在会话里也看不到任何解释 | ①新增 `extractTurnFailure()`:从 `assistant/chunk.finish.reason` 与 `turn/end.reason` 识别 `error`/`aborted`;②`discuss()` 因此返回结构化 `session-turn-failed`(带原始 message),面板显示"模型请求失败:…"而不是空回答;③`agent/session-turn-failed` 情形下仍回传 `sessionId`,会话保留可查看 | 是 |
122
+ | 2026-09-13 | 交付后修复 | 8 个模型工具应能被模型调用 | **所有 `data_*` 工具都返回 `value is not lossless JSON`**(真模型在会话里主动报告:"I attempted every read path — all five failed with the same host-level serialization error") | 宿主在把工具返回值快照进会话前会校验"无损 JSON":**`undefined` 属性本身即非法**(不是像 `JSON.stringify` 那样忽略)。`data_series` 的 `compare`、`data_health` 的若干字段在缺省时是 `undefined`,于是整个结果被拒,模型拿不到任何数据 | ①`define-tool.js` 新增 `dropUndefined()` 在交给宿主之前剔除缺省键(数组空洞转 `null`,不改变点数);②新增 `findNonLossless()` 自查并**报出具体路径**(`data_series.output.compare is undefined`),把"值不是无损 JSON"变成可定位的错误;③新增 4 个单元测试 + 真模型验证:工具返回真实点数与来源并被引用 | 是 |
123
+ | 2026-09-13 | 交付后修复 | 面板与摘要中的数字应自洽 | 真模型两次指出面板自相矛盾:①`/series` 返回 10 个点而同一响应的 `stats.count` 写 13,摘要里也是 `points=13` 却只列 12 个日期;②CPI 卡片同时显示 `Δ=0.0` 与 `Δ%=1.5` | ①`seriesView`/`seriesFor` 的统计量按**整段抓取序列**计算(YoY 需要更早的点),而 `points` 按区间裁剪——两者混用同一个 `count`;②`changeAbs` 用显示精度取整(1 位小数),把 0.05pp 的变动抹成 0.0,而 `changePct` 由未取整差值算出 1.5 | ①新增 `describeReturned()`:统计量仍按整段算,但 `count` 一律等于返回的点数(两处调用点统一);②`changeAbs` 保留一位**保护位**(`decimals + 1`)且与 `changePct` **同源同差值**,二者可按 `changePct = changeAbs / prev` 互验;③新增 3 个回归测试。这两条都是"真模型读面板读出来的 bug",已记入 `13-acceptance.md` | 是 |
124
+ | 2026-09-13 | 新增能力 | — | 新增 `GET /discuss/answer?session=…`:面板开独立会话后**轮询取回回答并就地显示**(会话照旧自动打开,可继续追问) | 原实现 `wait:false` 立即返回空串,回答只出现在新会话里,面板自己的 AI 区块一直空着——"点了没反应"的观感有相当一部分来自这里 | 网关新增 `answer(sessionId)`(按会话记录本轮事件游标,返回 `pending/done/failed` + 文本,非本插件会话返回 404);客户端 `api.discussAnswer()` + 1.5s 轮询(上限 3 分钟、卸载即取消);AI 区块显示"会话里的回答" | 是 |
125
+ | 2026-09-13 | 交付后修复(用户报告 4) | 切换指标后 AI 区块应显示当前指标的答案 | **换一个数据卡后,AI 问答区仍显示上一个指标的答案与"已在新会话中打开"提示**——读者看到的是 A 指标的回答配 B 指标的数字 | `openDetail()` 只重置了 `detail`/`detailLoading`,`ai`(含 `text`/`result`/`error`)与讨论态(`discussAnswer`/`discussPending`/`discussSession`/`discussError`)留在 store 里跨指标复用 | 新增 `store.applyIndicatorChange()`:选中不同指标时清空 AI 与讨论态(重新选中同一指标则原样保留,不打断正在生成的回答),由 `openDetail()` 调用;新增接线测试断言"切到另一个指标后旧回答不再存在,且同一指标不重置" | 是 |
126
+ | 2026-09-13 | 交付后修复 | `ai.mode: auto` 时面板应显示模型回答 | **`/ai/explain` 随机退化为"未通过溯源校验,已回退确定性摘要"**(实测 spx/unrate 必现,dgs10/cpi 偶发) | 溯源校验把"数字必须能在数据里找到"理解得太窄:①**摘要自己印出来的数字**(`标普 500` 的 500、`missing=112`、`importance=5`、`attentionScore=271.5`)不在校验白名单里,模型复述即被判"无来源";②两个观测值相减得到的差(`4.5−4.1=0.4`)同样被判无来源,而两端就在同一句里被引用 | ①校验新增 digest 文本作为溯源来源(`digestText` 参数由三处 `runValidated` 传入)——摘要印过的数字按定义可溯源;②新增 `isPublishedDifference()`:允许**两个已发布测量值之差**(容差按数值书写精度取末位半格),但不允许与 `counts`/结构数字相减,杜撰值(如估值 21.4 倍)仍被拒;③`indicatorBlock` 不再输出 `attentionScore`(排序提示不是测量值,输出它只会诱导模型引用内部数字);④真模型 6 指标 × 2 轮共 12 次全部 `mode=llm / violations=0` | 是 |
127
+ | 2026-09-13 | 交付后修复 | 摘要里的统计量与点列表应描述同一窗口 | 真模型在会话里指出:摘要声明 `mean=2616.73 points=250 missing=2577`(2004 年以来的样本)却只列出近一年的 250 个点,于是它**用大半篇幅讨论这个矛盾而不是分析指标** | `buildEntries` 把 `seriesFor` 的"整段抓取序列"统计量直接塞进摘要,而点列表是区间内的;同源问题此前已在 `/series` 上修过一次(见上表"面板与摘要中的数字应自洽"),摘要这条路径未覆盖 | `buildEntries` 末尾按 `result.range` 裁剪发布点并重算该窗口统计量(`displaySeries` 经 `createAiUseCases` 注入,避免 app 模块互相依赖);`missingCount`/`count`/`mean`/`percentile`/`zScore` 因此全部描述面板正在显示的窗口,而 YoY 等回溯仍用抓取到的更长序列 | 是 |
128
+ | 2026-09-13 | 交付后修复 | 网关应收到解析后的区间对象 | 确定性摘要输出 `- 区间:undefined .. undefined`,提示词里的时间范围也丢失 | `activeGateway.explain({ ...request, ...prompt, ... })` 里 `request.range` 是**预设字符串** `'1Y'`,它盖掉了 prompt 中的 `{ from, to }`;确定性网关按 `{ from, to }` 解构,于是拿到 `undefined` | 三处网关调用显式传入 `range: digestInput.range`,并加回归测试断言网关收到的 `range` 必须是含 `from`/`to` 的对象;实测确定性输出恢复为 `2025-09-13 .. 2026-09-13` | 是 |
129
+ | 2026-09-13 | 新功能(用户报告 5.1) | — | 「今日值得关注」每行新增**讨论**按钮:一键开独立会话,注入该指标面板数据 + **触发原因**(哪条规则、什么数值) | 用户希望"值得关注"不只是展示,能直接追问为什么 | `NoteworthyList` 增加 `onDiscuss`/`discussingId`;`POST /discuss` 新增 `group` + `noteworthy` 主体(会话 key = 指标 id,上下文额外带上该条的规则命中);`discussion.discuss()` 主体由 `topicKey` 统一(指标 / 单条关注 / 分组三种主体共用一套会话机制);真模型复验:会话拿到规则与数据后直接给出"为什么值得关注" | 是 |
130
+ | 2026-09-13 | 新功能(用户报告 5.2) | — | 新增**总体分析**页签:可选「全部/美国/中国/全球/我的」,一次综合分析该范围全部指标 + 今日触发规则,并支持「深入讨论」开独立会话 | 原本只有单指标解析,缺少"整体怎么看"的入口 | 新增 `POST /ai/overview`(`ai.overviewAnalysis`):digest 覆盖范围内每个指标**并附规则命中段**(`buildNoteworthyText`),提示词要求输出「总体态势/关键变化/相互印证/背离矛盾/下一个该盯的数据」五段而非逐个复述;缓存按 scope+range;`POST /discuss` 支持 `group`;客户端新增 `AnalysisTab`(结果独立于单指标 AI 状态)。实测:52 指标、2181 字、0 违规、带引用 | 是 |
131
+ | 2026-09-13 | 新功能(用户报告 5.3) | — | **图表重做**:K线/面积/折线/柱状四种视图、值轴时间轴、均值/极值参考线、指针吸附十字线与读数、OHLC 提示框 | 原图是一条 `path` 加 8px 刻度,无交互、无 K 线;而东财与新浪美股**本来就在返回 OHLC**,只是被解析器丢掉了 | ①数据层:`sortDedupe` 保留 `o/h/l`(`v` 恒为主值=收盘),新增 `ohlcOf()`/`hasOhlc()` 一致性校验(high/low 必须包住 open/close,否则退化为普通观测);`types.js` 校验 OHLC;两个 kline 适配器改为携带 bar,并在**数据边界**丢弃不自洽的 bar(真实 fixture 里就有 1 根:low 高于 open,见下条);②图表层:新增 `core/chart/candle.js`(`valueDomain` 用极值定域、`buildCandles`、`nearestIndex` 指针吸附、`buildCrosshair`、`buildReferenceLines`)与 `buildTimeAxis`(跨年自动显示年月、边缘标签内锚);`box()` 支持非对称留白;③组件:重写 `Chart`,四种视图 + 十字线 + 提示框 + 表格,`lib/client.js` 149KB→159KB | 是 |
132
+ | 2026-09-13 | 交付后修复 | 适配器契约应保证发布的数据自洽 | 契约测试发现真实 fixture `sina-us/US_MinKService.INX.jsonp` 里 5712 根 bar 中**有 1 根不自洽**(2023-07-03:low 4442.29 高于 open 4422.44) | 适配器只做数值解析不做一致性检查,于是"不可能存在的 K 线"进入下游;图表会画出低点高于开盘价的蜡烛——即面板发布了假数字 | 新增 `src/sources/ohlc.js`(`coherentBar`/`withBar`),两个 kline 适配器在边界复用:bar 不自洽时**丢掉 bar、保留收盘价**(丢观测比丢精度更糟);`sortDedupe` 作为运行期中途的兜底再拦一次 | 是 |
133
+ | 2026-09-13 | 交付后修复 | 模型输出不应包含其思考过程 | `/ai/overview` 的 markdown 首行是 **"用户要求重写,且上次因未引用数据点未通过。我需要严格按 &lt;data&gt; 输出…"**——模型把自己的推理当成了回答 | `dsh-llm-gateway` 在"预算被思考耗尽"时把 reasoning 文本当作 `markdown` 返回(原本是为了避免降级);但 reasoning 不是回答,它甚至能因为复述了摘要数字而通过溯源校验 | 该分支不再回填 `markdown`(reasoning 仅作为诊断留在 `result.reasoning`),空回答走"重试一次 → 降级确定性摘要",并新增 `emptyReason.kind = 'reasoning-only'` 说明原因;新增回归测试断言 markdown 为空且不含推理内容 | 是 |
134
+ | 2026-09-13 | 交付后修复(用户报告 6.1) | 点「讨论/深入讨论」应有可见反馈 | **按钮点了"没任何响应"**:后端其实全部 200(实测 5 种载荷:卡片/关注项/整体/分组/group:ALL),但 ①`discussError` 只在卡片抽屉的 `AiPanel` 里渲染,关注项与总体分析页签的错误**根本无处显示**;②`wait:false` 返回空串,回答只在新建的会话里出现,面板自己那块一直空着;③`discussing` 按钮只置灰,没有文案变化 | 新增 `DiscussionStatus` 卡片,在**所有能发起讨论的位置**(关注项列表、总体分析页签)显示阶段(正在开启/会话进行中/已就绪)、会话 ID、回答正文、失败原因与「打开会话/收起」;轮询增加"会话已被替换则停止写入"的守卫、超时提示与首次未命中即显示进行中文案;`sessions.open()` 抽出为 `openSession()` | 是 |
135
+ | 2026-09-13 | 交付后修复(用户报告 6.2) | 设置界面应说明能设置什么 | 设置页只有 4 行标签、**没有任何取值**,`state.settings` 从来没人填充(永远是硬编码默认值) | 新增 `GET /settings`(`useCases.settings()`):返回生效配置 + 来源路径 + 运行时事实(前缀/路由数/工具数/指标数/适配器数);设置页重写为三列表格(项目 / 当前值 / 说明),并标注哪些是行配置、面板内不可改,附上要改的 `cordis.patch.yml` 路径;打开页签时同时拉 `/settings` 与 `/health` | 是 |
136
+ | 2026-09-13 | 新功能(用户报告 6.3) | — | 股指支持**日K/周K/月K**;并按数据源能力决定是否显示该控件 | 上游本来就支持:东财 kline 有 `klt=101/102/103`,新浪 A 股有 `scale=240/1200/7200`;但面板固定传日线,且新浪美股**没有**重采样参数(会静默返回同一份日线,看起来像"切了没用") | ①`/series` 新增 `freq`,按适配器声明的 `barSizes` 能力翻译成对应参数(`klt` 或 `scale`;多源级联用中性 `size` 名,由级联自己翻译),并作为源参数进入缓存键;②`seriesView` 返回 `barSizes` 标志,面板只在为真时显示日/周/月;③新浪 A 股解析器补上 OHLC。实测:上证指数 日 726 点 / 周 154 / 月 37,全部带 OHLC;标普 500(新浪美股)如实隐藏该控件 | 是 |
137
+ | 2026-09-13 | 新功能(用户报告 6.4) | — | 新增**中国楼市、人口、进出口**数据:70 城新房/二手房价格同比、出生率、总和生育率、城镇化率、出口、进口、贸易顺差(共 8 个指标,目录 52→60) | 用户要的这几类此前完全没有 | 逐一探测上游后只接入**实测可用**的:东财 `RPT_ECONOMY_HOUSE_PRICE`(新增 `city` 过滤,因为该报表按城市分行)、`RPT_ECONOMY_CUSTOMS`(`EXIT_BASE`/`IMPORT_BASE` 为万美元,已在单位与备注中写明)、世界银行 `SP.DYN.CBRT.IN`/`SP.DYN.TFRT.IN`/`SP.URB.TOTL.IN.ZS`。国家统计局 `data.stats.gov.cn` 本网络 403、东财无房地产投资/人口/结婚报表(实测 12 个 reportName 全部"报表配置不存在"),故**不猜接口**;世界银行响应偶发 12–14s,超出默认 6s 源预算,因此适配器新增 `timeoutMs` 声明(registry 新增 `sourceTimeoutMs`) | 是 |
138
+ | 2026-09-13 | 新功能(用户报告 6.4 后半) | 无法添加时应给出路 | 「我的」页用自然语言添加监控数据时只显示"无法添加/没有可用数据源",**没有下一步** | 面板按设计只读,运行时无法注册数据源——但可以把这个需求交给能改代码的会话 | 新增 `POST /iterate`:在**插件工作区**开一个独立会话(`cwd` 走新增的 `PLUGIN_ROOT`,由模块 URL 推导,`src`/`lib` 都对),上下文里带上用户原话、逐条失败原因,以及本插件真实的扩展契约(适配器文件+注册行+目录项+录 fixture+跑测试)。实测:会话真的跑起来了(`pending:true` → 有输出)。**修复过程中发现**:只有上下文不唤醒 agent(`wakeup=false`),会话会建成但永不开始,因此迭代会话必须带一句开场提问 | 是 |
139
+ | 2026-09-13 | 新功能(用户报告 6.5/6.6) | 面板太乱、要能多选 | 60 张卡片平铺成一堵墙;没有多选统一分析 | ①`groupBySection()` 按目录自己的分组(美国/中国/全球/我的)分节,每节可折叠、带计数,未知分组落到"我的"而不是消失;②卡片左侧新增复选框(勾选态高亮边框),`SelectionBar` 显示已选数量与清单,并提供「分析选中」(复用 `/ai/summary` 携带所选 ids)与「清除选择」;③关注项行按钮文案改为「查看 …」,让点击目标明确 | 是 |
140
+ | 2026-09-13 | 交付后修复(用户报告 6.7) | 选中态应清晰可读 | **选中的页签文字看不清**:`.smd-btn[aria-pressed]` 用 `--dsw-alias-brand-primary` 作背景 + 硬编码 `#fff` 文字,在浅色主题下对比度过低 | 改用 shell 自己的成套 token:`--dsw-alias-button-primary-fill` + `--dsw-alias-label-primary-foreground`(悬停用 `--dsw-alias-button-primary-hover`),徽标同理,芯片选中态改 `--dsw-alias-interactive-bg-active` + 可见描边 | 是 |
141
+ | 2026-09-13 | 交付后修复(用户报告 7.1) | 讨论按钮应能开会话 | **点了完全没反应——这次是真的代码 bug**:`startDiscussion` 里写了 `const question = options.question ?? (question === '' ? … : question.trim())`,局部常量 `question` **遮蔽了同名 state 变量**,于是初始化表达式读的正是"正在声明的那个绑定",每次点击都抛 `ReferenceError: Cannot access 'question' before initialization`——因为它在 `onClick` 里(不在渲染期),React 不会像 props 漏传那样中止渲染,所以界面毫无反应 | 改名 `asked`;**新增"驱动真实按钮"的测试**:渲染 `DataRadarOverlay`(注入 stub api + store),找到关注项行上的「讨论」按钮、真的调用 `onClick()`,断言 store 立刻进入 `discussing`、请求体是 `{group:'ALL', noteworthy:[id]}`、成功后 `discussSession`/`discussPending` 就位、失败时 `discussError` 带原因。这类 bug 只有真的点一下才会暴露——结构性渲染测试永远看不到 | 是 |
142
+ | 2026-09-13 | 新功能(用户报告 7.2) | — | 设置页每个数据源可**测试连接**,并支持整体重新读取 | 用户想确认"这个源现在到底通不通" | `/health` 增加 `ids`(把探测收窄到某个适配器背后的指标)与 `range`,`health.probe` 默认用 **MAX** 区间(1Y 会漏掉只有更早观测的源,那些源会一直显示"未检查");设置页每行加「测试连接」、区块标题加「全部测试」,测试时显示"测试中…",结束后汇报"连接正常(本次测试指标数 N)"或逐个点名失败的源;`api.health()` 支持 `{probe,ids,range}` 与 60s 超时。实测:全部测试 11 个源、12.1s、0 失败;单源测试只探测该适配器的指标 | 是 |
143
+ | 2026-09-13 | 交付后修复 | 路由应容忍缺省 query | 加 `ids` 后 `/health` 在没有 query 对象时 500(`Cannot read properties of undefined (reading 'length')`):`listParam` 在键缺失时返回 **undefined**,而新代码直接读了 `.length` | `handleHealth` 改为 `query?.get?.()` 且对 `undefined` 结果做保护;顺手把 `discussingId` 状态与轮询间隔做成可注入(`pollIntervalMs`/`pollTimeoutMs`),测试因此能跑到轮询结束而不是留下定时器拖住进程 | 是 |
144
+ | 2026-09-13 | 交付后修复(用户报告 8.1) | 点「讨论」应真的能讨论 | **会话建出来了,但里面什么都没有、也没有回答**:用户在 06:34 真的点了两次(`/data/sessions/--workspace--/session-smd-mtzfuxg4-1-*`、`-2-*` 两份日志可查),两次的事件都只有 `agent/inbox/spliced`(上下文被投进 `next-turn` 收件箱)随后 `outcome=canceled`——**从未有一轮 turn 开始**。 | 面板按钮的请求体里 `question` 是 `undefined`(JSON 会丢掉该键),宿主据此走了"只注入上下文、不提问"的分支:`agent.send(context,'next-turn',false)` 只是把消息停在收件箱里等一个 turn,而没有任何东西会启动那个 turn。面板随即轮询 `/discuss/answer`,拿到"已空闲 + 无文本",于是显示"会话已结束但没有文本回答(可能是模型未配置或请求失败)"——把"没人提问"误报成"模型故障"。 | ①`discuss()` 在**没有输入问题时自己提问**(`buildDefaultQuestion` 按主体给开场问题,并把 `question`/`autoQuestion` 回传,面板显示"面板已代你提出开场问题:…");`ask:false` 保留"只播种"这一显式用法;②`/discuss/answer` 新增 `status:'idle'`(只播种、没提问)并把面板文案改成对应说明,不再误报模型故障;③失败仍回传 `sessionId`(`sendError` 支持附加字段),面板显示"会话已创建,但本轮提问失败"并给「打开会话」;④新增 4 个测试:真的驱动关注项行的按钮、断言 store 与请求体、断言 `idle` 文案不含"模型未配置"。**教训**:`undefined` 键在 HTTP 上会消失,"可选参数"因此变成了"另一条业务分支"——分支要么被测试覆盖,要么不该由缺省触发 | 是 |
145
+ | 2026-09-13 | 交付后修复(用户报告 8.2) | 讨论会话应拿到足够上下文 | 真模型在会话里给出**错误结论**:"本会话面板只有 1 个指标(us.cpi.yoy)……面板里也没有核心 CPI、CPI 环比(MoM)"——而 `us.core.cpi.yoy`、`us.cpi` 就在目录里。 | `/discuss` 单指标分支只把该指标自己的 block 塞进 digest(757 字),会话因此认为"面板只有这一个指标",拒绝回答本可回答的问题。 | ①新增 `core/insight/related.js`:按"同分组 + 共同主题词"挑出同族指标(主题词取前导字母段,`dgs10/dgs2/dgs3m`、`m1/m2`、`core.cpi.yoy/cpi.yoy` 都能互认),上限 6 个,进明细;②新增 `buildInventory()`:其余指标一行一个(id/标签/单位/频率/latest/Δ/status),并告知会话"清单里任何 id 都能用 `data_series` 取完整序列"与可用工具;③会话预算 8000→12000 字;④`buildEntries({detail:false})` 让清单**不触发**逐指标序列抓取。实测:上下文 757→3257 字,真模型据此做"整体 − 核心"剪刀差分解并引用 `us.core.cpi.yoy` | 是 |
146
+ | 2026-09-13 | 交付后修复 | 插件开的会话不应是"半残"会话 | 迭代会话(06:15 用户日志)里模型报告:"**可用工具只有 10 个,全部是 `data_*`**……我的工具面里没有文件系统",因此它拒绝执行"改插件代码"这个任务——而那个会话的全部意义就是改代码 | `agents.create()` 创建的 agent **没有加入任何 agent preset**(宿主对这种情况有明确告警:工具/提示词/技能都退化为"空全局层")。根因是插件一直用 `req.agent?.session` 取"请求方会话",而**这个宿主里 web 路由根本不会带上 `agent`**(全库检索无 `req.agent` 赋值点),于是"继承 preset/cwd"从未真正生效 | ①宿主侧改为接收浏览器传来的 `sessionId`:`agents.get(id)` → `meta.cwd` 用该会话的 cwd、`meta.agentPreset` 用它**已验证存在**的 preset id;②`agents.create({setup})` 里用 `agentPresets.composeFrom(agentCtx, requester.ctx)` 加入请求方的组合,没有请求方或加入失败时 `mount()` 默认 preset;组合失败只记日志、绝不让会话创建失败;③客户端 `currentSessionId()` 从 `sessions.list.getSnapshot().current` 取名(无该服务时返回 undefined,不抛错),`/discuss` 与 `/iterate` 都带上;④实测:会话里出现 `skill-catalog` 与 workspace-write 沙箱、工具调用有 `tool/result`;`data_*` 之外的技能/文件能力就位 | 是 |
147
+ | 2026-09-13 | 环境(非插件缺陷,已修复部署) | 会话里调用工具应可用 | 06:43 起本部署**所有会话的工具调用都失败**:`Cannot read properties of undefined (reading 'prepare')`(`dsh-agent-loop` 在 `ctx.tools[TOOL_RUNTIME_SCHEDULER].prepare(...)` 处取到 undefined);同一份代码在 06:37 的沙箱里还是好的 | `@deepseek-ai/dsh-tools` 在部署里出现了**两份**:profile 根(`/data/profiles/web/node_modules/@deepseek-ai/dsh-tools`,v0.1.0-rc.8,由 06:43 装上的 `dsh-ai-brief` 依赖 `^0.1.0-rc.7` 带进来并被 pnpm 提升到根)与 harness 自身(v0.1.0-rc.6)。`TOOL_RUNTIME_SCHEDULER` 是模块**求值时创建**的 `Symbol(...)`,两份模块 = 两个不同 symbol:`tools` 这个行按 profile 根解析到 rc.8(服务实例带 rc.8 的 symbol),而 `dsh-agent-loop` 从 harness 解析到 rc.6 并用 rc.6 的 symbol 去取——必然 undefined。**这不是插件能修的**:本插件刻意不 import `@deepseek-ai/dsh-tools` | 用 A/B 实验定位并验证:沙箱里把 profile 的 `@deepseek-ai/dsh-tools` 指向 harness 副本后,同一请求立刻恢复(`tool/result` 出现、回答 2787 字并引用 `us.core.cpi.yoy`)。部署侧处理:`node_modules/@deepseek-ai/dsh-tools` 原目录保留为 `dsh-tools.rc8-backup/`,同名项改为指向 harness 副本的软链(`readlink -f` 两者相同 → 同一模块实例);`ai-brief` 的启动日志在改动后仍为"plugin loaded"(它只用到 `defineTool`/`register` 这两个稳定 API)。**回滚**:删软链、把 `dsh-tools.rc8-backup` 改回 `dsh-tools`。**注意**:再次 `pnpm install` 可能把 rc.8 装回来并复现该故障;根治要么去掉 `dsh-ai-brief`,要么由上游把该 symbol 改为 `Symbol.for(...)` | 是 |
148
+ | 2026-09-13 | 交付后(发布准备) | 插件能否打包发布 / 推 GitHub | 未验证过:仓库不是 git 仓库、无 `LICENSE`、`private: true`、`name` 是 `@local/...`、`files` 里没有 `cordis.patch.yml`、也没有 `dsh.bundle.patch` —— 即"能跑"与"能发"是两件事,前者早已验证,后者一处没验证过 | 发布要求与运行要求不同:npm 会拒绝 `private` 包与不存在的 scope;`dsh plugin add <包名>` 依赖 `dsh.bundle.patch` 才能自动挂载那一行;而从 git 安装不会跑 `prepublishOnly`,所以 `lib/` 必须入库 | ①新增**打包门禁**(`test/net/boot.test.js`):`npm pack` → 解到 profile 的 `node_modules`(**不是**软链回源码树)→ 真实启动 → 断言 11 源 / 60 指标 / 8 工具 / 前端半被 boot 页面引用;实测 337 KB / 127 文件,`test/`(8.3 MB fixtures)不入包;②`package.json` 补齐发布字段:`license: MIT`、`keywords`、`files` += `cordis.patch.yml`/`LICENSE`、`dsh.bundle.patch`、`publishConfig.access: public`、`prepublishOnly`(重建 lib + 全量测试)、`test:boot` 脚本;③`.gitignore`(排除 `node_modules`/`*.tgz`/一次性备份;**保留 `lib/` 入库**,因为 git 安装不跑构建);④机器相关路径参数化(`SMD_ROOT`/`SMD_PROFILE_SOURCE`/`SMD_DSH_BIN`);⑤新增 `docs/15-publish.md`:还剩的两项决定(包名、LICENSE 署名)、`private` 开关、改名后必须同步的三处、GitHub/npm 具体命令与自检清单;⑥`git init` + `git add`(284 文件)——提交与推送需要本机没有的 git 身份/远端凭据,留给使用者 | 是 |
149
+ | 2026-09-13 | 交付后(发布准备 2) | 包名与署名落定 | 上一轮把包名留给使用者决定,而包名是**四处必须一致**的标识:`package.json#name`、挂载行 `cordis.patch.yml#name`、前端 bundle 的 `CLIENT_ID`(loader 用它注册浏览器半)、以及沙箱/门禁里安装包的目录名 | 只改 `package.json` 会让前端半"孤儿化"(id 与包名不符)并使门禁挂载一个不存在的名字;而**测试与诊断脚本里硬编码包名**意味着每次改名都要全仓库找字符串 | 定为 **`dsh-plugin-show-me-data`**(不带 scope:无需 npm org,`dsh plugin add dsh-plugin-show-me-data` 一句可用;已查 registry 未被占用;与 `dsh-plugin-usage-meter` 命名一致)。改名一次到位:`package.json#name`、`cordis.patch.yml`、`scripts/build-client.mjs#CLIENT_ID`(并重建 `lib/client.js`)、`test/net/boot.test.js` 与 `scripts/diag-discuss.mjs` **改为从 `package.json` 读包名**(含 npm pack 产物名 `<name>-<version>.tgz`,不再硬编码);去掉 `private: true`;新增 `LICENSE`(MIT,署名 hhhcbw + 数据来源说明);**同步更新本机部署**(`/data/profiles/web` 的挂载行、依赖 key、`node_modules/dsh-plugin-show-me-data` 软链),并用 `SMD_KEEP_PATCH=1` **用部署自己的 patch 文件**启动了一份 profile 副本验证:`/discuss` 200、会话创建成功 → 重命名后重启不会掉挂载。顺带修了门禁的一个真实脆弱点:profile 副本里已存在同名软链时 `symlinkSync` 抛 EEXIST(真实部署有这个链),改为先清席位再建链 | 是 |
150
+ | 2026-09-13 | 交付后(发布) | 推到远程仓库 | 本容器 `git push` 无法工作,两条独立原因叠加:①`github.com:443` **不可达**(同时 `api.github.com/zen` 200、`codeload.github.com` 超时);②全局 git 配置 `url.git@github.com:.insteadOf=https://github.com/` 会把 HTTPS 改写成 SSH,而容器里**没有 `ssh`**(`cannot run ssh: No such file or directory`) | 网络只允许 `api.github.com`,而 `git push` 需要 `github.com`;这是环境限制,不是仓库问题 | 用 **Git Data API** 完成推送并封装为 `scripts/push-via-api.mjs`:blobs→tree→commit→ref,**内容取自 git 对象库**(不是工作区)、每个 blob 的 sha 必须与本地一致、**tree sha 必须等于本地 tree sha**(实测 `3991ad35…`)否则中止;空仓库先用 Contents API 提交本仓库自己的 README 作为种子(Git Data API 在空仓库返回 `409`),随后把分支强制指向本地根提交,使 **远端 `main` 的 sha 与本地 HEAD 完全相同**(`d7c9e6f…`,286 文件),种子提交被丢弃;后续推送走 fast-forward(`force:false`,非快进即报错)且幂等(已在远端即退出)。本机同时补上 `refs/remotes/origin/main` 与 upstream,`git status` 直接显示与远端一致 | 是 |