dsh-archify-projects 0.0.0-stage → 0.1.1

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 +231 -2
  2. package/cordis.patch.yml +99 -0
  3. package/dist/client.js +2281 -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 +397 -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
@@ -0,0 +1,397 @@
1
+ # 交接文档 · dsh-archify-projects
2
+
3
+ > 面向接手的 agent。读完这一份就能改代码、跑验证、装/卸插件、看懂当初为什么这么写。
4
+ > 面向使用者的上手说明在 [README](../README.md),设计取舍在 [DESIGN.md](./DESIGN.md)。
5
+
6
+ 最后更新:2026-11-07(v0.1.0,功能完备、已在真机跑通)
7
+
8
+ ---
9
+
10
+ ## 0. 一句话
11
+
12
+ DSH 的左栏插件「架构建模」:把一个代码库登记进台账,用**受限 agent**(skill 只有 archify)
13
+ 生成**项目结构图**和**领域图**两张可交互 HTML,支持按 Git 分支/commit 更新,页面内嵌 iframe 直接看。
14
+
15
+ 宿主侧 Node 代码 + 浏览器侧单 bundle,不是静态插件:台账落盘、Git 探测、agent 编排、产物回收、
16
+ 路由与鉴权全在进程里跑。
17
+
18
+ ---
19
+
20
+ ## 1. 现在什么状态
21
+
22
+ | 方面 | 状态 |
23
+ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
24
+ | 功能 | 新增/删除项目、Git 探测(分支/commit/上游/脏)、首次生成、更新(切分支+快进+重跑)、重新生成单图、运行记录、深/浅色、非 Git 目录、iframe 内嵌预览、新窗口打开 |
25
+ | 测试 | 174 个用例 / 12 个文件,`vitest` 全绿(约 2 s) |
26
+ | 静态门 | `tsc`(两份 tsconfig)、`eslint`、`prettier --check`、`dependency-cruiser`、`artifact:check`(51.5 KB / 220 KB 预算)全绿 |
27
+ | 真机 | 在 `127.0.0.1:3080` 的真实宿主里跑通过「Git 项目」与「非 Git 目录」两条路径,深浅色都验过;截图见 [`docs/verify/`](./verify) |
28
+ | 产物 | `~/.dsh/dsh-archify-projects/` 下 1 个项目(`dsh-word-learn`),两张图 ready |
29
+ | 版本库 | 本地 `git init` 完成,**还没有远端仓库**(见 §9) |
30
+
31
+ **没有半成品功能**。已知未做的都在 §8,属于可选增强,不是缺陷。
32
+
33
+ ---
34
+
35
+ ## 2. 五分钟跑起来
36
+
37
+ ```bash
38
+ cd ~/Projects/dsh-archify-projects # Windows 检出在 D:/projects/blog/dsh-plugins/dsh-archify-projects
39
+ pnpm install
40
+ pnpm verify # build → artifact:check → typecheck/lint/format/deps → test
41
+ ```
42
+
43
+ > **在 Linux 上开发/验证,优先用容器**:`~/Projects/dsh-docker` 的 `dsh-dev`
44
+ > (`127.0.0.1:3080`)已经把本仓库挂成 profile 的 bundle,改源码不用碰宿主 `~/.dsh`。
45
+ > 绑定关系、日常循环、模型凭据、冒烟步骤都写在 [CONTAINER.md](./CONTAINER.md)。
46
+ > 下面这段是「装进本机 DSH」的通用流程,容器已经替你做过。
47
+
48
+ 装进 DSH(已在 `~/.dsh/profiles/web` 里做好,`link:` 形式):
49
+
50
+ ```jsonc
51
+ // ~/.dsh/profiles/web/package.json
52
+ "dependencies": { "dsh-archify-projects": "link:/home/xirizhi/Projects/dsh-archify-projects" },
53
+ "dsh": { "profile": { "bundles": [ /* ... 其它 ... */, "dsh-archify-projects" ] } }
54
+ ```
55
+
56
+ **改了哪一层,决定要不要重启**:
57
+
58
+ | 改动位置 | 生效方式 |
59
+ | ------------------------------------------------------------- | ------------------------------------------------------------------------ |
60
+ | `src/client/**`、`dist/`、`locale/` | 热更(`dsh-client-hmr` 轮询重建产物,约 0.5 s 后 remount),刷新页面即可 |
61
+ | `src/host/**`、`cordis.patch.yml`、`package.json` 的 `dsh` 段 | **必须重启 dsh**(ESM 模块缓存按进程存活) |
62
+ | `skills/archify/**` | 下次新建 agent 会话时生效,不用重启 |
63
+
64
+ 重启方式:双击桌面 **`DSH启动.bat`**(它是「停旧 + 起新」的一体入口,内部走 `~/dsh-tools/dsh-web.ps1`)。
65
+ `~/.dsh/logs/latest-web-url.txt` 会存带 `?token=` 的登录链接——**每次重启 token 都会变**,旧标签页会提示
66
+ `dsh web authentication required`,重开这个 URL 即可。
67
+
68
+ > ⚠️ 用 WMI/计划任务自动拉起 dsh 试过两次,都只成功到「停掉旧进程」,「起新的」这一步不落地
69
+ > (桌面 bat 依赖交互式窗口)。自动重启脚本见 `~/Documents/deepseek-harness/default-workspace/restart-helper.ps1`,
70
+ > 它能把带 token 的 URL 抓进 `latest-web-url.txt`,但**起服务仍建议人工点 bat 确认**。
71
+
72
+ ---
73
+
74
+ ## 3. 架构:一次「更新」发生了什么
75
+
76
+ ```
77
+ 页面按钮
78
+ └─ POST /dsh-archify-projects/projects/:id/update src/host/routes.ts
79
+ ├─ git.ts 切回记录的分支 → fetch → FF 到最新 → 重新探测快照
80
+ ├─ store.ts 台账里记一条 run(running),两张图置 queued
81
+ └─ runner.ts 起受限会话
82
+ ├─ sessions.create({ presetId: 'archify-projects' })
83
+ ├─ policy.ts agent.ctx 上装 tools.restrict(allow) + tools.guard(fn)
84
+ ├─ prompt.ts 拼 prompt(代码库路径、分支/commit、上次产物、产物标记约定)
85
+ ├─ 投 prompt → whenIdle(或 runTimeoutMs 超时)
86
+ ├─ 收尾:读 assistant 文本里的产物标记 → 否则扫 <项目目录>/.archify/ 兜底
87
+ └─ store.patch 两张图 ready/failed + 运行记录收尾
88
+ └─ 页面每 2 s 轮询 GET /projects,看到 ready 就把 iframe src 换成产物 URL
89
+ ```
90
+
91
+ 产物一律写在**插件自己的工作区**,不污染代码库:
92
+
93
+ ```
94
+ ~/.dsh/dsh-archify-projects/
95
+ ├── projects.json 台账(唯一真相)
96
+ └── workspace/<projectId>/
97
+ └── .archify/<kind>-<slug>-<stamp>/ candidate.json + 成品 *.html + finalize-summary.json
98
+ ```
99
+
100
+ 台账不变量(改代码时别破坏):
101
+
102
+ 1. `diagrams[kind].status ∈ {idle, queued, running, ready, failed}`;`ready` 必须有 `htmlPath`。
103
+ 2. 任何时刻最多一条 `runs[]` 是 `running`;宿主启动时由 `reconcileArtifacts()` 收尸。
104
+ 3. 产物路径一律**相对** `workspaceRoot`,路由按它拼 URL 并做越界校验(`..` 直接 403)。
105
+ 4. 删除项目要连工作区目录一起删;台账是唯一索引,磁盘上没有台账之外的有效产物。
106
+
107
+ ---
108
+
109
+ ## 4. HTTP 契约(前缀 `/dsh-archify-projects`)
110
+
111
+ | 方法 | 路径 | 用途 |
112
+ | ------ | ----------------------------- | ------------------------------------------------------------ |
113
+ | GET | `/health` | 存活 + `presetId` / 版本,排查「插件到底挂上没」 |
114
+ | GET | `/projects` | 台账全量(页面轮询这个) |
115
+ | POST | `/projects` | 新增项目(`{ repoPath, name? }`,自动探测 Git 并起首次生成) |
116
+ | GET | `/projects/:id` | 单项目 |
117
+ | PATCH | `/projects/:id` | 改名字等 |
118
+ | DELETE | `/projects/:id` | 删项目 + 工作区 |
119
+ | POST | `/projects/:id/check` | 重探 Git |
120
+ | POST | `/projects/:id/update` | 更新(切分支 + 快进 + 重跑两张图) |
121
+ | POST | `/projects/:id/diagram/:kind` | 重新生成单张图(`structure` / `domain`) |
122
+ | GET | `/projects/:id/runs` | 运行记录 |
123
+ | GET | `/artifact/<相对路径>` | 产物 HTML(iframe 与「新窗口打开」都走这里) |
124
+
125
+ 错误统一 `{ code, message }`,`code ∈ {bad-request, forbidden, not-found, conflict, internal, path-invalid, …}`
126
+ (见 `src/shared/protocol.ts`)。**页面按 code 决定文案,不解析 message**,别在文案里做逻辑。
127
+
128
+ ### 鉴权:curl 拿 403 是正常的
129
+
130
+ 路由挂在宿主 webServer 上,前面有宿主的浏览器信任栅栏 `connection.requestRejection(req)`:
131
+
132
+ - 命令行 `curl` → **403**(untrusted authority);
133
+ - 同源页面里的 `fetch` → 200。
134
+
135
+ 所以「用 curl 验证接口」永远 403,不是 bug,也不是路由没注册。判断路由在不在看 `/health`:
136
+ 403 说明**路由存在但被栅栏拦了**,404 才是真没挂上。
137
+
138
+ ---
139
+
140
+ ## 5. agent 只能用 archify,无关工具进不来(三层)
141
+
142
+ | 层 | 机制 | 效果 |
143
+ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
144
+ | preset | `cordis.patch.yml` 里两个私有 preset:出图 `archify-projects`(fs / fs-search / shell / skill)、问答 `archify-projects-ask`(**fs / fs-search / skill**) | 工具面天生就这么大 |
145
+ | skill 源 | preset 私有的 `dsh-skill-filesystem`:`includeDefaultRoots: false` + `bundledSkillDir` 指向 `skills/` | agent 的 skill 列表里只有 `archify` |
146
+ | 运行期 | `policy.ts`:`tools.restrict({ allow })` + `tools.guard(fn)` | 即使在名单外被调用也被拒;`skill` 只认 `archify` |
147
+
148
+ 两种会话,白名单不同:
149
+
150
+ | 会话 | 工具面 | 用途 |
151
+ | ----------- | --------------------------------------------------- | ------------------------------------------ |
152
+ | 出图(run) | `read, write, edit, glob, grep, skill` + 平台 shell | 读代码、写产物、跑 archify finalize |
153
+ | 提问(ask) | **只有 `read, glob, grep, skill`** | 回答对图的细节提问;没有 shell、没有写工具 |
154
+
155
+ 联网、子 agent、workflow、todo 这些工具**压根不可见**,调用即 `UNKNOWN_TOOL`。
156
+ 两类会话的 `skill` 都只认 `archify`。
157
+
158
+ `tools.restrict` 必须装在 `agent.ctx` 上(根 ctx 直接抛 `requires a scoped context`),
159
+ 所以它写在 `runner.ts` 建会话之后、第一条 prompt 之前,只影响本插件起的会话。
160
+
161
+ **archify skill 是 vendored 的**(`skills/archify/`,约 7.3 MB,来自
162
+ `@tt-a1i/archify-dsh@1.0.0` 的 tarball)。曾经指向 profile 里的那个包,结果用户一卸载原插件,
163
+ 建会话就 `Cannot find module '@tt-a1i/archify-dsh/package.json'`,图一张都出不来。
164
+ 现在靠 `createRequire(baseUrl).resolve('dsh-archify-projects/package.json')` 定位自己,自足。
165
+ (因此 `package.json` 的 `exports` 里必须有 `"./package.json": "./package.json"`,别删。)
166
+
167
+ ---
168
+
169
+ ## 6. 前端
170
+
171
+ - 单 bundle:`tsdown` 把 `src/client/**` 打成 `dist/client.js`(外部化 react),预算 220 KB。
172
+ - 主题:**只用宿主 token**(`--dsw-alias-*`),深浅色自动跟随,禁止硬编码颜色。
173
+ - 结构:`page.tsx`(状态与轮询)/ `components.tsx`(展示)/ `styles.ts`(一把 CSS)/ `locale.ts`(中英文案)/ `api.ts`(fetch 封装)。
174
+ - iframe:`sandbox="allow-scripts allow-same-origin"`,高度 `clamp(420px, 58vh, 760px)`。
175
+
176
+ 三个真机才暴露的坑,改前端前先读:
177
+
178
+ 1. **卡片和 iframe 都要 `flex:none`**。父链是 `dap-page(flex) → dap-body(grid) → dap-col(flex, overflow:auto) → dap-card(flex column)`;
179
+ 只锁 iframe,卡片本身仍会被压成 ~90 px,再用 `overflow:hidden` 把图裁成一条工具栏。
180
+ 2. **时钟按本地时区格式化**(`clockText()`)。早期 `startedAt.slice(11,19)` 切的是 UTC,东八区显示成 05:51。
181
+ 3. **`isRunning()` 有 25 分钟兜底**(`isRunAlive` / `hasStaleRun`)。宿主被 kill 时 `running` 会留在台账里,
182
+ 没有兜底页面就永久「生成中」且按钮全禁用。
183
+
184
+ ---
185
+
186
+ ## 7. 质量门与怎么加测试
187
+
188
+ ```bash
189
+ pnpm typecheck # tsc --noEmit + tsc -p tsconfig.host.json
190
+ pnpm lint # eslint(skills/ 已排除)
191
+ pnpm format:check # prettier(skills/、lib、dist、coverage 已排除)
192
+ pnpm deps:check # dependency-cruiser:禁环、禁 client→host 反向依赖
193
+ pnpm test # vitest + coverage
194
+ pnpm artifact:check # dist/client.js 体积与外部化
195
+ pnpm verify # 以上全部 + build
196
+ ```
197
+
198
+ 当前基线:**174 passed / 12 files**,`coverage` 目录不进版本库。
199
+
200
+ 测试布局(改哪儿补哪儿):
201
+
202
+ | 目录 | 覆盖 |
203
+ | ---------------------------------- | ---------------------------------------------------------------- |
204
+ | `tests/unit/shared.test.ts` | 纯函数:格式化、`isRunning`/`isStale`、prompt 拼接、产物标记解析 |
205
+ | `tests/unit/runner.test.ts` | 产物发现、运行历史、超时、**开库收尸**(`reconcileArtifacts`) |
206
+ | `tests/unit/routes.test.ts` | 路由表:正常路径 + 400/403/404 + 越界路径 |
207
+ | `tests/unit/git.test.ts` | Git 探测:非仓库、分离头、无上游、脏工作区 |
208
+ | `tests/unit/store.test.ts` | 台账读写、串行化、损坏文件兜底 |
209
+ | `tests/unit/policy.test.ts` | 白名单与守卫(非 archify skill 必须被拒) |
210
+ | `tests/unit/components.test.ts` | 客户端纯函数(`clockText`) |
211
+ | `tests/contract/locale.test.ts` | 中英文案 key 对齐(漏 key 会真机显示英文) |
212
+ | `tests/integration/runner.test.ts` | 桩 ctx 走通一次 run(含 preset 限制调用断言) |
213
+
214
+ 一条硬规矩:**修 bug 先写会红的用例**。上面三个真机坑里,坑 2、坑 3 都已经钉进单测。
215
+
216
+ ---
217
+
218
+ ## 8. 已知问题与可选增强(都非阻塞)
219
+
220
+ 1. **并发**:一次点击只能跑一个项目;`update` 正在跑时再点会被拒(返回 `conflict`)。多项目排队没有做。
221
+ 2. **运行收尸的边界**:`reconcileArtifacts` 只在**宿主启动**时跑。如果进程活着但 agent 卡死(没超时也没结束),
222
+ 前端只能靠 25 分钟兜底解锁,台账里的 `running` 仍要等下次启动才被清。
223
+ 3. **`archify` 产物目录名靠猜**:`discoverArtifacts` 按目录名前缀(`structure-` / `architecture-` / `domain-`)
224
+ 归类,没有就按「新目录优先」补位。archify 换命名就要改 `kindOfDirName()`。
225
+ 4. **没有远端仓库**:`git init` 已做,`origin` 未建(见 §9)。
226
+ 5. **没有 i18n 之外的 l10n**:目前只有 zh-CN / en 两份文案。
227
+ 6. **`skills/archify` 升级**:覆盖目录即可(`npm pack @tt-a1i/archify-dsh` 解包),无需改代码;
228
+ 依赖它的只有 `archify-skill.ts` 的路径探测与 prompt 里约定的产物标记。
229
+
230
+ ---
231
+
232
+ ## 9. 版本库
233
+
234
+ 公开仓库已就绪:<https://github.com/guicaiyue/dsh-archify-projects>(`main`,168 个文件)。
235
+
236
+ ```bash
237
+ cd D:/projects/blog/dsh-plugins/dsh-archify-projects
238
+ git remote -v # origin git@github.com:guicaiyue/dsh-archify-projects.git
239
+ git push # 已 set-upstream,直接推
240
+ ```
241
+
242
+ 提交习惯:一个功能/一次修复一个 commit,中文 subject(`feat:` / `fix:` / `docs:`),
243
+ 正文写清「为什么这么改」,别写「update file」。
244
+
245
+ > 建仓库这一步 agent 做不了:本机只有 SSH key(能推不能建),没有 `gh` CLI、没有 PAT、
246
+ > `git credential` 里也没有存货。远端仓库必须先由账号持有人在 GitHub 上建好。
247
+ > `.gitignore` 忽略 `node_modules/ lib/ dist/ coverage/`;`skills/archify/` 是**刻意提交**的,
248
+ > 不跟着仓库走克隆下来就跑不起来。
249
+
250
+ ---
251
+
252
+ ## 10. 文件索引(改东西先看这里)
253
+
254
+ | 文件 | 行数 | 职责 / 什么时候动它 |
255
+ | --------------------------- | ------ | --------------------------------------------------------------------------------------------------------- |
256
+ | `src/host/index.ts` | 114 | 插件入口:注册路由(`ctx.inject(['webServer'], …)`)、启动对账、装卡片。**改挂载方式看这里** |
257
+ | `src/host/routes.ts` | 457 | 路由分发与每个 handler。加接口从这里加,路径表在 `shared/protocol.ts` |
258
+ | `src/host/runner.ts` | 633 | agent 编排、产物回收(`discoverArtifacts`)、超时、开库对账(`reconcileArtifacts`)。**最复杂的一个文件** |
259
+ | `src/host/store.ts` | 232 | 台账读写、串行化、损坏兜底 |
260
+ | `src/host/git.ts` | 210 | Git 探测、切分支/快进。非 Git 目录走空快照 |
261
+ | `src/host/policy.ts` | 135 | 工具白名单 + 守卫。放宽/收紧 agent 能力只改这里 |
262
+ | `src/host/config.ts` | 107 | 配置项与默认值(`dataDir` / `presetId` / `archifySkillDir` / `runTimeoutMs` / `keepRuns`) |
263
+ | `src/host/context.ts` | 211 | 宿主服务的**防御式**包装。`ctx.get()` 与 `ctx.<svc>` 都可能抛,必须 try/catch |
264
+ | `src/host/http.ts` | 195 | 响应封装 + 浏览器信任栅栏 |
265
+ | `src/host/workspace.ts` | 85 | 工作区目录与 agent 工作目录 |
266
+ | `src/host/archify-skill.ts` | 123 | 定位 archify skill(vendored 优先,其次配置项) |
267
+ | `src/shared/*.ts` | 619 | 纯逻辑:类型与不变量、prompt、协议/路由常量、格式化。**无副作用,随便测** |
268
+ | `src/client/*` | 1570 | 页面、组件、样式、文案、fetch |
269
+ | `cordis.patch.yml` | 57 | 挂插件 + 声明受限 preset。**改 preset 要重启** |
270
+ | `docs/DESIGN.md` | 498 | 设计取舍、数据模型、里程碑与验证记录 |
271
+ | `docs/CONTAINER.md` | — | dsh-dev 容器里的绑定关系、日常循环、模型凭据与冒烟步骤 |
272
+ | `skills/archify/` | 7.3 MB | vendored 的 archify skill(第三方,勿手改) |
273
+
274
+ ---
275
+
276
+ ## 11. 接手第一步建议
277
+
278
+ 1. `pnpm install && pnpm verify` —— 先确认基线是绿的。
279
+ 2. 打开 `http://127.0.0.1:3080/`(token 见 `~/.dsh/logs/latest-web-url.txt`),点左栏「架构建模」,
280
+ 对 `dsh-word-learn` 点一次「重新生成」——用一张图确认「路由 → 会话 → 产物 → iframe」整条链在你手里是通的。
281
+ 3. 读 `docs/DESIGN.md` 的 §3(数据模型)/ §5.4(runner)/ §7(前端)。
282
+ 4. 想改行为,先在 `tests/unit/` 里写一条会红的用例再动手。
283
+
284
+ ---
285
+
286
+ ## 12. v0.3 变更:从「两张图」到「图册」
287
+
288
+ > 本节描述当前(2026-11-08)的模型;§1–§11 里仍按 v0.1 写的口径以本节为准。
289
+
290
+ ### 12.1 版本口径
291
+
292
+ - **分支级,不是 commit 级**:`DiagramState` 记录 `branch` + `tree`(暂存/未暂存/未跟踪计数),
293
+ `isStale` 只看「分支变了 / 工作区变了」,不看新 commit。
294
+ - `git status --porcelain` 由 `parseTree()` 解析成三个计数(`src/host/git.ts`);
295
+ **暂存区内容也算代码证据**,提示词里明确写「以工作区当前内容为准」。
296
+
297
+ ### 12.2 数据模型(`src/shared/types.ts`,`LEDGER_VERSION = 2`)
298
+
299
+ ```ts
300
+ Project {
301
+ git: GitSnapshot // + tree: { staged, unstaged, untracked }
302
+ diagrams: { structure, domain } // 默认两张,id 固定为 structure / domain
303
+ custom: DiagramState[] // 任意自定义图,id = dgm_xxx
304
+ concepts: Concept[] // 项目级概念表(id/name/summary/evidence/tags)
305
+ qa: QaEntry[] // 对图的提问记录(diagramId/question/answer/applied)
306
+ runs / lastRun // + diagramIds
307
+ }
308
+ DiagramState {
309
+ id, kind: structure|domain|custom, title, prompt,
310
+ persistence: permanent|temporary, parentId, setId, conceptIds,
311
+ status, htmlPath, specPath, generatedAt, branch, tree, error
312
+ }
313
+ ```
314
+
315
+ - `permanent`:prompt 存台账,`更新图册` 时自动用同一 prompt 重画;
316
+ - `temporary`:只跑这一次,`更新图册` 时不选它(产物仍可看);
317
+ - `parentId` 构成父子图;`setId` 相同 = 一套图(大图 + 细节小图);
318
+ - 概念是项目级,图只引用 `conceptIds` 子集。
319
+
320
+ 迁移在 `store.normalize`:v1 的 `diagrams.structure/domain` 对象直接读成两张永久图,
321
+ 其余字段(prompt/parent/set/concepts)补默认值;旧台账不需要手工改。
322
+
323
+ ### 12.3 接口新增
324
+
325
+ | 方法 | 路径 | 用途 |
326
+ | ------------ | ---------------------------------------------- | ----------------------------------------------------------------------------- |
327
+ | POST | `/projects/:id/diagrams` | 新建自定义图(`{title, prompt, persistence?, parentId?, conceptIds?, run?}`) |
328
+ | PATCH/DELETE | `/projects/:id/diagrams/:diagramId` | 改标题/prompt/时效/概念;删图(连带子图与 qa) |
329
+ | POST | `/projects/:id/diagrams/:diagramId/regenerate` | 重画单张(默认图与自定义图同一入口) |
330
+ | POST | `/projects/:id/diagrams/:diagramId/ask` | 只读提问,回答落 `qa[]` |
331
+ | POST | `/projects/:id/qa/:qaId/merge` | 把回答并进图(prompt 追加「用户追问补充」后重画) |
332
+ | POST | `/projects/:id/qa/:qaId/child` | 以回答为 prompt 建子图 |
333
+
334
+ `/projects/:id/diagram/:kind` 保留为兼容入口。
335
+
336
+ ### 12.4 提示词与上下文(`src/shared/prompt.ts`)
337
+
338
+ - `buildRunPrompt`:环境事实(分支 + 工作区计数)→ 概念表契约(写 `concepts.json`)→
339
+ 每张图的建模口径/自定义 prompt → archify 调用方式(目录名以图 id 开头)→
340
+ `ARCHIFY_ARTIFACT <id> <path>` 回报标记。
341
+ - `buildAskPrompt`:只给「这张图的 prompt + candidate.json 截断 + 绑定的概念及证据文件 +
342
+ 代码库根目录」,要求先结论后依据、查不到就说没有、最后给「建议补进图里的节点/关系」。
343
+ - 问答会话的工具面由 `policy.readonlyToolNames()` 收口为 `read/glob/grep/skill`。
344
+
345
+ ### 12.5 页面
346
+
347
+ 三栏工作台(`src/client/page.tsx`):左「图册树」(默认套图 / 自定义 / 子图 / 概念索引)、
348
+ 中「查看器」(iframe + 子图缩略条)、右「检查器」(图信息 / 提问)。
349
+ 总览是项目卡片墙(分支、工作区计数、概念数、过期提示)。样式仍只用 DSH token,
350
+ 单 bundle 81.6 KB(预算 220 KB)。
351
+
352
+ ### 12.6 v0.5/v0.6 增量(2026-11-08)
353
+
354
+ **新建图弹窗的图型 tag**(`docs/DESIGN.md` §15):
355
+
356
+ - `DiagramState` 增 `renderer: ArchifyType | null` 与 `pathScope: string[]`;
357
+ `ArchifyType` = archify 的 5 个真实 type(architecture / workflow / sequence / dataflow / lifecycle)。
358
+ - 默认两张图 `renderer` 固定 `architecture`(老台账迁移时补这个默认值);自定义图不选即 `null`,
359
+ 提示词写「由你按 prompt 自行选择」。
360
+ - `pathScope` 只接受仓库内相对路径:绝对路径、`..`、反斜杠一律丢弃(`sanitizeScope`)。
361
+ - 提示词:选了 renderer 写死「必须使用 \`<type>\`」;有 pathScope 写「只看这些目录」。
362
+
363
+ **进度(todo)**:
364
+
365
+ - 两个 preset 都挂 `dsh-tool-todo`(`allowParallelInProgress: false`);
366
+ - ⚠️ **`todo_write` 必须写进 `policy.ts` 的白名单**:`tools.restrict({allow})` 按名字收口,
367
+ 漏掉它模型会直接说「I don't have a todo_write tool」,页面也就没有进度(踩过一次);
368
+ - 提示词第 0 条硬约束:开工先 `todo_write` 拆 4–7 步,每步更新;
369
+ - runner 运行/提问期间每 3s 读 `todos` projection 写台账(`RunRecord.todos` / `QaEntry.todos`)。
370
+
371
+ **用量(token)**:
372
+
373
+ - runner 结束时读 `tokenUsage` projection(`uncachedInputTokens / outputTokens / cacheReadTokens /
374
+ cacheWriteTokens`)写 `RunRecord.usage`、`QaEntry.usage`,并累加 `Project.usageTotal`;
375
+ - 全 0 的用量记成 `null`(首轮就报错的调用不该留一条「0 tokens」);
376
+ - 费用是**估算**:`config.pricing = {inputPerMTok, outputPerMTok, cacheReadPerMTok}`,
377
+ 不配就只显示 token;页面文案永远带 `≈`。
378
+
379
+ **布局**:工作台整屏三栏(`dap-scroll--wb` + 三栏各自 `overflow:auto`),
380
+ 右栏再长也不撑高页面;提问输入框 sticky 在栏底。实测 `document.body.scrollHeight == clientHeight`,
381
+ 检查器内部 `scrollHeight 7803 / clientHeight 552`。
382
+
383
+ **容器模型口径**:宿主默认模型切到 `zifei`(自费网关)后,容器的 `self` 路由
384
+ (`192.168.1.4:31783`)`/v1/responses` 返回 404;`dsh-docker` 的 profile patch 与 `.env`
385
+ 已同步加 `zifei` + `ZIFEI_API_KEY`。换网关时记得两边一起改。
386
+
387
+ **v0.6 布局**(`docs/DESIGN.md` §16):工作台从三栏变两栏,删掉左栏「图册树」与
388
+ 「概念索引」;图的选择改成图片上方 **35px 的纯文字图名 tab**(v0.7 把 91px 的缩略图条又压掉了,
389
+ 不再有额外 iframe),「新建图」是条尾的虚线按钮。
390
+ 查看器宽度从 ≈566px 增到 784px(1440 窗口)。组件测试里断言了「没有 `.dap-rail__group`」,
391
+ 别把左栏加回来。
392
+
393
+ **内嵌查看器隐藏成品页头**:archify 成品 HTML 顶部有一行
394
+ `<项目名> · <图名>` 大字 + `pulse-dot`,而插件自己的查看器标题栏和帧头已经写了图名、
395
+ 分支与时间。`page.tsx` 的 `hideArtifactHeader()` 在 iframe `onLoad` 时往同源文档 head
396
+ 注入一条 `.header{display:none!important}`,只影响嵌入视图;点「新窗口」仍是成品原样。
397
+ 同源被沙箱挡住就静默跳过,不影响看图。
@@ -0,0 +1,107 @@
1
+ # 发布到 npm(首次 + 之后自动)
2
+
3
+ > 仓库自带的 `~/.npmrc` 指向 `registry.npmmirror.com`(国内镜像)。**镜像是只读的,发不了包。**
4
+ > 下面每条命令都显式带 `--registry=https://registry.npmjs.org`,别省。
5
+
6
+ ## 0. 前置
7
+
8
+ - npm 账号:<https://www.npmjs.com/signup>(用户名、邮箱、密码)
9
+ - **邮箱必须验证**,否则 `npm publish` 会 403
10
+ - 建议开 2FA(App 或安全密钥);npm 会对发布要求 OTP
11
+ - 包名 `dsh-archify-projects` 在 npm 上**当前未被占用**(查过 registry,404)
12
+
13
+ ## 1. 本机登录
14
+
15
+ ```bash
16
+ npm login --registry=https://registry.npmjs.org
17
+ # 浏览器会弹一次授权页;或用 npm login --auth-type=legacy 走用户名/邮箱/密码
18
+ npm whoami --registry=https://registry.npmjs.org # 应该打印你的用户名
19
+ ```
20
+
21
+ 登录成功后 `~/.npmrc` 会多出一行 `//registry.npmjs.org/:_authToken=npm_xxx`。
22
+ **这一行就是要放进 GitHub Secret 的值。**
23
+
24
+ ## 2. 首次发布
25
+
26
+ ```bash
27
+ cd dsh-archify-projects
28
+ pnpm verify # 必须是绿的
29
+ npm publish --access public --registry=https://registry.npmjs.org
30
+ ```
31
+
32
+ `prepublishOnly` 会自动 `pnpm build`,所以发出去的包里有 `lib/` 与 `dist/client.js`。
33
+ 发完自检:
34
+
35
+ ```bash
36
+ npm view dsh-archify-projects version --registry=https://registry.npmjs.org
37
+ npm view dsh-archify-projects dist.tarball --registry=https://registry.npmjs.org
38
+ ```
39
+
40
+ ## 3. 之后交给 CI 自动发
41
+
42
+ 1. GitHub 仓库 → **Settings → Secrets and variables → Actions → New repository secret**
43
+ 2. Name: `NPM_TOKEN`;Secret: 上面那行 `npm_xxx` 的 token 值(不含 `//registry.npmjs.org/:_authToken=` 前缀)
44
+ 3. 之后打标签即自动发:
45
+
46
+ ```bash
47
+ npm version patch --no-git-tag-version
48
+ git commit -am "chore: release v0.1.1"
49
+ git tag v0.1.1 && git push origin main --tags
50
+ ```
51
+
52
+ `.github/workflows/release.yml` 会先建 GitHub Release(始终),再因为 `NPM_TOKEN`
53
+ 存在而跑 `npm publish`。没配 secret 时这一步跳过,其它照常。
54
+
55
+ ## 3.5 发布被 2FA 挡住(E403)
56
+
57
+ npm 现在不接受「网页登录换来的 session token」直接发版,两条路:
58
+
59
+ **A. 用验证器的一次性码**
60
+
61
+ ```bash
62
+ NPM_OTP=123456 ./scripts/npm-onboard.sh
63
+ # 或直接
64
+ npm publish --access public --registry=https://registry.npmjs.org --otp=123456
65
+ ```
66
+
67
+ **B. 建 Bypass 2FA 的 granular token(CI 也用它,推荐)**
68
+
69
+ 1. <https://www.npmjs.com/settings/xirizhi/tokens> → **Generate New Token** → **Granular Access Token**
70
+ 2. Permissions:**Read and write**
71
+ 3. Packages and scopes:**All packages**(首次发布时包还不存在,只能选全部)
72
+ 4. **勾上 `Bypass two-factor authentication`**(这个开关默认关闭,不勾等于白建)
73
+ 5. 生成后先验证开关真的生效(**最容易踩的坑**):
74
+
75
+ ```bash
76
+ TOKEN=$(sed -n 's#^//registry.npmjs.org/:_authToken=##p' ~/.npmrc | tail -1)
77
+ curl -s -H "Authorization: Bearer $TOKEN" https://registry.npmjs.org/-/npm/v1/tokens \
78
+ | python3 -c 'import json,sys; [print(t["name"], "bypass_2fa=", t["bypass_2fa"]) for t in json.load(sys.stdin)["objects"]]'
79
+ ```
80
+
81
+ 必须看到 `bypass_2fa= True`。显示 `False` 说明勾选框没保存,得重建。
82
+
83
+ 6. 生成后:
84
+
85
+ ```bash
86
+ printf '//registry.npmjs.org/:_authToken=%s\n' 'npm_你的token' >> ~/.npmrc
87
+ npm publish --access public --registry=https://registry.npmjs.org
88
+ ```
89
+
90
+ > 注意:`Bypass 2FA` 会覆盖账号级/包级 2FA 设置,等同把发布权限交给这个 token。
91
+ > 给它设一个合理有效期,别设永不过期。
92
+
93
+ ## 4. 版本号与 tag 的关系
94
+
95
+ - `package.json` 的 `version` 是唯一真相;
96
+ - 标签必须是 `v<version>`,否则 release workflow 直接失败;
97
+ - npm 不允许覆盖已发布版本:改代码后必须 bump version 才能再发。
98
+
99
+ ## 5. 常见错误
100
+
101
+ | 报错 | 原因 | 处理 |
102
+ | --------------------------------------------------------------- | ------------- | ---------------------------------------------- |
103
+ | `403 You must verify your email` | 邮箱没验证 | 去 npm 邮箱点验证链接 |
104
+ | `403 You cannot publish over the previously published versions` | 版本号没 bump | 改 `version` 再发 |
105
+ | `EOTP` / 要求一次性密码 | 账号开了 2FA | 命令后加 `--otp=123456` |
106
+ | `404 Not Found - PUT ...` | 打到了镜像源 | 显式加 `--registry=https://registry.npmjs.org` |
107
+ | `402 Payment Required` | 想发私有包 | 公开包必须 `--access public` |
package/icon.svg ADDED
@@ -0,0 +1,12 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" role="img" aria-label="Archify Projects">
2
+ <!-- 画布一角:代表「从代码库导出的图谱画布」 -->
3
+ <path d="M3.5 6.2V5.1c0-.88.72-1.6 1.6-1.6h3.1" opacity=".55" />
4
+ <path d="M20.5 17.8v1.1c0 .88-.72 1.6-1.6 1.6h-3.1" opacity=".55" />
5
+ <!-- 图谱骨干:三个节点 + 两条边 -->
6
+ <circle cx="7.6" cy="9.4" r="2.15" />
7
+ <circle cx="16.4" cy="7.1" r="1.75" />
8
+ <circle cx="12.6" cy="16.6" r="1.75" />
9
+ <path d="M9.5 8.3l5.1-1" />
10
+ <path d="M8.6 11.3l2.9 3.6" />
11
+ <path d="M15.9 8.8l-2.2 6" opacity=".7" />
12
+ </svg>
@@ -0,0 +1,116 @@
1
+ /**
2
+ * 定位 archify Skill 的磁盘目录。
3
+ *
4
+ * 插件不认识 archify 的内部实现,只需要 SKILL.md 与 bin/archify.mjs 的所在目录:
5
+ * - preset 侧的 skill 提供方按这个目录提供唯一一个 skill;
6
+ * - 提示词里把 `node <dir>/bin/archify.mjs` 写成绝对路径交给 agent。
7
+ *
8
+ * 探测顺序(谁先命中用谁):
9
+ * 1. 配置项 `archifySkillDir`;
10
+ * 2. `@tt-a1i/archify-dsh` 包里的 `skills/archify`(随 profile 安装,最常见);
11
+ * 3. 本插件自带的 `skills/archify`(离线兜底)。
12
+ *
13
+ * 全部失败返回 null —— 上层据此走「降级产物」路径,而不是让运行直接失败。
14
+ */
15
+ import { createRequire } from 'node:module';
16
+ import { access } from 'node:fs/promises';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+ /** 本插件的包根(由本文件位置反推:lib/host/*.js → 上两级)。 */
20
+ function packageRoot() {
21
+ return path.resolve(fileURLToPath(new URL('../../', import.meta.url)));
22
+ }
23
+ /** 一个目录是不是可用的 archify skill 根(有 SKILL.md 就算)。 */
24
+ async function hasSkillFile(dir) {
25
+ try {
26
+ await access(path.join(dir, 'SKILL.md'));
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ /** 从若干包解析基准里找一个能解析 `@tt-a1i/archify-dsh/package.json` 的。 */
34
+ function resolveFromBases(specifier, bases) {
35
+ for (const base of bases) {
36
+ try {
37
+ const require = createRequire(path.join(base, 'noop.js'));
38
+ return require.resolve(specifier);
39
+ }
40
+ catch {
41
+ // 这个基准下解析不到,换下一个
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ /**
47
+ * 解析 archify skill 目录。
48
+ *
49
+ * @param explicit 配置里显式给的目录(可为 null)
50
+ * @returns 绝对路径或 null
51
+ */
52
+ export async function resolveArchifySkillDir(explicit) {
53
+ if (explicit !== null) {
54
+ return (await hasSkillFile(explicit)) ? explicit : null;
55
+ }
56
+ const bases = [
57
+ packageRoot(),
58
+ process.env.DSH_PROFILE_DIR ?? '',
59
+ process.env.DSH_HOME ?? '',
60
+ process.cwd(),
61
+ ].filter((b) => b.length > 0);
62
+ const pkgJson = resolveFromBases('@tt-a1i/archify-dsh/package.json', bases);
63
+ if (pkgJson !== null) {
64
+ const candidate = path.join(path.dirname(pkgJson), 'skills', 'archify');
65
+ if (await hasSkillFile(candidate))
66
+ return candidate;
67
+ }
68
+ // 有些安装把 skill 平铺在包根下
69
+ if (pkgJson !== null) {
70
+ const pkgDir = path.dirname(pkgJson);
71
+ if (await hasSkillFile(pkgDir))
72
+ return pkgDir;
73
+ }
74
+ const bundled = path.join(packageRoot(), 'skills', 'archify');
75
+ if (await hasSkillFile(bundled))
76
+ return bundled;
77
+ return null;
78
+ }
79
+ /** 读 SKILL.md 正文(skill 提供方用)。 */
80
+ export async function readSkillBody(skillDir) {
81
+ const { readFile } = await import('node:fs/promises');
82
+ return readFile(path.join(skillDir, 'SKILL.md'), 'utf8');
83
+ }
84
+ /**
85
+ * 从 SKILL.md 的 YAML frontmatter 里取 `name` 与 `description`。
86
+ *
87
+ * 只支持单行标量与 `|`/`>` 折叠块 —— archify 的 frontmatter 就这两种写法,
88
+ * 上完整 YAML 解析器不值得。
89
+ */
90
+ export function parseFrontmatter(source) {
91
+ const fallback = { name: 'archify', description: 'Archify diagram authoring' };
92
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source);
93
+ if (match === null)
94
+ return fallback;
95
+ const body = match[1] ?? '';
96
+ const name = readScalar(body, 'name') ?? fallback.name;
97
+ const description = readScalar(body, 'description') ?? fallback.description;
98
+ return { name, description };
99
+ }
100
+ function readScalar(frontmatter, key) {
101
+ const quoted = new RegExp(`^${key}\\s*:\\s*["'](.+?)["']\\s*$`, 'm').exec(frontmatter);
102
+ if (quoted !== null)
103
+ return quoted[1] ?? null;
104
+ const block = new RegExp(`^${key}\\s*:\\s*[|>]-?\\s*\\r?\\n((?:[ \\t]+.*\\r?\\n?)+)`, 'm').exec(frontmatter);
105
+ if (block !== null) {
106
+ const text = (block[1] ?? '')
107
+ .split(/\r?\n/)
108
+ .map((line) => line.trim())
109
+ .filter((line) => line.length > 0)
110
+ .join(' ');
111
+ return text.length > 0 ? text : null;
112
+ }
113
+ const plain = new RegExp(`^${key}\\s*:\\s*(.+?)\\s*$`, 'm').exec(frontmatter);
114
+ return plain === null ? null : (plain[1] ?? null);
115
+ }
116
+ //# sourceMappingURL=archify-skill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archify-skill.js","sourceRoot":"","sources":["../../src/host/archify-skill.ts"],"names":[],"mappings":"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"}