dsh-archify-projects 0.0.0-stage → 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 (148) hide show
  1. package/README.md +204 -2
  2. package/cordis.patch.yml +99 -0
  3. package/dist/client.js +2261 -0
  4. package/dist/client.js.map +1 -0
  5. package/docs/CONTAINER.md +197 -0
  6. package/docs/DESIGN.md +905 -0
  7. package/docs/HANDOVER.md +391 -0
  8. package/docs/PUBLISH.md +107 -0
  9. package/icon.svg +12 -0
  10. package/lib/host/archify-skill.js +116 -0
  11. package/lib/host/archify-skill.js.map +1 -0
  12. package/lib/host/config.js +95 -0
  13. package/lib/host/config.js.map +1 -0
  14. package/lib/host/context.js +28 -0
  15. package/lib/host/context.js.map +1 -0
  16. package/lib/host/git.js +185 -0
  17. package/lib/host/git.js.map +1 -0
  18. package/lib/host/http.js +184 -0
  19. package/lib/host/http.js.map +1 -0
  20. package/lib/host/index.js +90 -0
  21. package/lib/host/index.js.map +1 -0
  22. package/lib/host/policy.js +124 -0
  23. package/lib/host/policy.js.map +1 -0
  24. package/lib/host/routes.js +667 -0
  25. package/lib/host/routes.js.map +1 -0
  26. package/lib/host/runner.js +913 -0
  27. package/lib/host/runner.js.map +1 -0
  28. package/lib/host/store.js +370 -0
  29. package/lib/host/store.js.map +1 -0
  30. package/lib/host/workspace.js +65 -0
  31. package/lib/host/workspace.js.map +1 -0
  32. package/lib/shared/format.js +138 -0
  33. package/lib/shared/format.js.map +1 -0
  34. package/lib/shared/prompt.js +257 -0
  35. package/lib/shared/prompt.js.map +1 -0
  36. package/lib/shared/protocol.js +31 -0
  37. package/lib/shared/protocol.js.map +1 -0
  38. package/lib/shared/types.js +173 -0
  39. package/lib/shared/types.js.map +1 -0
  40. package/locale/en.json +6 -0
  41. package/locale/zh.json +6 -0
  42. package/package.json +98 -4
  43. package/skills/archify/LICENSE +22 -0
  44. package/skills/archify/SKILL.md +109 -0
  45. package/skills/archify/THIRD_PARTY_NOTICES.md +69 -0
  46. package/skills/archify/assets/JetBrainsMono-OFL.txt +93 -0
  47. package/skills/archify/assets/template.html +13709 -0
  48. package/skills/archify/bin/archify.mjs +6936 -0
  49. package/skills/archify/bin/delivery-update.mjs +91 -0
  50. package/skills/archify/bin/finalize.mjs +895 -0
  51. package/skills/archify/bin/open-artifact.mjs +146 -0
  52. package/skills/archify/bin/preview.mjs +969 -0
  53. package/skills/archify/bin/recover-output.mjs +41 -0
  54. package/skills/archify/bin/visual-check.mjs +2667 -0
  55. package/skills/archify/brand-marks/README.md +31 -0
  56. package/skills/archify/brand-marks/catalog.json +131 -0
  57. package/skills/archify/delta/architecture-delta.mjs +1313 -0
  58. package/skills/archify/examples/agent-run.lifecycle.json +55 -0
  59. package/skills/archify/examples/agent-tool-call.workflow.json +74 -0
  60. package/skills/archify/examples/async-job-roundtrip.sequence.json +56 -0
  61. package/skills/archify/examples/brand-aware-delivery.architecture.json +34 -0
  62. package/skills/archify/examples/cache-miss-request.sequence.json +77 -0
  63. package/skills/archify/examples/checkout-platform.base.architecture.json +32 -0
  64. package/skills/archify/examples/checkout-platform.head.architecture.json +32 -0
  65. package/skills/archify/examples/dataflow-product-analytics.html +13820 -0
  66. package/skills/archify/examples/deployment-release.lifecycle.json +48 -0
  67. package/skills/archify/examples/event-stream.dataflow.json +52 -0
  68. package/skills/archify/examples/holiday-planning.workflow.json +143 -0
  69. package/skills/archify/examples/incident-response.workflow.json +59 -0
  70. package/skills/archify/examples/lifecycle-agent-run.html +13765 -0
  71. package/skills/archify/examples/locales/de.json +425 -0
  72. package/skills/archify/examples/locales/es.json +440 -0
  73. package/skills/archify/examples/locales/fr.json +425 -0
  74. package/skills/archify/examples/locales/fr.partial.json +22 -0
  75. package/skills/archify/examples/locales/it.json +425 -0
  76. package/skills/archify/examples/locales/ja.json +425 -0
  77. package/skills/archify/examples/locales/ko.json +440 -0
  78. package/skills/archify/examples/locales/pt.json +425 -0
  79. package/skills/archify/examples/locales/ru.json +425 -0
  80. package/skills/archify/examples/product-analytics.dataflow.json +71 -0
  81. package/skills/archify/examples/production-deployment.architecture.json +51 -0
  82. package/skills/archify/examples/release-delivery.workflow.json +57 -0
  83. package/skills/archify/examples/sequence-cache-miss-request.html +13835 -0
  84. package/skills/archify/examples/web-app-rendered.html +13808 -0
  85. package/skills/archify/examples/web-app.architecture.json +41 -0
  86. package/skills/archify/examples/workflow-agent-tool-call-rendered.html +13826 -0
  87. package/skills/archify/migrations/workflow-v2.mjs +280 -0
  88. package/skills/archify/package.json +17 -0
  89. package/skills/archify/recipes/scenarios.mjs +430 -0
  90. package/skills/archify/references/architecture-layout-repair.md +18 -0
  91. package/skills/archify/references/authoring-contract.md +419 -0
  92. package/skills/archify/references/authoring-defaults.md +41 -0
  93. package/skills/archify/references/brand-marks.md +82 -0
  94. package/skills/archify/references/delivery-contract.md +588 -0
  95. package/skills/archify/references/repository-authoring.md +105 -0
  96. package/skills/archify/references/update-awareness.md +12 -0
  97. package/skills/archify/references/viewer-runtime.md +35 -0
  98. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  99. package/skills/archify/renderers/architecture/labels.mjs +215 -0
  100. package/skills/archify/renderers/architecture/render-architecture.mjs +1001 -0
  101. package/skills/archify/renderers/architecture/routing.mjs +1157 -0
  102. package/skills/archify/renderers/dataflow/README.md +105 -0
  103. package/skills/archify/renderers/dataflow/render-dataflow.mjs +544 -0
  104. package/skills/archify/renderers/lifecycle/README.md +171 -0
  105. package/skills/archify/renderers/lifecycle/grid-routing.mjs +429 -0
  106. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +938 -0
  107. package/skills/archify/renderers/sequence/README.md +129 -0
  108. package/skills/archify/renderers/sequence/render-sequence.mjs +526 -0
  109. package/skills/archify/renderers/shared/atomic-output.mjs +1982 -0
  110. package/skills/archify/renderers/shared/brand-marks.mjs +670 -0
  111. package/skills/archify/renderers/shared/cli.mjs +451 -0
  112. package/skills/archify/renderers/shared/desktop-readability.mjs +132 -0
  113. package/skills/archify/renderers/shared/diagnostics.mjs +173 -0
  114. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  115. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  116. package/skills/archify/renderers/shared/generated-validators.mjs +134 -0
  117. package/skills/archify/renderers/shared/geometry.mjs +1941 -0
  118. package/skills/archify/renderers/shared/i18n.mjs +603 -0
  119. package/skills/archify/renderers/shared/layout-report.mjs +42 -0
  120. package/skills/archify/renderers/shared/legend.mjs +217 -0
  121. package/skills/archify/renderers/shared/output-path.mjs +440 -0
  122. package/skills/archify/renderers/shared/path-semantics.mjs +1017 -0
  123. package/skills/archify/renderers/shared/portable-path.mjs +408 -0
  124. package/skills/archify/renderers/shared/repository-evidence.mjs +352 -0
  125. package/skills/archify/renderers/shared/repository-location.mjs +59 -0
  126. package/skills/archify/renderers/shared/route-quality.mjs +614 -0
  127. package/skills/archify/renderers/shared/sidecar-path.mjs +38 -0
  128. package/skills/archify/renderers/shared/spatial-grid.mjs +66 -0
  129. package/skills/archify/renderers/shared/text-fit.mjs +105 -0
  130. package/skills/archify/renderers/shared/utils.mjs +254 -0
  131. package/skills/archify/renderers/shared/validator.mjs +86 -0
  132. package/skills/archify/renderers/workflow/README.md +301 -0
  133. package/skills/archify/renderers/workflow/render-workflow.mjs +37 -0
  134. package/skills/archify/renderers/workflow/workflow-compiler.mjs +4913 -0
  135. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  136. package/skills/archify/schemas/README.md +256 -0
  137. package/skills/archify/schemas/architecture.schema.json +152 -0
  138. package/skills/archify/schemas/common.schema.json +181 -0
  139. package/skills/archify/schemas/dataflow.schema.json +254 -0
  140. package/skills/archify/schemas/lifecycle.schema.json +280 -0
  141. package/skills/archify/schemas/sequence.schema.json +234 -0
  142. package/skills/archify/schemas/workflow.schema.json +439 -0
  143. package/skills/archify/scripts/check-render-output.mjs +1388 -0
  144. package/skills/archify/scripts/check-update.mjs +1710 -0
  145. package/skills/archify/scripts/delivery-update-child.mjs +23 -0
  146. package/skills/archify/scripts/render-examples.mjs +26 -0
  147. package/skills/archify/scripts/update-contract.mjs +182 -0
  148. package/skills/archify/skill-release.json +10 -0
package/docs/DESIGN.md ADDED
@@ -0,0 +1,905 @@
1
+ # dsh-archify-projects 设计与开发文档
2
+
3
+ > 状态:v1 设计定稿(2026-11-06)
4
+ > 参照物:`tt-a1i/archify` 的 `integrations/deepseek-harness`(`@tt-a1i/archify-dsh`,adapter 1.0.0 / Skill 3.0.1)。
5
+ > 本插件**不是** archify-dsh 的复刻:archify-dsh 只做「塞一个 Skill 提供方」,本插件做「项目台账 + Git 感知 + Agent 编排 + 图谱产物管理」的工作台。
6
+
7
+ ---
8
+
9
+ ## 1. 目标与非目标
10
+
11
+ ### 1.1 目标
12
+
13
+ | # | 能力 | 验收口径 |
14
+ | --- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15
+ | G1 | 左侧顶栏入口 | `sidebar.panellist` 出现条目,图标 + 标题随侧栏折叠自适应,点击后主面板切到本插件页面 |
16
+ | G2 | 新增项目 | 填「名称 + 代码库路径」即建项目;路径必须存在且是目录 |
17
+ | G3 | 自动识别 Git | 有 `.git` 时记录 `root / branch / HEAD commit / 上游 / ahead-behind / 工作区脏否`;无 Git 时 `git.isRepo=false`,跳过分支逻辑 |
18
+ | G4 | 生成两张图 | 项目结构图(`architecture`)+ 领域图(`architecture`,领域建模口径),产出独立 HTML + `candidate.json`,页面内可预览 |
19
+ | G5 | 更新 | 有 Git:`fetch → 切回记录的分支 → 快进到最新 commit → 重新分析`;无 Git:直接重新分析当前目录 |
20
+ | G6 | 打开即同步 | 打开项目详情时后台静默探测 Git 漂移(分支变了 / 有新 commit / 工作区脏),有漂移则在卡片上给出「更新」引导 |
21
+ | G7 | Agent 更新复用 DSH | 更新走 DSH 自己的 agent(`ctx.sessionController` / `ctx.agents`),模型、会话日志、审批全部复用宿主 |
22
+ | G8 | 插件工作区 | `$DSH_HOME/dsh-archify-projects/workspace` 目录 + 注册进 `ctx.workspaceRegistry`;每次运行的会话挂在这个 workspace 上 |
23
+ | G9 | 对话只有 archify、无关工具禁用 | preset `archify-projects` 只挂 fs / fs-search / shell / skill-base 五个工具插件;skill 提供方是隔离的(只有 archify);`skill` 工具按名字守卫,非 `archify` 一律拒绝 |
24
+ | G10 | 双主题 | 页面只用 DSH alias token,`data-ds-dark-theme` 切换后黑白背景均正常(含图表 iframe 外框、状态点、表格斑马纹) |
25
+
26
+ ### 1.2 非目标(v1 明确不做)
27
+
28
+ - 不做代码库克隆(只接受已存在于本机的路径);不做远端仓库浏览。
29
+ - 不做多图类型任意组合(固定两张:结构图 + 领域图)。
30
+ - 不做图谱 diff / 版本对比(每轮产物按时间戳目录保留,旧版可直接打开)。
31
+ - 不改 DSH 内置页面;不 append 到 `document.body`;不替换 app root。
32
+ - 不内联打包 React(客户端走宿主模块表)。
33
+
34
+ ---
35
+
36
+ ## 2. 关键概念
37
+
38
+ - **项目(Project)**:一条台账记录 = 名称 + 代码库绝对路径 + Git 快照 + 两份图谱产物状态。
39
+ - **运行(Run)**:一次 Agent 执行。`kind` 为 `create` / `update` / `regenerate`。每次运行会新建一个 DSH 会话,`sessionId` 记录在案,前端可「打开会话」追日志。
40
+ - **插件工作区(Plugin Workspace)**:`<DSH_HOME>/dsh-archify-projects/workspace`。所有运行产物写在这里,不进用户仓库。
41
+ - **图谱产物(Diagram)**:`{ htmlPath, specPath, generatedAt, branch, commit, status, error }`。
42
+
43
+ ---
44
+
45
+ ## 3. 数据模型
46
+
47
+ ### 3.1 存储位置
48
+
49
+ ```
50
+ $DSH_HOME/dsh-archify-projects/
51
+ projects.json # 台账(原子写:tmp + rename)
52
+ workspace/ # 插件工作区(注册为 DSH workspace)
53
+ <projectId>/
54
+ .archify/
55
+ architecture-<slug>-<YYYYMMDD-HHMMSS>/
56
+ candidate.json
57
+ <slug>.html
58
+ domain-<slug>-<YYYYMMDD-HHMMSS>/
59
+ candidate.json
60
+ <slug>.html
61
+ ```
62
+
63
+ ### 3.2 `projects.json`
64
+
65
+ ```jsonc
66
+ {
67
+ "version": 1,
68
+ "updatedAt": "2026-11-06T13:00:00.000Z",
69
+ "projects": [
70
+ {
71
+ "id": "prj_9f2c1a", // 稳定 id,创建后不变
72
+ "name": "cashloan-mono",
73
+ "repoPath": "D:\\work\\cashloan-mono",
74
+ "createdAt": "...",
75
+ "updatedAt": "...",
76
+ "git": {
77
+ "isRepo": true,
78
+ "root": "D:\\work\\cashloan-mono",
79
+ "branch": "feature/risk-engine",
80
+ "detached": false,
81
+ "commit": "a1b2c3d",
82
+ "commitFull": "a1b2c3d...",
83
+ "commitTime": "2026-11-05T09:12:33+08:00",
84
+ "commitSubject": "feat: add risk engine",
85
+ "upstream": "origin/feature/risk-engine",
86
+ "ahead": 0,
87
+ "behind": 2,
88
+ "dirty": true,
89
+ "remoteUrl": "git@github.com:org/repo.git",
90
+ "checkedAt": "...",
91
+ },
92
+ "diagrams": {
93
+ "structure": {
94
+ "status": "ready",
95
+ "htmlPath": "...",
96
+ "specPath": "...",
97
+ "generatedAt": "...",
98
+ "branch": "...",
99
+ "commit": "...",
100
+ "error": null,
101
+ },
102
+ "domain": {
103
+ "status": "idle",
104
+ "htmlPath": null,
105
+ "specPath": null,
106
+ "generatedAt": null,
107
+ "branch": null,
108
+ "commit": null,
109
+ "error": null,
110
+ },
111
+ },
112
+ "lastRun": {
113
+ "id": "run_1a2b",
114
+ "kind": "create",
115
+ "status": "running",
116
+ "sessionId": "sess_...",
117
+ "startedAt": "...",
118
+ "finishedAt": null,
119
+ "error": null,
120
+ },
121
+ },
122
+ ],
123
+ }
124
+ ```
125
+
126
+ `git` 快照字段全部可空:无 Git 时 `isRepo:false` 而其余字段为 `null`。
127
+
128
+ 产物状态机:`idle → queued → running → ready | failed`。
129
+
130
+ ### 3.3 不变量
131
+
132
+ 1. `id` 创建后不变;`repoPath` 可改(改路径视为重新探测 Git,图谱产物保留但标记 `stale:true`)。
133
+ 2. 同一项目同一时刻最多一个 `queued|running` 的 Run。
134
+ 3. 删除项目只删台账与项目工作区目录,**永不**碰 `repoPath` 指向的代码库。
135
+ 4. 所有路径写入前 `path.resolve`,并拒绝 `repoPath` 为空串或不是目录。
136
+
137
+ ---
138
+
139
+ ## 4. 架构分层
140
+
141
+ ```
142
+ src/
143
+ shared/ # 纯函数与类型,零 IO,host 与 client 同时 import
144
+ types.ts # Project / Diagram / GitSnapshot / Run 类型与默认值
145
+ protocol.ts # HTTP 路由约定 + 请求/响应类型 + 错误码
146
+ prompt.ts # Agent 提示词模板构建(纯函数,可单测)
147
+ format.ts # 展示用格式化(相对时间、commit 短写、ahead/behind 文案)
148
+ host/ # Node 侧,tsc → lib/host/
149
+ index.ts # apply(ctx, config):挂路由、工作区、preset 依赖检查
150
+ config.ts # Config schema(schemastery)
151
+ store.ts # projects.json 读写 + 原子写 + 串行化
152
+ git.ts # execFile 调 git:probe / fetch / checkout / ff-merge
153
+ workspace.ts # 插件工作区目录创建 + workspaceRegistry 注册
154
+ runner.ts # 起 agent 会话、驱动、状态回写(唯一碰 sessionController 的地方)
155
+ routes.ts # ctx.webServer.register 路由表
156
+ http.ts # 请求解析、响应封装、fence(CSRF/origin)
157
+ preset.ts # preset 侧安全插件:skill 提供方隔离 + 工具限制 + skill 名守卫
158
+ client/ # 浏览器侧,tsdown → dist/client.js
159
+ entry.ts # 模块工厂壳入口(inject + apply)
160
+ panel.ts # sidebar.panellist 图标
161
+ page.ts # main 全页
162
+ components/ # ProjectList / ProjectDetail / AddProjectDialog / DiagramCard / GitBadge / Status
163
+ styles.ts # CSS 文本(全部用 --dsw-* token)
164
+ api.ts # fetch 封装
165
+ locale.ts # 中英文案
166
+ ```
167
+
168
+ **依赖方向铁律**(dependency-cruiser 守):
169
+ `shared ← host`、`shared ← client`、`host ✗→ client`、`client ✗→ host`、`client ✗→ node:*`、`host ✗→ 浏览器全局`、无环。
170
+
171
+ ---
172
+
173
+ ## 5. 宿主 API 契约
174
+
175
+ ### 5.1 插件外壳
176
+
177
+ ```yaml
178
+ # cordis.patch.yml(本插件的 bundle patch)
179
+ - insert:
180
+ - id: dsh-archify-projects
181
+ name: dsh-archify-projects
182
+ ```
183
+
184
+ `package.json` 关键字段:
185
+
186
+ ```jsonc
187
+ {
188
+ "type": "module",
189
+ "main": "lib/host/index.js",
190
+ "exports": {
191
+ ".": "./lib/host/index.js",
192
+ "./client": "./dist/client.js",
193
+ "./preset": "./lib/host/preset.js",
194
+ "./locale/*.json": "./locale/*.json",
195
+ },
196
+ "dsh": {
197
+ "bundle": { "patch": "./cordis.patch.yml" },
198
+ "client": {
199
+ "platform": "web",
200
+ "immediately": true,
201
+ "inject": [
202
+ "@deepseek-ai/dsh-client-locale",
203
+ "@deepseek-ai/dsh-client-ui-layout",
204
+ "@deepseek-ai/dsh-client-ui-sidebar",
205
+ ],
206
+ },
207
+ },
208
+ }
209
+ ```
210
+
211
+ ### 5.2 用到的宿主服务
212
+
213
+ | 服务 | 用途 | 备注 |
214
+ | ------------------------------------------------------- | ---------------------------- | --------------------------- |
215
+ | `ctx.webServer.register({kind:'exact', path, handler})` | 插件 HTTP 路由 | 与 word-learn 同套路 |
216
+ | `ctx.workspaceRegistry.create(path, title?)` | 注册插件工作区 | 幂等:先 `list()` 找同 path |
217
+ | `ctx.sessionController`(或 `ctx.agents` + setup) | 起会话 + prompt | 见 5.4 |
218
+ | `ctx.skills.registerProvider(...)` | preset 内的隔离 skill 提供方 | 见 5.5 |
219
+ | `ctx.tools.restrict / guard` | 工具白名单 + skill 名守卫 | 见 5.5 |
220
+ | `ctx.logger` | 诊断日志 | 不抛 |
221
+
222
+ ### 5.3 HTTP 路由
223
+
224
+ 前缀 `/dsh-archify-projects/`。所有写操作要求 `POST` + JSON。
225
+
226
+ | 方法 | 路径 | 说明 |
227
+ | ------ | ----------------------------- | --------------------------------------------------------------- |
228
+ | GET | `/health` | 版本、工作区路径、是否具备 git |
229
+ | GET | `/projects` | 台账全量 |
230
+ | POST | `/projects` | `{name, repoPath}` → 探测 Git + 建目录 + 入账 + 触发 create run |
231
+ | PATCH | `/projects/:id` | 改名 / 改路径(改路径会重新探测 Git) |
232
+ | DELETE | `/projects/:id` | 删台账与项目目录 |
233
+ | POST | `/projects/:id/check` | 重新探测 Git(不触发 agent) |
234
+ | POST | `/projects/:id/update` | Git 同步 + 触发 update run |
235
+ | POST | `/projects/:id/diagram/:kind` | `kind=structure | domain`,重新生成单张图 |
236
+ | GET | `/projects/:id/runs` | 运行历史 |
237
+ | GET | `/artifact/:projectId/*` | 读产物(限定在项目工作区内,防穿越) |
238
+ | GET | `/models`? | 不需要 |
239
+
240
+ 响应统一 `{ok:true, value}` / `{ok:false, error:{code,message}}`。
241
+
242
+ **fence**:校验 `sec-fetch-site`(同源/同站之外 403)、`Origin`/`Host` 一致、方法白名单、路径穿越(解析后必须仍在项目目录内)。
243
+
244
+ ### 5.4 Agent 编排(runner)
245
+
246
+ 一次 Run:
247
+
248
+ 1. 生成 `runId`,把 `diagram.status` 置 `running`,落盘。
249
+ 2. 确保插件工作区存在(`workspace.ts`)。
250
+ 3. 起会话:
251
+ - 首选 `ctx.sessionController.create({ workspaceId, agentPreset: 'archify-projects' })`(若该服务存在);
252
+ - 回退 `ctx.agents.create({ sessionId, agentOptions, setup })`,在 `setup(agentCtx)` 里做与 preset 等价的限制(restrict + skill provider + guard)。
253
+ —— 两条路共享同一份 `preset.ts` 限制函数,避免不一致。
254
+ 4. `prompt`:写入结构化提示词(`shared/prompt.ts`),`mode:'queue'`。
255
+ 5. 监听该会话的结束:`agent.whenIdle()`;或轮询会话状态。结束后读产出的 `candidate.json` / `html` 路径,回写台账。
256
+ 6. 失败:`diagram.status='failed'` + `error`,保留 `sessionId` 供人看日志。
257
+
258
+ 产物路径发现:提示词里**强制**要求 agent 在最后回复里输出两行机器可读标记:
259
+
260
+ ```
261
+ ARCHIFY_ARTIFACT structure <绝对路径>
262
+ ARCHIFY_ARTIFACT domain <绝对路径>
263
+ ```
264
+
265
+ runner 解析会话最后一条 assistant 文本(或直接按约定目录 glob 最新 `.archify/*/` 目录兜底)。
266
+
267
+ ### 5.5 preset:只有 archify、无关工具禁用
268
+
269
+ ```yaml
270
+ - id: preset-archify-projects
271
+ name: '@deepseek-ai/dsh-agent-preset'
272
+ config:
273
+ id: archify-projects
274
+ name: Archify 图谱
275
+ description: 只用于代码库架构/领域建模出图,工具与 skill 均受限
276
+ plugins:
277
+ - id: persona
278
+ name: '@deepseek-ai/dsh-persona'
279
+ config:
280
+ prefix: 你是架构建模助手……
281
+ - id: tool-fs
282
+ name: '@deepseek-ai/dsh-tool-fs'
283
+ - id: tool-fs-search
284
+ name: '@deepseek-ai/dsh-tool-fs-search'
285
+ - id: tool-bash
286
+ name: '@deepseek-ai/dsh-tool-bash'
287
+ disabled: !!js process.platform === 'win32'
288
+ - id: tool-pwsh
289
+ name: '@deepseek-ai/dsh-tool-pwsh'
290
+ disabled: !!js process.platform !== 'win32'
291
+ - id: tool-skill
292
+ name: '@deepseek-ai/dsh-tool-skill'
293
+ - id: archify-sandbox
294
+ name: dsh-archify-projects/preset
295
+ ```
296
+
297
+ `lib/host/preset.js` 的 `apply(ctx)`:
298
+
299
+ 1. 解析 archify skill 目录:按序探测
300
+ `require.resolve('@tt-a1i/archify-dsh/package.json')` → 同目录 `skills/archify`;
301
+ 仓库内自带 `skills/archify` 兜底。找不到则 `ctx.logger.warn` 并退化为「不给 skill 工具」。
302
+ 2. `ctx.skills.registerProvider(...)`:名字 `archify-projects`,rank 600,`list()` 只返回 archify 一个条目,`get('archify')` 读 SKILL.md 正文。
303
+ 3. `ctx.tools.restrict({ allow: [...] })`,`allow` 按平台计算:
304
+ `['read','write','edit','glob','grep','skill'] + (win32 ? 'pwsh' : 'bash')`。
305
+ 名字不存在会抛 → 用 try/catch 包住并降级到 guard,日志记录降级原因(不静默)。
306
+ 4. `ctx.tools.guard(exec)`:
307
+ - `exec.name === 'skill'` 且 `exec.arguments.name !== 'archify'` → 返回拒绝理由。
308
+ - `exec.name` 不在白名单 → 返回拒绝理由(restrict 失效时的兜底)。
309
+
310
+ > 这是「无关工具禁用」的双保险:restrict 管**模型可见的工具表**,guard 管**实际执行**。
311
+
312
+ ### 5.6 复用 archify 的硬约束
313
+
314
+ - archify 的 `bin/archify.mjs` 用 `node <skill>/bin/archify.mjs finalize ...` 调用。插件不认识它的内部实现,只通过提示词约束:
315
+ - 工作目录 = 项目工作区目录(`.archify/` 落在这里);
316
+ - `--repo-root` 传代码库绝对路径(让 archify 读真实源码取证);
317
+ - 两张图分别 `architecture`,`meta.quality_profile="showcase"`。
318
+ - Skill 版本漂移不阻塞:只要 SKILL.md 存在就按当前内容执行。
319
+
320
+ ---
321
+
322
+ ## 6. Git 策略
323
+
324
+ 全部经 `execFile('git', args, {cwd, timeout, windowsHide:true})`,不拼 shell 字符串。
325
+
326
+ | 动作 | 命令 | 失败处理 |
327
+ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
328
+ | 探测 | `rev-parse --show-toplevel`、`rev-parse --abbrev-ref HEAD`、`rev-parse HEAD`、`log -1 --format=%cI | %s`、`rev-parse --abbrev-ref @{u}`、`rev-list --left-right --count @{u}...HEAD`、`status --porcelain`、`remote get-url origin` | 任一失败 → 该字段 `null`;`--show-toplevel` 失败即 `isRepo:false` |
329
+ | 同步 | `fetch --all --prune`(有 origin 时)→ `rev-parse --verify <branch>` → `checkout <branch>`(若 detached,`checkout -B <branch>`)→ `merge --ff-only @{u}` | 工作区脏且需要切换 → 不硬来,回 `{skipped:true, reason:'dirty'}` 并在 UI 明示 |
330
+ | 当前点 | 同步后重新 probe | — |
331
+
332
+ **安全**:仓库路径来自用户配置,`checkout` 只操作该仓库;不 `reset --hard`、不 `clean`、不 `stash`。分支名作为独立 argv 传入(不做字符串拼接)。
333
+
334
+ ---
335
+
336
+ ## 7. 前端设计
337
+
338
+ ### 7.1 设计系统(`design_recommend` 结论 + 本插件裁剪)
339
+
340
+ | 维度 | 取值 | 理由 |
341
+ | ---- | --------------------------------------------------------------- | ---------------------------------------------------------------------- |
342
+ | 气质 | 开发者工具 / 控制台,克制、精确、密度中等 | DSH 是工具型宿主,页面必须像它的一部分 |
343
+ | 风格 | Modern Dark 的克制版 + Swiss 排版 | 推荐里的「cinematic/glassmorphism/haptic」是移动端语境,桌面宿主里不做 |
344
+ | 字体 | 正文/标题继承宿主 `--dsw-font-family`;路径、commit、分支用等宽 | 不引入 Web 字体(离线可用、与宿主一致) |
345
+ | 主色 | 宿主 `--dsw-alias-brand-primary` | 不抢 DSH 品牌色 |
346
+ | 强调 | 成功态复用 `--dsw-alias-state-success-primary` | 图谱「已就绪」用宿主绿 |
347
+ | 圆角 | 8 / 12(卡片 12) | 宿主 radius 令牌偏小,16 在桌面控制台里过大 |
348
+ | 间距 | 4 / 8 / 16 / 24 / 32 | design_recommend 的 `--space-*` 刻度 |
349
+ | 动效 | 150–200ms,`power1.inOut`;只用 opacity/transform | Motion 旋钮 3/10 |
350
+ | 密度 | 标准(6/10) | 列表行高 40,卡片内边距 16 |
351
+
352
+ ### 7.2 token 映射表(**禁止硬编码颜色**)
353
+
354
+ | 语义 | token |
355
+ | ------------- | ------------------------------------------------- |
356
+ | 页面底 | `--dsw-alias-bg-base` |
357
+ | 卡片/分区 | `--dsw-alias-bg-layer-1` |
358
+ | 内嵌/浮层 | `--dsw-alias-bg-layer-2` |
359
+ | 主文字 | `--dsw-alias-label-primary` |
360
+ | 次文字 | `--dsw-alias-label-secondary` |
361
+ | 弱化文字 | `--dsw-alias-label-tertiary` |
362
+ | 分割线 | `--dsw-alias-border-l1` / `--dsw-alias-border-l2` |
363
+ | 控件边框 | `--dsw-alias-border-l3` / `--dsw-alias-border-l4` |
364
+ | 悬停底 | `--dsw-alias-interactive-bg-hover` |
365
+ | 品牌/主按钮 | `--dsw-alias-brand-primary` |
366
+ | 成功/就绪 | `--dsw-alias-state-success-primary` |
367
+ | 告警/脏工作区 | `--dsw-alias-state-warn-primary` |
368
+ | 失败 | `--dsw-alias-state-error-primary` |
369
+ | 空闲 | `--dsw-alias-state-idle-primary` |
370
+ | 代码块 | `--dsw-alias-markdown-code-block` |
371
+
372
+ 深色切换由宿主在 `<html data-ds-dark-theme>` 上做,alias token 自动翻转,因此**页面不需要任何 `@media (prefers-color-scheme)` 分支**。
373
+
374
+ ### 7.3 页面结构
375
+
376
+ ```
377
+ ┌ 顶栏(页内) ─────────────────────────────────────────────┐
378
+ │ 架构建模 [刷新] [新增项目 +] │
379
+ ├ 左列(320px) ───┬ 右列(详情) ──────────────────────────┤
380
+ │ ▸ 项目卡片 列表 │ 项目名 · 路径 · [打开会话] [更新] │
381
+ │ 名称 │ Git 条:分支 / commit / 上游 / ahead-behind / 脏 │
382
+ │ 分支 · commit │ ┌ 项目结构图 ─────────────────────────┐ │
383
+ │ 两图状态点 │ │ 状态 · 生成时间 · 当时 commit │ │
384
+ │ │ │ [预览] [新窗口打开] [重新生成] │ │
385
+ │ │ └─────────────────────────────────────┘ │
386
+ │ │ ┌ 领域图 ─────────────────────────────┐ │
387
+ │ │ └─────────────────────────────────────┘ │
388
+ │ │ 运行记录(最近 5 条) │
389
+ └───────────────────┴────────────────────────────────────────┘
390
+ ```
391
+
392
+ 空状态:左列无项目时整页居中给「还没有项目 · 新增项目」引导(UX 规范:Empty States 必须给行动点)。
393
+
394
+ 预览:`<iframe src="/dsh-archify-projects/artifact/<id>/.archify/.../x.html">`,`sandbox="allow-scripts allow-same-origin"`,高度 480,可「新窗口打开」。
395
+
396
+ ### 7.4 交互与状态
397
+
398
+ | 场景 | 表现 |
399
+ | -------- | -------------------------------------------------------------------------------------------- |
400
+ | 新增项目 | 对话框:名称(可空 → 用目录名)+ 路径(必填,失焦即时校验)→ 提交后卡片进入「探测 Git…」骨架 |
401
+ | 路径非法 | 输入框下红字 + 提交按钮禁用(不是提交后弹错) |
402
+ | 无 Git | Git 条显示「非 Git 目录 · 基于当前目录分析」,不显示分支控件 |
403
+ | 运行中 | 卡片状态点脉冲(`opacity` 1→0.4,1.4s),详情页显示「Agent 运行中…」+ 会话链接 |
404
+ | 完成 | 状态点变绿,显示生成时间与**生成时的 commit**;若当前 commit 与之不同,标注「有更新可用」 |
405
+ | 失败 | 红点 + 错误摘要 + 「查看会话」 |
406
+ | 更新 | 二次确认(会切分支/快进)→ 进度条文字(拉取 → 切换 → 分析) |
407
+
408
+ ### 7.5 无障碍与鲁棒性
409
+
410
+ - 所有图标按钮有 `aria-label`;列表项是 `<button>` 而非可点 `div`(UX 规范:Compact Control Semantics)。
411
+ - 焦点环用 `--dsw-alias-brand-primary`,不用 `outline:none`。
412
+ - 文字对比度 ≥ 4.5:1(全部走 alias token,宿主已保证)。
413
+ - 长路径、长分支名用 `text-overflow: ellipsis` + `title` 全文,不撑破布局(UX 规范:Text Reflow)。
414
+ - 动效尊重 `prefers-reduced-motion`。
415
+
416
+ ---
417
+
418
+ ## 8. 质量门
419
+
420
+ | 门 | 命令 | 判据 |
421
+ | -------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
422
+ | 类型 | `pnpm typecheck` | `tsc --noEmit` + `tsc -p tsconfig.host.json --noEmit` 0 错 |
423
+ | 静态检测 | `pnpm lint` | ESLint flat config(正确性 + 边界),0 error |
424
+ | 格式 | `pnpm format:check` | Prettier |
425
+ | 依赖边界 | `pnpm deps:check` | dependency-cruiser:无环 + 分层方向 |
426
+ | 单测 | `pnpm test:unit` | vitest node:store / git 解析 / prompt / format / routes |
427
+ | 组件测试 | `pnpm test:component` | vitest + jsdom + RTL:真实渲染 `dist/client.js` 产物,覆盖列表/详情/对话框/空状态/**深浅色 token 使用断言** |
428
+ | 产物检查 | `pnpm artifact:check` | `dist/client.js` 是**单个** `__ModuleLoader__.load` 壳、无顶层 import/export、只外置 baseline 11 个模块、体积预算 |
429
+ | 构建 | `pnpm build` | tsdown + tsc 全绿 |
430
+ | 前端验证 | 装进 profile → 重启 dsh → 浏览器(bsk)打开 GUI | 侧栏入口可见、页面渲染、**切深浅色各截一张图**、新增项目走通 |
431
+
432
+ 覆盖率门槛:`lib/host/**` 行 ≥ 70、分支 ≥ 62、函数 ≥ 78;`store.ts`/`git.ts`/`prompt.ts` 单独 ≥ 90。
433
+
434
+ ---
435
+
436
+ ## 9. 里程碑
437
+
438
+ | M | 内容 | 完成判据 | 状态 |
439
+ | --- | --------------------------------------------------------------------- | --------------------------------------------- | ---- |
440
+ | M1 | 设计文档 + 骨架(package.json / tsconfig / eslint / vitest / tsdown) | `pnpm install` 通过,`pnpm lint` 空跑通过 | ✅ |
441
+ | M2 | `shared/` 纯逻辑 + 单测 | 提示词/格式化/协议单测绿 | ✅ |
442
+ | M3 | `host/` 台账 + Git + 路由(不含 agent) | 集成测试用桩 ctx 走通 CRUD + 探测 | ✅ |
443
+ | M4 | `host/` runner + `preset.js` 限制 | 桩 agent 走通一次 run,断言 prompt 与限制调用 | ✅ |
444
+ | M5 | `client/` 页面 + 组件测试 | 组件测试绿,产物检查绿 | ✅ |
445
+ | M6 | 装进 profile,前端真机验证(深浅色) | 截图 + 交互走通 | ✅ |
446
+ | M7 | README / 文档收尾 | — | ✅ |
447
+
448
+ ### 9.1 真机验证记录(2026-11-07)
449
+
450
+ 两种主题、两条路径都在真实宿主里跑通过,截图存在 `docs/verify/`:
451
+
452
+ | 场景 | 结果 | 证据 |
453
+ | ----------------------------- | -------------------------------------------------------------------- | -------------------------------------------------- |
454
+ | 深色主题整页 | 列表 / Git 卡 / 两张图 / 运行记录全部正常 | `docs/verify/panel-dark.png` |
455
+ | 浅色主题整页 | 同上,token 全部跟随宿主,无硬编码颜色漏出 | `docs/verify/panel-light.png`、`panel-light-2.png` |
456
+ | Git 项目首次生成 | 结构图 + 领域图双 ready,iframe 正常渲染 | `docs/verify/panel-dark.png` |
457
+ | 非 Git 目录(`isRepo:false`) | 跳过分支切换,直接按当前目录分析,双 ready | 见 §6 Git 策略 |
458
+ | 更新(切分支 + 快进 + 重跑) | 确认弹窗 → 运行启动 → 结果落台账 | `docs/verify/runs-after-restart.png` |
459
+ | 宿主被 kill 后重开 | 悬挂的 `running` 被标 failed「宿主重启,本次运行中断」,按钮恢复可点 | `docs/verify/runs-after-restart.png` |
460
+ | 图表 iframe 高度 | `flex:none` + `clamp(420px,58vh,760px)`,不再被压成一条工具栏 | 同上 |
461
+
462
+ 真机验证抓出来的三个坑(都应写进回归):
463
+
464
+ 1. **`.dap-diagram` 卡片本身也是 flex item**,只给 iframe 设 `flex:none` 不够,父卡片还会被祖先列压扁、再用 `overflow:hidden` 把图裁掉。
465
+ 2. **运行记录的时钟**:`run.startedAt.slice(11, 19)` 切的是 UTC,东八区显示成 05:51。改成按本地时区格式化(`clockText`)。
466
+ 3. **宿主重启会留下永久 `running`**:进程被 kill 时没人写 `finishedAt`,页面据此永远「生成中」且按钮全禁用。开库对账现在会收尸(`reconcileArtifacts`),前端 `isRunning` 另有 25 分钟兜底。
467
+
468
+ ---
469
+
470
+ ## 10. 风险与对策
471
+
472
+ | 风险 | 对策 |
473
+ | -------------------------------------------- | ------------------------------------------------------------------------------------- |
474
+ | 宿主 API 版本差异(sessionController 有/无) | runner 双路:`sessionController` 优先,`agents.create` 回退;启动时探测并记日志 |
475
+ | `tools.restrict` 名字校验抛错 | try/catch 降级到 guard,日志记录;不允许静默 |
476
+ | archify skill 目录找不到 | preset 里不挂 skill 工具 + 提示词改为「直接写 candidate.json」的降级路径;UI 明示降级 |
477
+ | Agent 产不出约定标记 | 产物发现走「标记优先 + `.archify/` 最新目录兜底」 |
478
+ | 用户仓库工作区脏 | 不自动 stash/reset;只 `--ff-only`,失败即报「需要手动处理」 |
479
+ | 产物路径穿越 | `/artifact/` 路由把 `resolve` 后的路径与项目目录做前缀校验 |
480
+ | 前端 CSS 污染宿主 | 全部类名加 `dap-` 前缀 + 样式用 `<style>` 标签在 `apply` 里注入、`ctx.effect` 卸载 |
481
+
482
+ ---
483
+
484
+ ## 11. 目录结构(落地版)
485
+
486
+ ```
487
+ dsh-archify-projects/
488
+ package.json
489
+ tsconfig.json tsconfig.host.json tsconfig.eslint.json
490
+ tsdown.config.ts vitest.config.ts eslint.config.js
491
+ cordis.patch.yml icon.svg README.md
492
+ locale/{zh-CN,en}.json
493
+ docs/DESIGN.md
494
+ src/shared/… src/host/… src/client/…
495
+ tests/{unit,features,contract,component,integration,support}/
496
+ scripts/check-artifact.mjs
497
+ ```
498
+
499
+ ---
500
+
501
+ ## 12. v0.2 设计方向(草案,2026-11-07)
502
+
503
+ **判断**:插件要做的不是「再多画一种图」,而是 DSH 里的**代码库架构地图册**——
504
+ 每个在册仓库有一张跟随 Git 演进的结构图 / 领域图,随时打开、随时更新,并且一眼能看出
505
+ 「这张图对应哪个 commit、过期了没有、这次更新改了什么」。它同时是给人看的总览、
506
+ 给 agent 复用的上下文,以及评审变更时的底图。
507
+
508
+ **v0.1 的缺口**(现在的「列表 + iframe」回答不了):
509
+
510
+ 1. 哪个仓库过期了、落后几个提交、多久没更新?——只能逐个点开看。
511
+ 2. 这张图是哪次运行、哪个 commit 生成的?更早的版本还能不能打开?——台账有 `runs[]`,
512
+ 但没有「运行 → 产物」的索引,历史图只能去磁盘上翻。
513
+ 3. 生成是黑盒:20 分钟里 agent 在读什么、卡在哪一步?——只有「运行中」和一个会话链接。
514
+ 4. 图和代码的漂移只能靠人记住。
515
+
516
+ **v0.2 范围**(按优先级):
517
+
518
+ | # | 能力 | 数据影响 |
519
+ | --- | ------------------------------------------------------------------------------- | --------------------------------------------------------------- |
520
+ | 1 | 总览 Atlas:卡片墙 + 新鲜度(branch/commit/落后/脏/上次更新)+ 批量更新 | 无(`GET /projects` 已够,前端算漂移) |
521
+ | 2 | 项目工作区:结构/领域切换、全屏、版本时间线、运行历史并入右栏 | `runs[]` 需要补产物路径(新增可空字段 `artifacts`,旧台账兼容) |
522
+ | 3 | 生成过程可见:轮询 `runs[].status` 升级为步骤条(探测 → 阅读 → 出图),可开会话 | 先复用现有 run 状态;步骤流需要 runner 暴露会话事件(后续) |
523
+ | 4 | 漂移提示:图谱 commit ≠ 当前 HEAD → 卡片/详情一键更新 | 无(`diagram.commit` 已有) |
524
+ | 5 | 导出:HTML / PNG、复制产物链接 | 无(复用 `/artifact/` 路由) |
525
+
526
+ **非目标(仍然不做)**:克隆仓库、任意图类型组合、跨仓库依赖图、图 diff(版本时间线可切换即止)。
527
+
528
+ **落点不变**:宿主 `--dsw-alias-*` token 上色、单 bundle(220 KB 预算)、iframe 预览、
529
+ 台账 `projects.json` 唯一真相、产物不写进用户仓库。
530
+
531
+ **原型**:`docs/design/workbench-v02.html`(OpenDesign 项目 `archify-workbench-v02` 的
532
+ `index.html` 快照),截图 `docs/design/v02-*.png`。浅色 / 深色两套视觉,交互:
533
+ 卡片进详情、结构/领域切换、原型底栏切屏与切主题。
534
+
535
+ **落地顺序**(待维护者确认后开工):页面拆两屏(overview / detail)→ `RunRecord` 补
536
+ `artifacts` 并回填历史 → 版本时间线 → 生成步骤条 → 导出。
537
+
538
+ ---
539
+
540
+ ## 13. v0.3 图册模型(2026-11-07,按需求收敛)
541
+
542
+ **一句话**:从「一个仓库两张固定图」升级成「一个仓库一册图」——先提取**概念**,
543
+ 再用自然语言 prompt 让 agent 按概念出图;图有**临时/永久**两种时效、可以**父子嵌套**
544
+ 和**大图配小图**,还能对着一张图**追问并自动补细节**。
545
+
546
+ 原型:`docs/design/workbench-v03.html`(OpenDesign 项目 `archify-workbench-v03`),
547
+ 截图 `docs/design/v03-*.png`。
548
+
549
+ ### 13.1 版本口径:分支级,不是 commit 级
550
+
551
+ 这是和 v0.1/v0.2 最大的区别,**台账里不再用 commit 标识「这一版图」**:
552
+
553
+ | 维度 | v0.2 | v0.3 |
554
+ | ---------- | ----------------------- | -------------------------------------------------- |
555
+ | 版本键 | `branch + commit` | `branch`(+ 工作区状态) |
556
+ | 工作区改动 | 只标记 dirty | **纳入分析**:暂存区(staged)和未暂存都算代码证据 |
557
+ | 产物归属 | 一次运行对应一个 commit | 一次运行对应「某分支当时的代码(含工作区)」 |
558
+ | 展示 | 显示生成时的 commit | 显示 `分支 · 含工作区改动 · 生成时间` |
559
+
560
+ 理由:开发中真正想看的是「我现在这份代码长什么样」,而不是某个历史 commit 的冻结快照。
561
+ `headCommit` 仍可作为**内部溯源**(日志、排障)记录,但不进版本键、不进 UI 主文案。
562
+
563
+ 因此「过期」的判定从「commit 变了」改成:
564
+
565
+ - 分支变了(`git.branch !== diagram.branch`);
566
+ - 或工作区状态变了(`dirty` 标志或 staged/unstaged 计数与生成时不同);
567
+ - 或距上次生成超过阈值(提示但不强制)。
568
+
569
+ ### 13.2 概念(Concept):图的第一等公民
570
+
571
+ 概念是从代码里提取的、可复用的架构单元,**属于项目**而不是属于某张图:
572
+
573
+ ```jsonc
574
+ // 项目级概念表(存 workspace/<projectId>/concepts.json)
575
+ {
576
+ "version": 1,
577
+ "concepts": [
578
+ {
579
+ "id": "cpt_ledger",
580
+ "name": "项目台账",
581
+ "summary": "projects.json 是唯一真相,产物路径相对 workspace 存储",
582
+ "evidence": ["src/host/store.ts", "src/host/config.ts"], // 来源文件,供追问时定位
583
+ "tags": ["data", "invariant"],
584
+ "branch": "main",
585
+ "updatedAt": "...",
586
+ },
587
+ ],
588
+ }
589
+ ```
590
+
591
+ - 概念在**首次生成**时提取(约 15–25 个),之后每次更新增量维护(新增/合并/废弃)。
592
+ - 图只引用**概念子集**(`conceptIds`),所以「补充细节」= 往图里加概念,
593
+ 「创建子图」= 用其中一两个概念单独立一张图。
594
+ - 概念跨图复用:同一份概念既能进结构图,也能进自定义图,不用重复提取。
595
+
596
+ ### 13.3 图(Diagram)与套图(Set)
597
+
598
+ ```jsonc
599
+ {
600
+ "id": "dgm_host_modules",
601
+ "projectId": "prj_...",
602
+ "title": "src/host 模块结构",
603
+ "kind": "structure" | "domain" | "custom",
604
+ "prompt": "画出 src/host 的模块结构,标出 routes → runner 的调用关系", // 永久图必填
605
+ "persistence": "permanent" | "temporary", // ★ 时效
606
+ "parentId": "dgm_structure", // 可选:挂在哪张图下(父子)
607
+ "setId": "set_structure", // 可选:属于哪套图(大图配小图)
608
+ "conceptIds": ["cpt_route", "cpt_runner", "..."],
609
+ "branch": "main",
610
+ "generatedAt": "...",
611
+ "htmlPath": ".archify/main/structure-.../x.html", // 相对 workspaceRoot
612
+ "status": "idle" | "queued" | "running" | "ready" | "failed",
613
+ "error": null
614
+ }
615
+ ```
616
+
617
+ **时效(这是需求里最核心的新概念)**:
618
+
619
+ | 时效 | 行为 | 存哪 |
620
+ | ------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------- |
621
+ | `permanent`(永久) | prompt **存进台账**;下次「更新图册」自动用同一 prompt 重画;出现在侧栏图册里 | `projects.json` 的 `diagrams[]` |
622
+ | `temporary`(临时) | 只跑这一次,产物留在磁盘但**不进下次更新计划**;prompt 不存(或存了也不复用) | 台账里可留一条 `temporary` 记录用于本次展示,更新时清理 |
623
+
624
+ 「任何图或套图都能临时/永久」→ 时效字段放在**图**上,套图的时效由其成员推导:
625
+ 一套图里只要有一张永久,这套图就会在下次更新时被重建(其余临时成员按需重画)。
626
+
627
+ **套图**:`setId` 相同的图构成一套,约定「一张大图 + N 张细节小图」:
628
+
629
+ ```
630
+ set_structure
631
+ ├── 大图 src/host 模块结构(永久)
632
+ ├── 小图 routes 内部(永久)
633
+ ├── 小图 runner 生命周期(永久)
634
+ └── 小图 台账读写(临时)
635
+ ```
636
+
637
+ UI 上大图铺满查看器,小图在下方缩略图条里点选切换;父图上的「+ 子图」直接用选中的
638
+ 概念开一张新图并挂上 `parentId`。
639
+
640
+ ### 13.4 追问与自动补细节
641
+
642
+ 每张图有独立的问答线程(`qa[]`),问题走**同一个受限 agent**,但 prompt 带三样上下文:
643
+
644
+ 1. 这张图的 prompt 与概念子集;
645
+ 2. 图产物的 `candidate.json`(已有哪些节点/边);
646
+ 3. 概念的 `evidence` 文件路径(让 agent 能真的去读代码回答,而不是编)。
647
+
648
+ 回答下面给两个动作,正好对应需求里的两种「补充」:
649
+
650
+ - **补进图里**:把新概念/新节点合并进当前图 → 产生该图的新版本(旧版本按时间戳保留);
651
+ - **创建子图**:以这次回答为 prompt 开一张子图(`parentId` = 当前图,默认永久)。
652
+
653
+ > 注意:问答本身不消耗「更新」额度,但要防止滥用——同一张图并发提问超过 1 个时排队。
654
+
655
+ ### 13.5 数据落盘
656
+
657
+ ```
658
+ $DSH_HOME/dsh-archify-projects/
659
+ projects.json # 台账:projects[] + 每个项目的 diagrams[]
660
+ workspace/<projectId>/
661
+ concepts.json # 项目级概念表
662
+ .archify/<branch-slug>/<diagramId>-<stamp>/
663
+ candidate.json # 图的结构化描述(节点/边/分组)
664
+ <slug>.html # 成品
665
+ qa.jsonl # 该图的问答记录
666
+ ```
667
+
668
+ **不变量更新**:
669
+
670
+ 1. `ready` 必须有 `htmlPath`;`temporary` 图不参与下次更新计划。
671
+ 2. 任何时刻最多一条 `runs[]` 是 `running`。
672
+ 3. `parentId` 必须指向同项目存在的图;`setId` 成员必须同项目、同 branch。
673
+ 4. 删除项目要连概念表与整棵子图一起删。
674
+ 5. 概念被删除前,先摘掉引用它的图(或标 `concept-missing` 让用户决定)。
675
+
676
+ ### 13.6 页面结构调整(v0.3 原型)
677
+
678
+ | 区域 | v0.2 | v0.3 |
679
+ | ---------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
680
+ | 总览 | 项目卡片墙 | 卡片墙 + 概念数 + 图/套图数 + 「工作区有改动」统计 |
681
+ | 工作区布局 | 左列表 + 右详情(单列) | **三栏**:左「图册树」(默认套图 / 自定义 / 当前图的小图 / 概念索引)、中「查看器」(大图 + 小图缩略条)、右「检查器」(图信息 / 提问) |
682
+ | 版本 | 时间线(按 commit) | 分支选择 + 工作区状态条(不再列 commit) |
683
+ | 新建图 | 无 | 对话框:prompt + 时效(永久/临时)+ 归属(独立套图/挂父图)+ 概念范围 |
684
+ | 提问 | 无 | 右栏「提问」页签:线程 + 「补进图里」/「创建子图」 |
685
+
686
+ ### 13.7 落地顺序
687
+
688
+ 1. **数据模型**:`Diagram` 加 `prompt/persistence/parentId/setId/conceptIds/branch`,
689
+ `RunRecord` 加 `artifacts`;`concepts.json` 与迁移(旧台账只有两张图 → 升级成
690
+ `permanent` + `kind` 对应)。
691
+ 2. **分支级 Git**:`git.ts` 补 `staged/unstaged` 计数;更新判定改为分支+工作区。
692
+ 3. **更新编排**:`update` 跑「默认两张永久图 + 所有 `permanent` 自定义图」,
693
+ 临时图不跑;每张图一个产物目录。
694
+ 4. **概念提取**:prompt 里加「先输出概念清单再画图」,落 `concepts.json`。
695
+ 5. **问答**:`POST /diagrams/:id/ask`,复用受限 agent,回答落 `qa.jsonl`,
696
+ 两个动作各对应一个路由(merge / create-child)。
697
+ 6. **前端**:三栏工作区 + 新建图对话框 + 问答页签(原型已定稿)。
698
+
699
+ ---
700
+
701
+ ## 14. v0.4:布局收敛、进度(todo)与用量(2026-11-08)
702
+
703
+ 原型:`docs/design/workbench-v04.html`(OpenDesign 项目 `archify-workbench-v04`),
704
+ 截图 `docs/design/v04-*.png`。
705
+
706
+ ### 14.1 右栏必须有高度限制(bug 修复)
707
+
708
+ 实测问题:一条长回答会把右栏撑得比页面还高,左/中两栏被拉成空白,整页跟着滚动
709
+ (2026-11-08 的真机截图)。根因是三栏网格 `align-items: start` + 右栏内容自然撑高。
710
+
711
+ 约定改成**工作台整屏、三栏各自滚动**:
712
+
713
+ - `#screen-wb` 是 `height:100%; display:flex; flex-direction:column`,页面主体不再整体滚动;
714
+ - 三栏 `align-items: stretch`,每个 panel `min-height:0; display:flex; flex-direction:column`;
715
+ - 面板内部(图册树 / 查看器 stage / 检查器 insp-body)各自 `overflow:auto`;
716
+ - 提问输入框 `position:sticky; bottom:0`,并给滚动体留底部 padding,避免最后一行被输入框盖住;
717
+ - 结果:提问线程再长,页面高度不变,左图和中图不动,只有右栏内部滚动。
718
+
719
+ ### 14.2 所有 agent 会话都要有 todo(进度可见)
720
+
721
+ - 两个 preset(出图 `archify-projects`、问答 `archify-projects-ask`)都挂
722
+ `@deepseek-ai/dsh-tool-todo`,配置 `allowParallelInProgress: false`(该字段必填)。
723
+ - 提示词第一条硬约束:**开始前先建 4–7 步的 todo**,每完成一步就更新;最后一步固定是
724
+ 「回报产物标记」。
725
+ - runner 在运行期间读会话的 `todos` projection(宿主已有),把快照写进
726
+ `RunRecord.todos: {content,status}[]`;会话里没有 todo 时留空,页面不显示进度条。
727
+ - 页面:
728
+ - 顶部 `runbar`:`生成中 · 正在:<当前 in_progress 步骤>(5/7)` + 进度条 + 百分比 + 本次 tokens + 会话链接;
729
+ - 检查器「进度」:完整 todo 列表(✓ / spinner / ○);
730
+ - 总览卡片运行中显示当前步骤(`正在:…`),代替只转圈的焦虑感。
731
+
732
+ ### 14.3 项目 token 消耗
733
+
734
+ - 宿主自带 `dsh-token-meter`,`tokenUsage` projection 给出
735
+ `uncachedInputTokens / outputTokens / cacheReadTokens / cacheWriteTokens`。
736
+ - runner 每次运行结束时把这份快照写进 `RunRecord.usage`;项目累计 = `runs[].usage` 求和
737
+ (保留最近 `keepRuns` 条,累计值单独冗余在 `Project.usageTotal`,避免裁剪后失真)。
738
+ - 显示位置:
739
+ - 工作台 gitbar 一枚 chip:`18.4k tokens · ≈ ¥0.09`(本次);
740
+ - 检查器「用量」:本次 / 累计 tokens + 输入/缓存/输出明细 + **估算**费用;
741
+ - 总览卡片页脚:`main · 2 小时前 · 3 组图 · 18.4k tokens`。
742
+ - 费用是估算:单价来自配置 `pricing`(默认按 `deepseek-v4.1-flash` 的输入/输出价),
743
+ 文案必须带「≈ / 估」,不假装是账单。
744
+
745
+ ### 14.4 数据模型增量
746
+
747
+ ```ts
748
+ RunRecord {
749
+ ...existing,
750
+ todos: { content: string; status: 'pending' | 'in_progress' | 'completed' }[]
751
+ usage: { uncachedInput: number; output: number; cacheRead: number; cacheWrite: number } | null
752
+ }
753
+ Project {
754
+ ...existing,
755
+ usageTotal: number // 历史累计 tokens(不随 runs 裁剪丢失)
756
+ }
757
+ ```
758
+
759
+ `GET /projects` 已经返回整条项目,页面不需要新接口。
760
+
761
+ ### 14.5 实现顺序
762
+
763
+ 1. `cordis.patch.yml`:两个 preset 加 `tool-todo`(ask 也加,问答同样能建计划);
764
+ 2. prompt:首条硬约束 + 出图分步模板(概念 → structure → finalize → domain → finalize → messages);
765
+ 3. runner:运行中读 `todos` projection(节流写台账),结束时读 `tokenUsage` 写 `usage` / `usageTotal`;
766
+ 4. 页面:整屏三栏 + 右栏独立滚动 + runbar + 进度 / 用量区块;
767
+ 5. 测试:todo 投影解析、用量累加、长 QA 不影响布局(组件测试可用长文本断言容器高度)。
768
+
769
+ ---
770
+
771
+ ## 15. v0.5:新建图弹窗——把 archify 的图型做成可解释的 tag(2026-11-08)
772
+
773
+ 原型:`docs/design/workbench-v05.html`(OpenDesign 项目 `archify-workbench-v05`),
774
+ 截图 `docs/design/v05-dialog-*.png`。
775
+
776
+ ### 15.1 为什么要有图型 tag
777
+
778
+ 用户往往不知道 archify 能画什么,只知道「我想看某个东西」。弹窗里把 archify 的 5 个
779
+ type 做成 tag,点一下告诉用户:**这张图适合看什么、会展示代码库的哪些信息、能让你掌握什么**。
780
+ 选不选都可以 —— 不选就是「不限,由 agent 判断」;选了只是让 agent 少猜一步、图更稳。
781
+
782
+ | tag | archify type | 适合看 | 会展示 | 能掌握 |
783
+ | ------ | -------------- | -------------------------------- | ---------------------------------------- | -------------------------------------- |
784
+ | 架构图 | `architecture` | 系统由什么组成、谁依赖谁 | 组件与服务、进程/网络/信任边界、外部依赖 | 分层与职责、关键依赖、边界外的风险 |
785
+ | 流程图 | `workflow` | 一个流程怎么走、有哪些判断与审批 | 步骤、分支条件、失败与回退 | 主流程与异常路径、谁在什么条件下做什么 |
786
+ | 时序图 | `sequence` | 一次请求在模块之间怎么往返 | 参与者、消息顺序、返回与异步 | 调用链、时序依赖、失败重试点 |
787
+ | 数据流 | `dataflow` | 数据从哪来、经过谁、到哪去 | 数据源、转换、存储、消费者 | 数据血缘与口径、敏感数据流向 |
788
+ | 状态机 | `lifecycle` | 一个对象有哪些状态、怎么流转 | 状态、转移条件、终态 | 状态机全貌、等待与重试、非法流转 |
789
+
790
+ ### 15.2 弹窗结构
791
+
792
+ 1. **图型(可选)**:`不限(agent 判断)` + 5 个 tag;单选、可取消。
793
+ 2. **说明卡**:选中后展示「适合看 / 会展示 / 能掌握」三行;prompt 占位符同步变成
794
+ 「已选时序图:prompt 可以只写关注点…」。
795
+ 3. **Prompt**:自然语言,必填。
796
+ 4. **概念范围**:从项目概念表勾选;只围绕这些概念取证,图更聚焦。
797
+ 5. **目录范围**(新增,可选):`src/host` / `src/client` / `src/shared` 这类目录 chip;
798
+ 只让 agent 在这些目录里取证,不选则扫全仓库。
799
+ 6. **时效 / 归属**:沿用 v0.3 的永久/临时、独立套图/挂父图。
800
+
801
+ 图型、概念、目录全部可选;三者都是「选得更准,图更好」,不是必填项。
802
+
803
+ ### 15.3 数据模型增量
804
+
805
+ ```ts
806
+ type ArchifyType = 'architecture' | 'workflow' | 'sequence' | 'dataflow' | 'lifecycle'
807
+
808
+ DiagramState {
809
+ ...existing,
810
+ /** 用户选的 archify 渲染器;null = 由 agent 判断。 */
811
+ renderer: ArchifyType | null
812
+ /** 限定取证的目录(相对仓库根);空数组 = 全仓库。 */
813
+ pathScope: readonly string[]
814
+ }
815
+ ```
816
+
817
+ - `CreateDiagramRequest` / `PatchDiagramRequest` 加 `renderer`、`pathScope`;
818
+ - 提示词:选了 renderer 就写死「渲染器必须用 `<type>`」;没选则写「按 prompt 选择 type」;
819
+ 有 pathScope 就写「只在这些目录里取证,别读别的」;
820
+ - 默认两张图:`structure` → `architecture`,`domain` → `architecture`(建模口径不同),
821
+ `renderer` 固定,不进弹窗。
822
+
823
+ ### 15.4 实现顺序
824
+
825
+ 1. types/protocol/store:加 `renderer`、`pathScope`(旧台账补 null / []);
826
+ 2. prompt:渲染器与目录范围的硬约束;
827
+ 3. 弹窗:图型 tag + 说明卡 + 目录 chip;
828
+ 4. 测试:图型序列化/迁移、prompt 含 renderer/pathScope、弹窗选择联动。
829
+
830
+ ---
831
+
832
+ ## 16. v0.6:删掉左栏,图选择变成图片上方的缩略图条(2026-11-08)
833
+
834
+ 原型:`docs/design/workbench-v06.html`(OpenDesign 项目 `archify-workbench-v06`),
835
+ 截图 `docs/design/v06-prototype.png`;真机截图 `docs/verify/v06-*.png`。
836
+
837
+ ### 16.1 评估结论:可行
838
+
839
+ 用户的判断成立:左栏的列表在真实使用里几乎不看,但它占掉 218px,把图片挤成
840
+ 「能看但不够看」的宽度;而同一条信息(有哪些图、哪张选中)完全可以压在图片上方
841
+ 一条 91px 的缩略图条里。改动后工作区从三栏变两栏:
842
+
843
+ | 项 | 改前 | 改后 |
844
+ | ------------------------------------- | ------------------------------------ | ------------------------------- |
845
+ | 列结构 | 图册树 218px + 查看器 + 检查器 320px | 查看器 + 检查器 320px |
846
+ | 查看器宽度(1440 窗口、DSH 侧栏 220) | ≈ 566px | **784px**(+218) |
847
+ | 图选择 | 左栏纵向列表 | 图片上方横向缩略图条(滚动) |
848
+ | 新建图 | 顶部按钮 + 左栏按钮 | 缩略图条末尾的虚线「+ 新建图」 |
849
+ | 页面高度 | 900 | 900(不因内容变高) |
850
+
851
+ ### 16.2 两个需要决策的点,已按这个做
852
+
853
+ 1. **缩略图用真预览还是占位**:用真预览(把 artifact HTML 用 `scale(0.25)` 塞进
854
+ 132×74 的卡片),ready 的图才有;`idle/queued/running/failed` 显示状态文字。
855
+ 代价是每个 ready 图多一个 iframe —— 本地静态 SVG,实测 4 张缩略图加载正常;
856
+ 超过 ~10 张时横向滚动,iframe 用 `loading="lazy"`。
857
+ 2. **图自带的工具栏**:archify 成品顶部有「浅色/经典/演示/导出」一排按钮,缩到
858
+ 0.25 倍仍然抢眼且点不到;同源注入一段 `<style>` 把 `.toolbar / .diagram-nav /
859
+ .focus-chip / .archify-toast` 隐藏(`hideArtifactChrome`),跨域或被拦就跳过。
860
+
861
+ ### 16.3 父子图与套图怎么放
862
+
863
+ - 父图(`parentId === null`)排在默认两张之后;子图紧跟父图,带 `└` 缩进标记与
864
+ 左移 12px,视觉上仍是从属关系。
865
+ - 套图(`setId`)不再单独分组——缩略图条按「默认 → 自定义根 → 子图」排,
866
+ 组间用一条竖分隔线;同屏可见,不需要展开/折叠。
867
+ - 临时图在缩略图右上角挂「临时」小标签,永久图不挂(默认即永久)。
868
+
869
+ ### 16.4 概念索引:删除
870
+
871
+ 左栏底部的「概念索引」删掉了。理由:成品图里已经带概念/节点信息,左栏这份重复;
872
+ 概念仍然存在于台账与弹窗的「概念范围」里,不影响功能。
873
+
874
+ ### 16.5 实现
875
+
876
+ - `page.tsx`:删掉 `.dap-rail*` 面板与 `RailItem`,新增 `ThumbStrip` + `Thumb` +
877
+ `hideArtifactChrome`;顶部 header 不再放「新建图」。
878
+ - `styles.ts`:`.dap-wb` 两列;`.dap-strip` / `.dap-thumbcard*` / `.dap-strip__new`;
879
+ 删掉 `.dap-rail*` 全部样式。
880
+ - 窄屏(<1180)检查器下沉一整行(`max-height:420px`)。
881
+
882
+ ---
883
+
884
+ ## 17. v0.7:缩略图条改成纯文字图名 tab(2026-11-08)
885
+
886
+ 用户反馈:91px 的缩略图条仍然太高,「只要一个图名就行了,不需要缩略图」。
887
+
888
+ 改动:
889
+
890
+ | 项 | v0.6 | v0.7 |
891
+ | ----------- | ------------------------------------- | -------------------------------------- |
892
+ | 条高 | 91px(132×74 缩略卡) | **35px**(26px tab + 上下 padding) |
893
+ | 每项内容 | 0.25 倍 iframe 真预览 + 标题 + 状态点 | **状态点 + 图名**(临时图挂 4 字标签) |
894
+ | 新建图 | 96px 虚线卡片 | 同一条尾部的虚线小按钮(26px 高) |
895
+ | 查看器高度 | 605px | **729px**(图片多出 124px) |
896
+ | 额外 iframe | 每张 ready 图一个 | **0 个**(只剩查看器那一个) |
897
+ | 页面高度 | 900 | 900 |
898
+
899
+ 顺带删掉了 `hideArtifactChrome()`:没有缩略图就不需要再往成品里注入隐藏工具栏的样式,
900
+ 少一次跨文档写操作,也少一个失败面。
901
+
902
+ 组件测试同步改:断言 tab 文本、断言 `.dap-thumbcard*` 一个都不存在(防止缩略图回归)。
903
+
904
+ OpenDesign 原型同步更新到 v0.7:`docs/design/workbench-v07.html`
905
+ (项目 `archify-workbench-v07`),截图 `docs/design/v07-prototype.png`,条高与实现一致(35px)。