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.
- package/LICENSE +27 -0
- package/README.md +96 -0
- package/cordis.patch.yml +40 -0
- package/docs/01-product-effect.md +178 -0
- package/docs/02-architecture.md +275 -0
- package/docs/03-data-contracts.md +291 -0
- package/docs/04-sources.md +342 -0
- package/docs/05-ui-spec.md +167 -0
- package/docs/06-ai-layer.md +194 -0
- package/docs/07-implementation-plan.md +399 -0
- package/docs/08-test-plan.md +133 -0
- package/docs/09-packaging-install.md +249 -0
- package/docs/10-kickoff-prompt.md +94 -0
- package/docs/11-decisions.md +203 -0
- package/docs/12-runtime-verified.md +115 -0
- package/docs/13-acceptance.md +153 -0
- package/docs/14-progress.md +150 -0
- package/docs/15-publish.md +185 -0
- package/lib/app/ai-deterministic.js +327 -0
- package/lib/app/ai-validate.js +284 -0
- package/lib/app/ai.js +440 -0
- package/lib/app/health.js +77 -0
- package/lib/app/overview.js +349 -0
- package/lib/app/propose-indicator.js +122 -0
- package/lib/app/refresh.js +251 -0
- package/lib/app/series-view.js +195 -0
- package/lib/app/watchlist.js +102 -0
- package/lib/client.js +4322 -0
- package/lib/core/ai/prompts.js +213 -0
- package/lib/core/chart/axis.js +133 -0
- package/lib/core/chart/bar.js +58 -0
- package/lib/core/chart/candle.js +216 -0
- package/lib/core/chart/line.js +186 -0
- package/lib/core/chart/scale.js +132 -0
- package/lib/core/format.js +143 -0
- package/lib/core/indicators/catalog.js +1011 -0
- package/lib/core/indicators/resolve.js +196 -0
- package/lib/core/insight/digest.js +250 -0
- package/lib/core/insight/rank.js +115 -0
- package/lib/core/insight/related.js +90 -0
- package/lib/core/insight/rules.js +417 -0
- package/lib/core/stats/derive.js +123 -0
- package/lib/core/stats/series.js +465 -0
- package/lib/core/time/range.js +242 -0
- package/lib/core/types.js +478 -0
- package/lib/host/ai/discussion.js +559 -0
- package/lib/host/ai/dsh-llm-gateway.js +333 -0
- package/lib/host/config.js +194 -0
- package/lib/host/http/respond.js +165 -0
- package/lib/host/http/routes.js +689 -0
- package/lib/host/index.js +293 -0
- package/lib/host/infra/fs-repos.js +179 -0
- package/lib/host/infra/memory-fallback.js +64 -0
- package/lib/host/tools/define-tool.js +295 -0
- package/lib/host/tools/register.js +431 -0
- package/lib/host.js +7 -0
- package/lib/ports/clock.js +57 -0
- package/lib/ports/snapshot-repo.js +48 -0
- package/lib/sources/eastmoney-macro.js +197 -0
- package/lib/sources/eastmoney-quote.js +201 -0
- package/lib/sources/ecb.js +179 -0
- package/lib/sources/fred.js +207 -0
- package/lib/sources/http.js +136 -0
- package/lib/sources/ohlc.js +36 -0
- package/lib/sources/quote-cascade.js +177 -0
- package/lib/sources/registry.js +153 -0
- package/lib/sources/sina-cn.js +197 -0
- package/lib/sources/sina-us.js +187 -0
- package/lib/sources/tencent.js +158 -0
- package/lib/sources/us-treasury-rates.js +275 -0
- package/lib/sources/us-treasury.js +196 -0
- package/lib/sources/worldbank.js +170 -0
- package/package.json +69 -0
- package/src/app/ai-deterministic.js +327 -0
- package/src/app/ai-validate.js +284 -0
- package/src/app/ai.js +440 -0
- package/src/app/health.js +77 -0
- package/src/app/overview.js +349 -0
- package/src/app/propose-indicator.js +122 -0
- package/src/app/refresh.js +251 -0
- package/src/app/series-view.js +195 -0
- package/src/app/watchlist.js +102 -0
- package/src/client/api.js +323 -0
- package/src/client/components.js +1877 -0
- package/src/client/copy.js +368 -0
- package/src/client/index.js +169 -0
- package/src/client/store.js +219 -0
- package/src/core/ai/prompts.js +213 -0
- package/src/core/chart/axis.js +133 -0
- package/src/core/chart/bar.js +58 -0
- package/src/core/chart/candle.js +216 -0
- package/src/core/chart/line.js +186 -0
- package/src/core/chart/scale.js +132 -0
- package/src/core/format.js +143 -0
- package/src/core/indicators/catalog.js +1011 -0
- package/src/core/indicators/resolve.js +196 -0
- package/src/core/insight/digest.js +250 -0
- package/src/core/insight/rank.js +115 -0
- package/src/core/insight/related.js +90 -0
- package/src/core/insight/rules.js +417 -0
- package/src/core/stats/derive.js +123 -0
- package/src/core/stats/series.js +465 -0
- package/src/core/time/range.js +242 -0
- package/src/core/types.js +478 -0
- package/src/host/ai/discussion.js +559 -0
- package/src/host/ai/dsh-llm-gateway.js +333 -0
- package/src/host/config.js +194 -0
- package/src/host/http/respond.js +165 -0
- package/src/host/http/routes.js +689 -0
- package/src/host/index.js +293 -0
- package/src/host/infra/fs-repos.js +179 -0
- package/src/host/infra/memory-fallback.js +64 -0
- package/src/host/tools/define-tool.js +295 -0
- package/src/host/tools/register.js +431 -0
- package/src/ports/clock.js +57 -0
- package/src/ports/snapshot-repo.js +48 -0
- package/src/sources/eastmoney-macro.js +197 -0
- package/src/sources/eastmoney-quote.js +201 -0
- package/src/sources/ecb.js +179 -0
- package/src/sources/fred.js +207 -0
- package/src/sources/http.js +136 -0
- package/src/sources/ohlc.js +36 -0
- package/src/sources/quote-cascade.js +177 -0
- package/src/sources/registry.js +153 -0
- package/src/sources/sina-cn.js +197 -0
- package/src/sources/sina-us.js +187 -0
- package/src/sources/tencent.js +158 -0
- package/src/sources/us-treasury-rates.js +275 -0
- package/src/sources/us-treasury.js +196 -0
- package/src/sources/worldbank.js +170 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# 09 · 打包、安装、挂载与调试
|
|
2
|
+
|
|
3
|
+
本文的命令与文件格式均基于对当前部署的实测阅读(`dsh-client-ui-jobs`、`dsh-base/cordis.patch.yml`、
|
|
4
|
+
`dsh client-modules` 的说明)。**M10 第一步必须按本文原样执行一次并记录结果。**
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 包结构(最小可安装形态)
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
/root/show_me_data/
|
|
12
|
+
├── package.json # 见 §2
|
|
13
|
+
├── lib/
|
|
14
|
+
│ ├── host.js # 宿主半:Cordis 插件(真正的 ESM,Node 直接跑)
|
|
15
|
+
│ └── client.js # 浏览器半:由 scripts/build-client.mjs 生成
|
|
16
|
+
├── src/ # 源码(见 02 文档 §2)
|
|
17
|
+
├── test/ # 测试与 fixtures
|
|
18
|
+
├── scripts/ # 构建、录制 fixture、冒烟
|
|
19
|
+
└── cordis.patch.yml # 挂载片段(供人工/脚本追加到 profile)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**关键点**:宿主半是**普通 ESM**(不是打包产物),所以可以直接 `node lib/host.js` 之外的常规方式
|
|
23
|
+
被 Loader `import`。浏览器半**必须是** `window.__ModuleLoader__.load({...})` 形态的经典脚本。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. `package.json`
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"name": "dsh-plugin-show-me-data",
|
|
32
|
+
"version": "0.1.0",
|
|
33
|
+
"private": true,
|
|
34
|
+
"type": "module",
|
|
35
|
+
"main": "lib/host.js",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": "./lib/host.js",
|
|
38
|
+
"./client": "./lib/client.js",
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"dsh": {
|
|
42
|
+
"client": {
|
|
43
|
+
"platform": "web",
|
|
44
|
+
"inject": [
|
|
45
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-primitives"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@deepseek-ai/cordis": "*"
|
|
53
|
+
},
|
|
54
|
+
"files": ["lib", "src", "README.md"]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- `dsh.client.platform: "web"` + `exports["./client"]` 是**浏览器半被发现的唯一依据**
|
|
59
|
+
(`client-modules` 扫描已启用 Loader entry 中的 web 客户端包)。
|
|
60
|
+
- `dsh.client.inject` 里的包名**必须在 M0 用 `cordis_inspect` 与运行时加载报错核实**:
|
|
61
|
+
镜像是 `dsh-client-ui-jobs` 的清单,我们要额外包含提供 `slots` 服务的客户端包。
|
|
62
|
+
- peerDependencies 只声明 `@deepseek-ai/cordis`;**不要**把 DSH 的包写进 dependencies
|
|
63
|
+
(否则 pnpm 会去下载一份,导致两套实例)。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. 浏览器半的确切格式(实测得来的模板)
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
// lib/client.js —— 手写或由 scripts/build-client.mjs 生成
|
|
71
|
+
window.__ModuleLoader__.load({
|
|
72
|
+
id: 'dsh-plugin-show-me-data',
|
|
73
|
+
factory: (require) => {
|
|
74
|
+
const module = { exports: {} }
|
|
75
|
+
const exports = module.exports
|
|
76
|
+
const React = require('react')
|
|
77
|
+
|
|
78
|
+
// ── 纯函数区(可被 test/helpers/client-shim.js 单测)──
|
|
79
|
+
function formatValue(v, unit, decimals) { /* … */ }
|
|
80
|
+
function sparkPath(values, w, h) { /* … */ }
|
|
81
|
+
|
|
82
|
+
// ── 组件区(必须用 React.createElement,禁止 JSX)──
|
|
83
|
+
function MetricCard(props) {
|
|
84
|
+
return React.createElement('div', { className: 'smd-card' },
|
|
85
|
+
React.createElement('span', { className: 'smd-label' }, props.label.zh),
|
|
86
|
+
React.createElement('span', { className: 'smd-value' }, formatValue(props.latest, props.unit)))
|
|
87
|
+
}
|
|
88
|
+
function DataRadar(props) { /* … */ }
|
|
89
|
+
|
|
90
|
+
// ── 插件体 ──
|
|
91
|
+
const inject = ['slots'] // 按运行时实际服务名确认
|
|
92
|
+
function apply(ctx) {
|
|
93
|
+
ctx.slots.inject('shell.overlay', () => ctx.slots.register(
|
|
94
|
+
{ name: 'shell.overlay', id: 'show-me-data', order: 30 },
|
|
95
|
+
DataRadar,
|
|
96
|
+
))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exports.apply = apply
|
|
100
|
+
exports.inject = inject
|
|
101
|
+
exports.__test = { formatValue, sparkPath } // 仅测试用,运行时无副作用
|
|
102
|
+
return module.exports
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**必须遵守**(否则会以"UI 加载了但页面报错"的形式失败,且模型很难自查):
|
|
108
|
+
|
|
109
|
+
| 规则 | 说明 |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| 无 JSX / 无 TypeScript / 无 `import` | 不经打包器,`new Function` 直接解析 |
|
|
112
|
+
| 只能 `require` 模块图里存在的模块 | 目前实测可用:`react`、`react/jsx-runtime`、`@deepseek-ai/dsh-client-*` 中被 `dsh.client.inject` 声明的包 |
|
|
113
|
+
| 所有副作用放在 `factory` 内部 | 脚本执行时只注册 factory;CSS 注入等必须发生在 factory 里 |
|
|
114
|
+
| 每个贡献都要能卸载 | 用 `ctx.effect(...)` 或保留 disposer;服务/定时器/样式都要 |
|
|
115
|
+
| 不要碰 `document.body` / 产品 DOM 选择器 | 用 `styles.insert(css)` 与主题 CSS 变量 |
|
|
116
|
+
| 组件状态别放在会被重挂载的组件里 | 面板开合会重挂载卡片;事实放模块级 store |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 4. 宿主半的确切形式
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
// lib/host.js
|
|
124
|
+
export const name = 'show-me-data'
|
|
125
|
+
|
|
126
|
+
// 硬依赖才写进 inject;可选能力用 ctx.get() 并在缺失时降级
|
|
127
|
+
export const inject = ['webServer', 'tools']
|
|
128
|
+
|
|
129
|
+
export function apply(ctx) {
|
|
130
|
+
const disposers = []
|
|
131
|
+
|
|
132
|
+
// 1) HTTP 路由(浏览器半的唯一数据通道)
|
|
133
|
+
const routes = registerRoutes(ctx) // 内部用 ctx.webServer.register(...)
|
|
134
|
+
disposers.push(routes)
|
|
135
|
+
|
|
136
|
+
// 2) 模型工具(对话式入口)
|
|
137
|
+
disposers.push(registerTools(ctx))
|
|
138
|
+
|
|
139
|
+
// 3) 定时刷新(必须 inject: ['timer'] 才能用 ctx.interval)
|
|
140
|
+
// 4) 启动时可选预热
|
|
141
|
+
|
|
142
|
+
return () => { for (const d of disposers.reverse()) d?.() }
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
纪律(来自 DSH 插件开发规范,逐条会在 `test/host/plugin-shape.test.js` 里被断言):
|
|
147
|
+
|
|
148
|
+
- **命名空间导出**(`name`/`inject`/`apply`),**不要 default export**。
|
|
149
|
+
- `ctx.get('x')` 用于可选服务并处理 `undefined`;`ctx.x` 只用于 `inject` 声明的硬依赖。
|
|
150
|
+
- 定时器必须走 `ctx.interval`/`ctx.timeout`(`inject: ['timer']`),禁止全局 `setInterval`。
|
|
151
|
+
- 事件用 `ctx.on`,外部订阅用 `ctx.effect` 并在卸载时释放。
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 5. 挂载片段 `cordis.patch.yml`
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
# 追加到 /data/profiles/web/cordis.patch.yml(该文件已存在,内容是 [] 或若干 patch 条目)
|
|
159
|
+
- insert:
|
|
160
|
+
- id: show-me-data
|
|
161
|
+
name: 'dsh-plugin-show-me-data'
|
|
162
|
+
config:
|
|
163
|
+
refreshMinutes: 30
|
|
164
|
+
ai:
|
|
165
|
+
enabled: true
|
|
166
|
+
mode: auto # auto | llm | deterministic | relay
|
|
167
|
+
groups: [US, CN, GLOBAL, CUSTOM]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
patch 语义(实测自 `dsh-base` 的注释):**一个 patch 会替换目标行的整份 `config`,不是合并**。
|
|
171
|
+
所以这个片段里必须写全我们希望生效的所有配置项。
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 6. 安装步骤
|
|
176
|
+
|
|
177
|
+
`/data/profiles/**` 在会话工作区之外,能否写入取决于当时的文件策略:
|
|
178
|
+
|
|
179
|
+
- 若策略是 **`workspace-write`**(默认姿态):写 profile 会被拒,看到拒绝提示后
|
|
180
|
+
对**同一条命令**就地提权重试一次(用户会看到并批准)。
|
|
181
|
+
- 若策略是**全访问/`danger-full-access`**(本会话当前即为该策略):直接写即可,无需提权。
|
|
182
|
+
- 若**审批提示被禁用**:提权不会成功,必须请用户切换会话的文件策略后再安装,不要绕路。
|
|
183
|
+
|
|
184
|
+
步骤本身不变:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
# ① 安装 pnpm(DSH 的 plugin 子命令依赖它;实测当前 PATH 上没有)
|
|
188
|
+
# 当前 `dsh plugin` 会直接报 “pnpm not found on PATH”
|
|
189
|
+
corepack enable && corepack prepare pnpm@latest --activate
|
|
190
|
+
pnpm -v
|
|
191
|
+
|
|
192
|
+
# ② 把插件装进 web profile(在 profile 目录里跑 pnpm)
|
|
193
|
+
dsh plugin --profile web add /root/show_me_data
|
|
194
|
+
|
|
195
|
+
# ③ 追加挂载行(§5 的内容)到 profile 的用户 patch 层
|
|
196
|
+
# 注意:不要改 /data/profiles/web/cordis.yml(它是空 root,由 bundle 组合)
|
|
197
|
+
|
|
198
|
+
# ④ 重启 web profile 进程(用受管后台作业启动,记录确切 URL,然后核对)
|
|
199
|
+
# ⑤ 刷新浏览器页面,确认面板出现
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**为什么必须重启**:浏览器半由 Node 侧在组装 boot graph 时按已启用 entry 扫描并哈希,
|
|
203
|
+
新装的包在**下一次启动/组装**才进入图。重启后用 `curl`/浏览器确认 HTTP 路由也起来了:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
curl -s http://127.0.0.1:38080/api/show-me-data/health
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
> `dsh plugin --profile web --help` 目前会因为缺 pnpm 而报错,这是**预期**,不是插件问题。
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 7. 回滚
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# 1) 从 cordis.patch.yml 删掉 show-me-data 那一行(或把整个 insert 段清空)
|
|
217
|
+
# 2) 卸载依赖
|
|
218
|
+
dsh plugin --profile web remove dsh-plugin-show-me-data
|
|
219
|
+
# 3) 重启 web profile
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
因为插件被设计成"每个贡献都可卸载",删掉挂载行后**不应留下**路由、工具、定时器或样式残留——
|
|
223
|
+
这一条在 DoD 里(`08` §8 第 8 项)有对应测试。
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 8. 调试手册
|
|
228
|
+
|
|
229
|
+
| 症状 | 先查什么 |
|
|
230
|
+
| --- | --- |
|
|
231
|
+
| 面板完全不出现 | ①挂载行是否在 patch 里且未被 `disabled` ②`pluginInventory/list` 里该 entry 的 phase 是否为 `active` ③浏览器控制台是否有 client-render 报错 ④`__DSH_BOOT__` 的图里有没有该包 |
|
|
232
|
+
| 面板出现但数据全空 | `/api/show-me-data/health` 逐个源的可用性;宿主进程日志 |
|
|
233
|
+
| 路由 404 | `webServer.register` 的 `path`/`kind` 是否与请求路径匹配(`exact` vs `prefix`);是否被别的插件占了同一路径(重复注册会抛) |
|
|
234
|
+
| 工具没出现在模型工具表 | `ctx.tools.register` 的 `output` 声明是否合规;工具是否注册在正确的 scope;`Tool.listTools` 里能否看到 |
|
|
235
|
+
| `fetch` 失败/被拦 | 宿主半用的是全局 `fetch`(Node 24 有);若你误把取数写进了**动态包**,会撞上沙箱的 `fetch` trap——那是设计如此,取数必须在安装的插件宿主半 |
|
|
236
|
+
| AI 不工作 | `ctx.get('llm')` 是否存在;`agentDefaultModel.currentSelection()` 是否返回了 provider/model;设置里 AI 是否开启;降级到 deterministic 是否正常(应该正常) |
|
|
237
|
+
| 改了 `lib/client.js` 页面没变 | 先刷新;仍不变则确认 boot graph 是否被缓存(重启 web profile 一定能生效)。**不要**指望 DSH 仓库的 `dev:web` 监听器来重建我们的包——那个监听器只服务仓库自身的客户端插件 |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 9. 开发期的两条加速通道
|
|
242
|
+
|
|
243
|
+
| 通道 | 做法 | 适用 | 代价 |
|
|
244
|
+
| --- | --- | --- | --- |
|
|
245
|
+
| **动态 Cordis 包做 UI 原型** | 用 `cordis_define` 写浏览器半,数据用浏览器 `fetch` 直连 **CORS 友好**的源(东方财富/World Bank/ECB/财政部实测 `ACAO: *`;**FRED 不行**,`ACAO: null`) | 调面板布局、图表样式、交互手感 | 不能用于取数(沙箱无 `fetch`);不落盘;重启即失 |
|
|
246
|
+
| **HTTP 直测** | 宿主半装好后用 `curl` 打路由 | 后端逻辑联调,无需开浏览器 | 需要先完成一次安装 |
|
|
247
|
+
|
|
248
|
+
原型期用假数据(`test/fixtures` 里的回放数据)即可,**不要在原型里写真的取数逻辑**——
|
|
249
|
+
那部分只能在安装的插件宿主半里存在。
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# 10 · 创造模式启动提示词
|
|
2
|
+
|
|
3
|
+
把下面整段(`===` 之间)粘贴到一个**预设为 `cordis`(创造模式)**、工作目录为
|
|
4
|
+
`/root/show_me_data` 的新会话里。之后按 `07` 文档的里程碑顺序推进即可。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
===
|
|
10
|
+
任务:在 DeepSeek Harness 上实现插件 `@local/dsh-plugin-show-me-data`(全球数据指标雷达)。
|
|
11
|
+
工作目录:/root/show_me_data(这里已经有一整套设计文档,先读,不要重新设计)。
|
|
12
|
+
|
|
13
|
+
【第一步:读文档,不要跳】
|
|
14
|
+
按顺序读并遵守:
|
|
15
|
+
README.md
|
|
16
|
+
docs/02-architecture.md ← 分层、端口契约、运行时事实、扩展点
|
|
17
|
+
docs/03-data-contracts.md ← 数据结构与种子指标目录(含实测可用的 seriesRef)
|
|
18
|
+
docs/04-sources.md ← 已实测的数据源与陷阱
|
|
19
|
+
docs/05-ui-spec.md ← UI 与图表规格
|
|
20
|
+
docs/06-ai-layer.md ← AI 端口、提示词、溯源校验
|
|
21
|
+
docs/07-implementation-plan.md ← 里程碑与 TDD 卡片(你的施工图)
|
|
22
|
+
docs/08-test-plan.md ← 测试门禁
|
|
23
|
+
docs/09-packaging-install.md ← 打包与安装
|
|
24
|
+
读完先复述你对「分层边界」和「第一批要写的测试」的理解,再动手。
|
|
25
|
+
|
|
26
|
+
【第二步:M0 运行时校准(不做完不许写业务代码)】
|
|
27
|
+
用 cordis_inspect / cordis_inspect_query 现场核对 docs/02-architecture.md §6 列出的 7 项假设,
|
|
28
|
+
把结果写进 docs/12-runtime-verified.md(表格:假设 / 实测 / 结论 / 需要修改哪些文档与测试)。
|
|
29
|
+
若假设与实测冲突:以**运行时实测**为准,并同步修正对应文档与测试计划。
|
|
30
|
+
|
|
31
|
+
【执行方式:TDD,严格红-绿-重构】
|
|
32
|
+
- 测试用 node:test + node:assert/strict,零外部依赖;绝不引入 jest/vitest/tsdown。
|
|
33
|
+
- 每个任务卡:先写测试 → 跑 node --test 确认**因缺少实现而失败** → 写最小实现 → 重构 → 提交。
|
|
34
|
+
- 测试默认不打网:网络一律走 test/fixtures 回放;真网冒烟只放在 test/net/ 并用 RUN_NET=1 门控。
|
|
35
|
+
- 每个 TDD 循环结束跑一次:node --test test/,必须全绿才进入下一个任务卡。
|
|
36
|
+
- 每完成一个里程碑,更新 docs/14-progress.md(勾选 + 记录与计划的偏差)。
|
|
37
|
+
|
|
38
|
+
【硬性禁令(违反即返工)】
|
|
39
|
+
1. 不要在动态 Cordis 包(cordis_define/cordis_run)里实现取数:动态宿主半跑在 vm 沙箱里,
|
|
40
|
+
`fetch` 是被封装的 trap,调用即抛错;本部署也没有挂载 fetch provider。
|
|
41
|
+
动态包只允许用于 UI 外观原型(且只能用浏览器 fetch + CORS 友好的源)。
|
|
42
|
+
2. 不要依赖 ctx.web.fetch(本部署 tool-web 配置 fetch:false,没有 fetch provider)。
|
|
43
|
+
3. 不要猜任何上游接口、字段名或 seriesRef。docs/04-sources.md 里标注「未验证 / 待 fixture 确认」的,
|
|
44
|
+
一律先用真实请求 + 录制 fixture 确认;确认不了就把该指标标为 unsupported 并写明调查结论。
|
|
45
|
+
tests 里如果出现"猜的 seriesRef",直接删掉重做。
|
|
46
|
+
4. src/core/** 与 src/app/** 里禁止出现 fetch / fs / Date.now / new Date / process / @deepseek-ai 导入。
|
|
47
|
+
时间与网络一律从端口注入(Clock / SourceAdapter)。
|
|
48
|
+
5. 浏览器半(src/client/**、lib/client.js)禁止 JSX、TypeScript、import/export、以及直连外部数据源域名;
|
|
49
|
+
取数只能经 /api/show-me-data/* 路由。
|
|
50
|
+
6. 安装要写 /data/profiles/**(会话工作区之外)。若被文件策略拒绝:对**同一条命令**就地提权重试一次
|
|
51
|
+
(若审批提示已被禁用,则停下来让我切换会话的文件策略,不要绕路);
|
|
52
|
+
绝不要改 /data/profiles/web/cordis.yml(它是空 root),要改的是 cordis.patch.yml。
|
|
53
|
+
7. 不要为了让测试变绿而在 core 里加 IO 或加特例分支。
|
|
54
|
+
|
|
55
|
+
【交付物】
|
|
56
|
+
A. 可运行的插件包:lib/host.js(Cordis 插件,命名空间导出 name/inject/apply)
|
|
57
|
+
与 lib/client.js(window.__ModuleLoader__.load 形态的浏览器半)。
|
|
58
|
+
B. 完整测试:test/{core,contract,sources,app,host,net}/,node --test test/ 全绿。
|
|
59
|
+
C. 脚本:scripts/build-client.mjs、scripts/record-fixtures.mjs、scripts/probe-sources.mjs、scripts/smoke.mjs。
|
|
60
|
+
D. 记录文件:docs/12-runtime-verified.md、docs/13-acceptance.md、docs/14-progress.md。
|
|
61
|
+
E. 安装与验收:按 docs/09-packaging-install.md 装进 web profile(含必要的一次提权),
|
|
62
|
+
重启后用受管后台作业启动并记录确切 URL,刷新页面确认面板可用;
|
|
63
|
+
逐条过 docs/01-product-effect.md §3 的 15 条交互清单并记录结果。
|
|
64
|
+
|
|
65
|
+
【完成定义(DoD)】
|
|
66
|
+
1. node --test test/ 全绿;覆盖率:core ≥95%、app ≥90%、sources ≥85%。
|
|
67
|
+
2. 分层静态断言测试通过(core/app 无 IO 与 DSH 依赖)。
|
|
68
|
+
3. GUI 里 15 条交互全过,写进 docs/13-acceptance.md。
|
|
69
|
+
4. 关掉/不配置 LLM 时,AI 解析、时段总结、数据问答三个入口仍可用,且标注「确定性摘要模式」。
|
|
70
|
+
5. 给定一个故意编造 seriesRef 的假 LLM 适配器,propose 必须落到 unsupported,且校验器拦下伪造引用(有测试)。
|
|
71
|
+
6. 扩展演练(docs/07 §11):加一个指标(只改目录 + 加 fixture)与加一个数据源(只加 1 个文件 + 1 行注册 + fixture),
|
|
72
|
+
各在 30 分钟内完成,且 git diff 证明未改 core 逻辑文件。
|
|
73
|
+
7. 卸载挂载行后无残留(路由/工具/定时器/样式)。
|
|
74
|
+
|
|
75
|
+
【推进节奏】
|
|
76
|
+
按 M0 → M1 → M2 → M3 → M4 → M5 → M6 → M7 → M8 → M9 → M10 顺序做;
|
|
77
|
+
M3/M4/M5 之间若需要并行,可用 subagent 并行,但 M4 必须等 M3 的 fixtures 就绪。
|
|
78
|
+
每个里程碑结束向我汇报:做了什么、测试数、覆盖率、与计划的偏差、下一个里程碑的第一张任务卡。
|
|
79
|
+
不要一次做完十个里程碑再汇报。
|
|
80
|
+
===
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 使用说明
|
|
86
|
+
|
|
87
|
+
- **一次只让它推进一两个里程碑**,每轮结束看它的汇报再决定继续。M0 校准的偏差往往会影响后续设计,
|
|
88
|
+
早点发现成本最低。
|
|
89
|
+
- 如果它想跳过 M0 直接写代码,**打回**——`02` §6 的 7 项里任何一项与假设不符(尤其是浮层槽位协议
|
|
90
|
+
与 `webServer` 签名),都会导致后面 M8/M10 大返工。
|
|
91
|
+
- 如果它报告某个数据源不可用(例如中国 10Y 国债收益率找不到源),这**不是失败**:
|
|
92
|
+
按 `04`/`03` 的约定标 `unsupported` 并写明结论即可,这正是"不猜接口"纪律的体现。
|
|
93
|
+
- 装插件需要一次性提权写入 `/data/profiles/**`,请留意批准提示。它会重启 web profile 进程;
|
|
94
|
+
如果你正在使用当前 GUI,确认它给出的新 URL 与你的访问地址一致。
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# 11 · 决策记录(ADR)
|
|
2
|
+
|
|
3
|
+
每条记录:背景 → 决策 → 理由 → 被否方案 → 后果。**实施中若需偏离,在本文件追加一条 ADR,不要默默改。**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ADR-001 · 产品形态用「真正安装的插件包」,不用动态 Cordis 包
|
|
8
|
+
|
|
9
|
+
**背景**:创造模式提供 `cordis_define`/`cordis_run`,能立刻在 GUI 里看到 UI。
|
|
10
|
+
|
|
11
|
+
**决策**:产品交付物是一个 npm 包(宿主半 + 浏览器半),通过 profile 的 `insert` 行挂载。
|
|
12
|
+
|
|
13
|
+
**理由**:① 动态包只活在进程内存,不落盘、重启即失、无法自动晋升(`dsh-tool-cordis` 明确说明);
|
|
14
|
+
② 动态宿主半在 `node:vm` 沙箱里,`fetch` 是抛错 trap,**无法取数**;
|
|
15
|
+
③ 我们需要持久化(关注清单、快照缓存)、定时刷新、模型工具,这些都属于长期能力。
|
|
16
|
+
|
|
17
|
+
**被否**:纯动态包实现(看起来最快,但连第一步取数都做不到);纯浏览器半直连数据源
|
|
18
|
+
(FRED 的 `ACAO: null`,CORS 直接堵死)。
|
|
19
|
+
|
|
20
|
+
**后果**:每次改宿主半都需要重启 web profile;因此把 UI 迭代尽量放在浏览器半(改 `lib/client.js` 后刷新)。
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## ADR-002 · 取数在宿主半;主干源选 FRED CSV
|
|
25
|
+
|
|
26
|
+
**背景**:需要美国 CPI/非农/失业率/政策利率/国债收益率等核心指标。
|
|
27
|
+
|
|
28
|
+
**决策**:宿主半用 Node 24 内置 `fetch`;美国宏观与利率走
|
|
29
|
+
`https://fred.stlouisfed.org/graph/fredgraph.csv?id=<SERIES>&cosd=<date>`。
|
|
30
|
+
|
|
31
|
+
**理由**:实测 200 且**无需 API key**;覆盖我们需要的绝大多数美国指标;
|
|
32
|
+
CSV 解析极简(缺失值 `.` 语义明确);`api.stlouisfed.org` 的 JSON API 需要 key,故不用。
|
|
33
|
+
|
|
34
|
+
**被否**:BLS 公开 API(实测 403);Yahoo Finance(实测 403,需要 crumb);
|
|
35
|
+
Stooq(反爬 JS 墙);新浪 `hq.sinajs.cn`(403,需特定 Referer)。
|
|
36
|
+
|
|
37
|
+
**后果**:美国数据的时效以 FRED 的更新节奏为准(通常滞后官方发布数小时到一天),
|
|
38
|
+
面板必须显示"最后观测日 + 抓取时间",不能暗示实时性。
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## ADR-003 · 浏览器半 ↔ 宿主半用 `webServer` HTTP 路由,不用 Typert Remote
|
|
43
|
+
|
|
44
|
+
**背景**:持久插件的 C→H 通信有两种正式方式:Typert 生成的 Remote,或 `ctx.webServer.register`。
|
|
45
|
+
|
|
46
|
+
**决策**:用 HTTP 路由(`/api/show-me-data/*`)。
|
|
47
|
+
|
|
48
|
+
**理由**:Typert 需要 codegen 链路(`typert-loader`/`typert-registry` 与生成物),
|
|
49
|
+
对一个自包含插件是沉重的外部依赖;HTTP 路由是标准服务、签名简单、`curl` 可直接调试、
|
|
50
|
+
流式(SSE)天然支持——正好满足 AI 流式输出。
|
|
51
|
+
|
|
52
|
+
**被否**:Typert Remote(作为后续可选优化保留);`harness.handle`(那是动态包的专用机制,
|
|
53
|
+
只适用于动态 Cordis 包,不适用于安装的插件)。
|
|
54
|
+
|
|
55
|
+
**后果**:需要自己处理路由参数校验、错误结构与 CORS/同源(同源,无需 CORS 头)。
|
|
56
|
+
若将来要做正式的进程间 API,再考虑迁 Typert。
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## ADR-004 · 图表手写 SVG,不引图表库
|
|
61
|
+
|
|
62
|
+
**背景**:浏览器半的模块图里没有图表库,且它的 `require` 只能解析已注册模块。
|
|
63
|
+
|
|
64
|
+
**决策**:所有图像用纯函数生成 SVG 路径/图元,自绘坐标轴、网格、tooltip。
|
|
65
|
+
|
|
66
|
+
**理由**:① 技术上无法 `require('echarts')`;② **反过来是巨大优势**:
|
|
67
|
+
几何计算变成纯函数,可以 100% 单测(空数组、单点、全等值、含 NaN、万点抽稀都能进 CI),
|
|
68
|
+
而用图表库时这些只能靠肉眼;③ 体积为零,加载快。
|
|
69
|
+
|
|
70
|
+
**被否**:把图表库打进 client bundle(需要引入打包器,与 ADR-005 冲突);
|
|
71
|
+
用 canvas 绘制(不可测、不可导出、无 a11y)。
|
|
72
|
+
|
|
73
|
+
**后果**:需要自己写约 6 个几何函数与 4 种图型;视觉精致度预计不如成熟图表库,
|
|
74
|
+
用"少而清晰"的样式策略弥补(见 `05` §4)。
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ADR-005 · 测试用 `node:test`,零外部依赖
|
|
79
|
+
|
|
80
|
+
**背景**:当前 profile 里没有 vitest/tsdown;npm registry 可达但每加一个依赖都是一个故障点。
|
|
81
|
+
|
|
82
|
+
**决策**:`node:test` + `node:assert/strict`;覆盖率用 `--experimental-test-coverage`。
|
|
83
|
+
|
|
84
|
+
**理由**:Node 24 内置能力已覆盖我们需要的全部(describe/it、并发、超时、mock、覆盖率);
|
|
85
|
+
零安装意味着**任何时候 `node --test test/` 都能跑**,不依赖网络与包管理器状态。
|
|
86
|
+
|
|
87
|
+
**被否**:vitest(需安装 + 配置 + 与 ESM/hand-written client 的兼容成本)。
|
|
88
|
+
|
|
89
|
+
**后果**:没有快照测试与 jsdom;因此"组件渲染"用**假 React + 结构断言**代替
|
|
90
|
+
(见 `08` §2),真正的视觉验收放在 M8/M10 人工完成。
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## ADR-006 · AI 走端口 + 确定性降级 + 输出溯源校验
|
|
95
|
+
|
|
96
|
+
**背景**:AI 解释/总结/问答是核心卖点,但 LLM 可能不可用、可能编造。
|
|
97
|
+
|
|
98
|
+
**决策**:`AiGateway` 端口 + 三个适配器(`ctx.llm` / 会话中继 / **确定性**);
|
|
99
|
+
所有输出过 `ai-validate`(引用校验 + 数字校验),不通过则重试一次、仍不过就降级为确定性摘要。
|
|
100
|
+
|
|
101
|
+
**理由**:① AI 不可用时面板必须仍可用(否则一个网络配置问题就让核心功能全灭);
|
|
102
|
+
② 金融数据场景下"看起来很像的编造"比"没有 AI"危害大得多;
|
|
103
|
+
③ 端口化让全部 AI 行为都能用假适配器单测——包括"故意作恶的适配器"。
|
|
104
|
+
|
|
105
|
+
**被否**:直接调 LLM 不做校验(不可测、有幻觉风险);只用 AI 不做规则引擎(见 ADR-007)。
|
|
106
|
+
|
|
107
|
+
**后果**:需要维护 `08` §7 的黄金文件;提示词改动会触发 digest 黄金文件更新,属于预期成本。
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## ADR-007 · 「值得关注」由规则引擎决定,AI 只做排序与措辞
|
|
112
|
+
|
|
113
|
+
**背景**:用户要"AI 觉得值得关注的数据",但排序结果需要可复现、可解释、可测。
|
|
114
|
+
|
|
115
|
+
**决策**:`core/insight/rules.js` 8 条确定性规则产出 `Hit[]`(含 `reason` 与 `evidence`),
|
|
116
|
+
`rank.js` 给出评分与顺序;AI 只负责把这些理由组织成自然语言,以及在 LLM 可用时做二次归并。
|
|
117
|
+
|
|
118
|
+
**理由**:① 可单测、可回归(黄金文件);② 无 AI 时首页依然可用且有解释;
|
|
119
|
+
③ 每条命中都能显示"为什么",这是产品可信度的来源;④ 避免"模型今天心情不同,首页排序就变了"。
|
|
120
|
+
|
|
121
|
+
**被否**:纯 AI 排序(不可复现、无 LLM 时首页没有任何内容)。
|
|
122
|
+
|
|
123
|
+
**后果**:规则的权重表成为需要维护的配置;调整权重必须显式更新黄金文件(评审可见)。
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## ADR-008 · 持久化用自建 JSON 文件仓储,不用 `storageDomain`
|
|
128
|
+
|
|
129
|
+
**背景**:需要持久化关注清单、自定义指标与快照缓存。DSH 提供 `ctx.storage` / `ctx.storageDomain`。
|
|
130
|
+
|
|
131
|
+
**决策**:`WatchlistRepository` / `SnapshotRepository` 端口 + 文件 JSON 适配器(内存适配器用于测试)。
|
|
132
|
+
|
|
133
|
+
**理由**:① 端口化后同一套契约测试可同时跑内存版与文件版,测试无需 DSH 运行时;
|
|
134
|
+
② 数据形态简单(几十条配置 + 快照),domain KV 的 schema/zod 与路由表是过度设计;
|
|
135
|
+
③ 卸载插件时数据留在自己的目录,行为可预期、易备份。
|
|
136
|
+
|
|
137
|
+
**被否**:`storageDomain`(作为可选适配器保留,将来需要跨插件共享状态时再启用)。
|
|
138
|
+
|
|
139
|
+
**后果**:需要自己处理并发写(串行化队列)与损坏文件恢复(解析失败回落默认值 + 备份坏文件),
|
|
140
|
+
这两点都进了测试计划。
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## ADR-009 · 浏览器半自包含 + 手写加载器格式 + 可测构建脚本
|
|
145
|
+
|
|
146
|
+
**背景**:一个包在客户端模块图里只有一个节点,浏览器半**无法** `require` 本包的宿主代码。
|
|
147
|
+
|
|
148
|
+
**决策**:浏览器半是自包含源码;`scripts/build-client.mjs` 把它包成
|
|
149
|
+
`window.__ModuleLoader__.load({ id, factory })`;测试用 `client-shim` 执行 factory
|
|
150
|
+
从而单测浏览器半的纯函数与组件结构。
|
|
151
|
+
|
|
152
|
+
**理由**:① 满足平台约束;② 构建脚本本身可单测(产物能被 `new Function` 解析、无残留 import);
|
|
153
|
+
③ 不需要真正的打包器,符合 ADR-005。
|
|
154
|
+
|
|
155
|
+
**被否**:引入 esbuild/tsdown 做 bundle(多一个工具链与构建产物同步问题)。
|
|
156
|
+
|
|
157
|
+
**后果**:不在宿主半与浏览器半之间共享代码,共享的纯函数需在两侧各有一份或经 HTTP 传递
|
|
158
|
+
(当前设计里:图表几何在浏览器半(`core/chart` 的纯函数在两侧都可用同一份源码,
|
|
159
|
+
因为它是纯 ESM 且不依赖 DSH——构建脚本会把 `core/chart/**` 一并拼进 client 产物)。
|
|
160
|
+
**这一点是 M0 需要确认的实现细节**:确认拼接顺序与命名冲突处理。)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## ADR-010 · 时间处理:注入 Clock,不做交易日历
|
|
165
|
+
|
|
166
|
+
**背景**:时间范围、同比对齐、陈旧判断都涉及日期;交易日历是常见的时间黑洞。
|
|
167
|
+
|
|
168
|
+
**决策**:所有"现在"来自注入的 `Clock`;"最近 N 个交易日"= 上游实际返回的最后 N 个观测;
|
|
169
|
+
陈旧阈值按频率表(日 2 交易日按自然日近似 4 天 / 周 10 / 月 40 / 季 100 / 年 400)。
|
|
170
|
+
|
|
171
|
+
**理由**:① 固定时钟才能测跨年、闰年、月末边界;② 自建交易日历会引入维护成本与错误;
|
|
172
|
+
③ 用上游实际观测数最诚实——数据源给什么就是什么。
|
|
173
|
+
|
|
174
|
+
**被否**:引入 holiday 日历库;用自然日硬推交易日。
|
|
175
|
+
|
|
176
|
+
**后果**:极少数情况下"最近 N 个交易日"会包含非交易日差异,但不影响任何统计语义(因为统计基于实际观测)。
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## ADR-011 · 面板状态放模块级 store,不放组件 state
|
|
181
|
+
|
|
182
|
+
**背景**:浮层面板开合会重挂载子组件(DSH 的卡片/浮层生命周期如此)。
|
|
183
|
+
|
|
184
|
+
**决策**:模块级 `createStore()`(订阅式、自带单测),组件订阅它。
|
|
185
|
+
|
|
186
|
+
**理由**:DSH 自己的客户端插件明确说明"不要把运行状态放在会被重挂载的组件 state 里",
|
|
187
|
+
否则一次开合就丢失数据与滚动位置,并触发重复取数。
|
|
188
|
+
|
|
189
|
+
**被否**:用组件 state + 提升到根组件(根组件本身也会被重挂载)。
|
|
190
|
+
|
|
191
|
+
**后果**:需要自己写约 40 行 store(含订阅/取消订阅),并测"订阅后卸载不留监听器"。
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## ADR-012 · 首次发布不做的能力(有意排除,避免范围蔓延)
|
|
196
|
+
|
|
197
|
+
| 不做 | 原因 | 将来如何加 |
|
|
198
|
+
| --- | --- | --- |
|
|
199
|
+
| 新闻聚合与事件解读 | 需要另一类适配器与去重/时间线语义,属于独立特性 | 作为 `SourceAdapter` 的兄弟类型 `NewsAdapter`,新增端口而非改造现有 |
|
|
200
|
+
| 预测/点位输出 | 与"只用已有数据、不做预测"的 AI 纪律冲突 | 需要独立的产品决策与评估方法 |
|
|
201
|
+
| 付费数据源(Wind/iFinD/Bloomberg) | 需要凭证与授权 | 走同一 `SourceAdapter` 契约,凭证从 `ctx.credentials` 取 |
|
|
202
|
+
| 多用户与权限 | 本地单进程使用 | 与 DSH 的 scope 体系结合,属于后续架构话题 |
|
|
203
|
+
| 分钟级实时行情 | 上游(腾讯/东财)稳定性与编码问题多,收益低 | 作为 `freq:'intraday'` 的独立适配器,且必须在 UI 上明确区分时效 |
|