@joekytc/dsh-swarm 0.1.2 → 0.2.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 (567) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +3 -3
  3. package/client/BoardCard.tsx +24 -2
  4. package/client/ConfigSection.tsx +71 -0
  5. package/client/ConfigSelect.tsx +69 -0
  6. package/client/KanbanBoard.tsx +3 -0
  7. package/client/KanbanTab.tsx +4 -3
  8. package/client/TaskDrawer.tsx +2 -26
  9. package/client/WorkflowRail.tsx +3 -1
  10. package/client/config-store.ts +51 -0
  11. package/client/config.css +110 -0
  12. package/client/index.ts +39 -4
  13. package/client/kanban.css +19 -2
  14. package/client/session-bridge.ts +39 -0
  15. package/client/timeline-model.ts +10 -1
  16. package/client/workflow-model.ts +3 -1
  17. package/lib/client.js +358 -53
  18. package/lib/config.d.ts +8 -1
  19. package/lib/config.js +6 -1
  20. package/lib/dispatcher/agent-runner.d.ts +28 -6
  21. package/lib/dispatcher/agent-runner.js +175 -62
  22. package/lib/dispatcher/chain-auditor.js +4 -1
  23. package/lib/dispatcher/dispatcher.d.ts +62 -3
  24. package/lib/dispatcher/dispatcher.js +243 -20
  25. package/lib/dispatcher/session-events.d.ts +11 -15
  26. package/lib/dispatcher/session-events.js +17 -5
  27. package/lib/dispatcher/v-orchestrator.d.ts +42 -9
  28. package/lib/dispatcher/v-orchestrator.js +236 -28
  29. package/lib/domain/config-override.d.ts +38 -0
  30. package/lib/domain/config-override.js +101 -0
  31. package/lib/domain/delivery-contract.d.ts +3 -2
  32. package/lib/domain/delivery-contract.js +28 -16
  33. package/lib/domain/gate-policy.d.ts +26 -0
  34. package/lib/domain/gate-policy.js +44 -0
  35. package/lib/domain/kanban-service.d.ts +16 -3
  36. package/lib/domain/kanban-service.js +41 -6
  37. package/lib/domain/permissions.js +1 -1
  38. package/lib/domain/planning-checklist.d.ts +5 -0
  39. package/lib/domain/planning-checklist.js +80 -6
  40. package/lib/domain/projection.js +2 -1
  41. package/lib/domain/review-evidence.js +1 -1
  42. package/lib/domain/state-machine.js +2 -1
  43. package/lib/domain/types.d.ts +2 -2
  44. package/lib/index.js +19 -21
  45. package/lib/roles/clean-fs-tools.d.ts +22 -0
  46. package/lib/roles/clean-fs-tools.js +118 -0
  47. package/lib/roles/skill-installer.d.ts +6 -0
  48. package/lib/roles/skill-installer.js +32 -0
  49. package/lib/roles/toolsets.d.ts +13 -1
  50. package/lib/roles/toolsets.js +163 -31
  51. package/lib/roles/wiki-worker.d.ts +1 -0
  52. package/lib/roles/wiki-worker.js +10 -2
  53. package/lib/routes/kanban-http.d.ts +5 -2
  54. package/lib/routes/kanban-http.js +36 -3
  55. package/lib/routes/planning-driver.d.ts +3 -1
  56. package/lib/routes/planning-driver.js +10 -6
  57. package/lib/routes/prefix-router.d.ts +16 -0
  58. package/lib/routes/prefix-router.js +37 -2
  59. package/lib/services/config-provider.d.ts +42 -0
  60. package/lib/services/config-provider.js +94 -0
  61. package/lib/services/gate-runner.d.ts +24 -0
  62. package/lib/services/gate-runner.js +87 -0
  63. package/lib/services/kanban-provider.d.ts +5 -1
  64. package/lib/services/kanban-provider.js +40 -2
  65. package/lib/services/llm-catalog.d.ts +35 -0
  66. package/lib/services/llm-catalog.js +19 -0
  67. package/lib/tools/kanban-tools.js +1 -0
  68. package/lib/tools/main-session-tools.d.ts +17 -2
  69. package/lib/tools/main-session-tools.js +88 -21
  70. package/lib/tools/planning-tools.d.ts +4 -0
  71. package/lib/tools/planning-tools.js +39 -12
  72. package/lib/tools/prefetch-tools.js +1 -0
  73. package/lib/tools/spec-card-tools.js +1 -0
  74. package/lib/tools/wiki-tools.js +5 -4
  75. package/lib/wiki/kb-linkage.d.ts +9 -2
  76. package/lib/wiki/kb-linkage.js +3 -3
  77. package/lib/wiki/local-kb-client.d.ts +14 -0
  78. package/lib/wiki/local-kb-client.js +101 -0
  79. package/lib/wiki/local-kb.d.ts +6 -0
  80. package/lib/wiki/local-kb.js +25 -0
  81. package/lib/wiki/memory-recall.d.ts +14 -6
  82. package/lib/wiki/memory-recall.js +17 -13
  83. package/lib/wiki/page-path.d.ts +9 -3
  84. package/lib/wiki/page-path.js +29 -11
  85. package/lib/wiki/wiki-vault-client.d.ts +3 -3
  86. package/lib/wiki/wiki-vault-client.js +6 -6
  87. package/package.json +16 -11
  88. package/personas/kanban-d/preset.yml +1 -1
  89. package/personas/kanban-dt/agent.cordis.yml +2 -2
  90. package/personas/kanban-dt/preset.yml +1 -1
  91. package/personas/kanban-p/agent.cordis.yml +16 -0
  92. package/personas/kanban-p/preset.yml +1 -1
  93. package/personas/kanban-pt/agent.cordis.yml +38 -8
  94. package/personas/kanban-pt/preset.yml +1 -1
  95. package/personas/kanban-w/agent.cordis.yml +35 -16
  96. package/personas/kanban-w/preset.yml +1 -1
  97. package/personas/persona-dt.md +1 -1
  98. package/personas/persona-p.md +4 -1
  99. package/personas/persona-pt.md +13 -6
  100. package/personas/persona-w.md +11 -9
  101. package/skills/grill-me/SKILL.md +71 -0
  102. package/skills/llm-wiki-skill/.mise.toml +2 -0
  103. package/skills/llm-wiki-skill/.nvmrc +1 -0
  104. package/skills/llm-wiki-skill/AGENTS.md +173 -0
  105. package/skills/llm-wiki-skill/CHANGELOG.md +548 -0
  106. package/skills/llm-wiki-skill/CLAUDE.md +152 -0
  107. package/skills/llm-wiki-skill/HERMES.md +44 -0
  108. package/skills/llm-wiki-skill/README.en.md +261 -0
  109. package/skills/llm-wiki-skill/README.md +291 -0
  110. package/skills/llm-wiki-skill/SKILL.md +1133 -0
  111. package/skills/llm-wiki-skill/assets/graph-demo.gif +0 -0
  112. package/skills/llm-wiki-skill/assets/graph-preview.png +0 -0
  113. package/skills/llm-wiki-skill/deps/LICENSE-d3.txt +13 -0
  114. package/skills/llm-wiki-skill/deps/LICENSE-marked.txt +44 -0
  115. package/skills/llm-wiki-skill/deps/LICENSE-purify.txt +568 -0
  116. package/skills/llm-wiki-skill/deps/LICENSE-roughjs.txt +21 -0
  117. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/SKILL.md +261 -0
  118. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/references/config/first-time-setup.md +106 -0
  119. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/cdp.ts +179 -0
  120. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/constants.ts +13 -0
  121. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/defuddle-converter.ts +58 -0
  122. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/html-to-markdown.ts +135 -0
  123. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/legacy-converter.ts +629 -0
  124. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/main.ts +314 -0
  125. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/markdown-conversion-shared.ts +305 -0
  126. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/media-localizer.ts +317 -0
  127. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/package.json +14 -0
  128. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/paths.ts +29 -0
  129. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/package.json +9 -0
  130. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/src/index.test.ts +307 -0
  131. package/skills/llm-wiki-skill/deps/baoyu-url-to-markdown/scripts/vendor/baoyu-chrome-cdp/src/index.ts +523 -0
  132. package/skills/llm-wiki-skill/deps/d3.min.js +2 -0
  133. package/skills/llm-wiki-skill/deps/marked.min.js +6 -0
  134. package/skills/llm-wiki-skill/deps/purify.min.js +3 -0
  135. package/skills/llm-wiki-skill/deps/rough.min.js +7 -0
  136. package/skills/llm-wiki-skill/deps/youtube-transcript/SKILL.md +47 -0
  137. package/skills/llm-wiki-skill/deps/youtube-transcript/scripts/get_transcript.py +72 -0
  138. package/skills/llm-wiki-skill/designs/graph-edge-legibility/graph-edge-treatments.html +480 -0
  139. package/skills/llm-wiki-skill/docs/design/oriental-atlas/DESIGN.md +183 -0
  140. package/skills/llm-wiki-skill/docs/design/oriental-atlas/README.md +10 -0
  141. package/skills/llm-wiki-skill/docs/design/oriental-atlas/design-brief.md +170 -0
  142. package/skills/llm-wiki-skill/docs/design/oriental-atlas/oriental-editorial-atlas.html +1908 -0
  143. package/skills/llm-wiki-skill/docs/design/oriental-atlas/reviews/result-review.md +68 -0
  144. package/skills/llm-wiki-skill/docs/graph/2026-06-16-interaction-risk-audit.md +88 -0
  145. package/skills/llm-wiki-skill/docs/graph/2026-06-16-interaction-test-coverage-map.md +212 -0
  146. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-0-2-baseline.md +86 -0
  147. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-1-3-dom-svg-large-baseline.md +85 -0
  148. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-1-4-disposable-validation-gate.md +97 -0
  149. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-18-phase-2-4-global-renderer-trial-matrix.md +168 -0
  150. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-1-sigma-graphology-trial.md +71 -0
  151. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-2-vis-network-trial.md +78 -0
  152. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-3-aggregation-fallback-trial.md +72 -0
  153. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-phase-6-4-global-renderer-route-decision.md +94 -0
  154. package/skills/llm-wiki-skill/docs/graph/performance/2026-06-19-sigma-global-production-integration-result.md +102 -0
  155. package/skills/llm-wiki-skill/docs/graph/performance/artifacts/2026-06-18-phase-0-2/stage-4.5-offline-dense-wheel.json +9 -0
  156. package/skills/llm-wiki-skill/docs/graph/performance/artifacts/2026-06-18-phase-0-2/stage-4.5-offline-dense-wheel.png +0 -0
  157. package/skills/llm-wiki-skill/docs/graph/performance/artifacts/2026-06-18-phase-0-2/stage-4.5-offline-navigation.png +0 -0
  158. package/skills/llm-wiki-skill/docs/spark/2026-06-15-omp-tool-status-events-design.md +174 -0
  159. package/skills/llm-wiki-skill/docs/spark/2026-06-16-graph-interaction-geometry-design.md +832 -0
  160. package/skills/llm-wiki-skill/docs/spark/2026-06-16-graph-six-layer-architecture-design.md +577 -0
  161. package/skills/llm-wiki-skill/docs/spark/2026-06-17-static-renderer-coordination-split-design.md +172 -0
  162. package/skills/llm-wiki-skill/docs/spark/2026-06-18-large-graph-global-community-design.md +909 -0
  163. package/skills/llm-wiki-skill/docs/spark/2026-06-19-sigma-global-graph-renderer-design.md +360 -0
  164. package/skills/llm-wiki-skill/docs/spark/2026-06-20-paper-ui-port-design.md +162 -0
  165. package/skills/llm-wiki-skill/docs/spark/2026-06-20-paper-ui-v2-shell-alignment-design.md +384 -0
  166. package/skills/llm-wiki-skill/docs/spark/2026-06-21-graph-experience-completion-design.md +534 -0
  167. package/skills/llm-wiki-skill/docs/spark/2026-06-22-chat-auto-scroll-design.md +185 -0
  168. package/skills/llm-wiki-skill/docs/spark/2026-06-22-graph-community-node-visual-design.md +266 -0
  169. package/skills/llm-wiki-skill/docs/spark/2026-06-25-sigma-overlay-refactor-perf-design.md +170 -0
  170. package/skills/llm-wiki-skill/docs/spark/2026-06-26-global-sigma-community-spotlight-design.md +393 -0
  171. package/skills/llm-wiki-skill/docs/spark/mockups/2026-06-18-large-graph-global-community-flow.html +746 -0
  172. package/skills/llm-wiki-skill/install.ps1 +87 -0
  173. package/skills/llm-wiki-skill/install.sh +722 -0
  174. package/skills/llm-wiki-skill/package-lock.json +11171 -0
  175. package/skills/llm-wiki-skill/package.json +24 -0
  176. package/skills/llm-wiki-skill/packages/graph-engine/package.json +37 -0
  177. package/skills/llm-wiki-skill/packages/graph-engine/src/anim/.gitkeep +1 -0
  178. package/skills/llm-wiki-skill/packages/graph-engine/src/anim/index.ts +179 -0
  179. package/skills/llm-wiki-skill/packages/graph-engine/src/architecture.ts +79 -0
  180. package/skills/llm-wiki-skill/packages/graph-engine/src/diff.ts +121 -0
  181. package/skills/llm-wiki-skill/packages/graph-engine/src/facade.ts +1406 -0
  182. package/skills/llm-wiki-skill/packages/graph-engine/src/graph-node.ts +37 -0
  183. package/skills/llm-wiki-skill/packages/graph-engine/src/index.ts +25 -0
  184. package/skills/llm-wiki-skill/packages/graph-engine/src/layout/edge-geometry.ts +17 -0
  185. package/skills/llm-wiki-skill/packages/graph-engine/src/layout/index.ts +16 -0
  186. package/skills/llm-wiki-skill/packages/graph-engine/src/layout/spatial-index.ts +509 -0
  187. package/skills/llm-wiki-skill/packages/graph-engine/src/model/atlas.ts +18 -0
  188. package/skills/llm-wiki-skill/packages/graph-engine/src/model/index.ts +10 -0
  189. package/skills/llm-wiki-skill/packages/graph-engine/src/model/labels.ts +11 -0
  190. package/skills/llm-wiki-skill/packages/graph-engine/src/model/layout.ts +81 -0
  191. package/skills/llm-wiki-skill/packages/graph-engine/src/model/learning.ts +8 -0
  192. package/skills/llm-wiki-skill/packages/graph-engine/src/model/legacy-helpers.ts +1379 -0
  193. package/skills/llm-wiki-skill/packages/graph-engine/src/model/queue.ts +7 -0
  194. package/skills/llm-wiki-skill/packages/graph-engine/src/model/storage.ts +4 -0
  195. package/skills/llm-wiki-skill/packages/graph-engine/src/model/visibility.ts +9 -0
  196. package/skills/llm-wiki-skill/packages/graph-engine/src/render/.gitkeep +1 -0
  197. package/skills/llm-wiki-skill/packages/graph-engine/src/render/adapter.ts +443 -0
  198. package/skills/llm-wiki-skill/packages/graph-engine/src/render/aggregation-containers.ts +64 -0
  199. package/skills/llm-wiki-skill/packages/graph-engine/src/render/community-cloud-geometry.ts +178 -0
  200. package/skills/llm-wiki-skill/packages/graph-engine/src/render/community-wash.ts +238 -0
  201. package/skills/llm-wiki-skill/packages/graph-engine/src/render/community-washes.ts +19 -0
  202. package/skills/llm-wiki-skill/packages/graph-engine/src/render/compat-contract.ts +14 -0
  203. package/skills/llm-wiki-skill/packages/graph-engine/src/render/controller.ts +705 -0
  204. package/skills/llm-wiki-skill/packages/graph-engine/src/render/controls.ts +288 -0
  205. package/skills/llm-wiki-skill/packages/graph-engine/src/render/dom-svg-renderer.ts +180 -0
  206. package/skills/llm-wiki-skill/packages/graph-engine/src/render/edges.ts +51 -0
  207. package/skills/llm-wiki-skill/packages/graph-engine/src/render/geometry.ts +380 -0
  208. package/skills/llm-wiki-skill/packages/graph-engine/src/render/gestures.ts +673 -0
  209. package/skills/llm-wiki-skill/packages/graph-engine/src/render/graph-renderer-root.ts +387 -0
  210. package/skills/llm-wiki-skill/packages/graph-engine/src/render/hit-testing.ts +56 -0
  211. package/skills/llm-wiki-skill/packages/graph-engine/src/render/host-dom.ts +14 -0
  212. package/skills/llm-wiki-skill/packages/graph-engine/src/render/hover-card.ts +37 -0
  213. package/skills/llm-wiki-skill/packages/graph-engine/src/render/index.ts +213 -0
  214. package/skills/llm-wiki-skill/packages/graph-engine/src/render/keyboard.ts +48 -0
  215. package/skills/llm-wiki-skill/packages/graph-engine/src/render/legend.ts +25 -0
  216. package/skills/llm-wiki-skill/packages/graph-engine/src/render/minimap.ts +45 -0
  217. package/skills/llm-wiki-skill/packages/graph-engine/src/render/model.ts +1339 -0
  218. package/skills/llm-wiki-skill/packages/graph-engine/src/render/node-drag-lifecycle.ts +94 -0
  219. package/skills/llm-wiki-skill/packages/graph-engine/src/render/nodes.ts +103 -0
  220. package/skills/llm-wiki-skill/packages/graph-engine/src/render/offline-reader.ts +209 -0
  221. package/skills/llm-wiki-skill/packages/graph-engine/src/render/overlays-presenter.ts +192 -0
  222. package/skills/llm-wiki-skill/packages/graph-engine/src/render/overlays.ts +73 -0
  223. package/skills/llm-wiki-skill/packages/graph-engine/src/render/pin-position.ts +51 -0
  224. package/skills/llm-wiki-skill/packages/graph-engine/src/render/preview.ts +65 -0
  225. package/skills/llm-wiki-skill/packages/graph-engine/src/render/relation-focus.ts +91 -0
  226. package/skills/llm-wiki-skill/packages/graph-engine/src/render/render-context.ts +85 -0
  227. package/skills/llm-wiki-skill/packages/graph-engine/src/render/render-pipeline.ts +871 -0
  228. package/skills/llm-wiki-skill/packages/graph-engine/src/render/render-styles.ts +1405 -0
  229. package/skills/llm-wiki-skill/packages/graph-engine/src/render/renderer-surface.ts +75 -0
  230. package/skills/llm-wiki-skill/packages/graph-engine/src/render/search.ts +57 -0
  231. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-coordinates.ts +44 -0
  232. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-global-drag.ts +246 -0
  233. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-global-renderer.ts +1425 -0
  234. package/skills/llm-wiki-skill/packages/graph-engine/src/render/sigma-overlay-svg.ts +121 -0
  235. package/skills/llm-wiki-skill/packages/graph-engine/src/render/simulation-bridge.ts +59 -0
  236. package/skills/llm-wiki-skill/packages/graph-engine/src/render/state.ts +262 -0
  237. package/skills/llm-wiki-skill/packages/graph-engine/src/render/toolbar.ts +40 -0
  238. package/skills/llm-wiki-skill/packages/graph-engine/src/render/viewport.ts +336 -0
  239. package/skills/llm-wiki-skill/packages/graph-engine/src/select/.gitkeep +1 -0
  240. package/skills/llm-wiki-skill/packages/graph-engine/src/select/index.ts +189 -0
  241. package/skills/llm-wiki-skill/packages/graph-engine/src/sim/index.ts +372 -0
  242. package/skills/llm-wiki-skill/packages/graph-engine/src/sim/pins.ts +84 -0
  243. package/skills/llm-wiki-skill/packages/graph-engine/src/summary/index.ts +447 -0
  244. package/skills/llm-wiki-skill/packages/graph-engine/src/themes/.gitkeep +1 -0
  245. package/skills/llm-wiki-skill/packages/graph-engine/src/themes/index.ts +8 -0
  246. package/skills/llm-wiki-skill/packages/graph-engine/src/themes/tokens.ts +117 -0
  247. package/skills/llm-wiki-skill/packages/graph-engine/src/types/d3-quadtree.d.ts +22 -0
  248. package/skills/llm-wiki-skill/packages/graph-engine/src/types.ts +531 -0
  249. package/skills/llm-wiki-skill/packages/graph-engine/test/aggregation-fallback-trial-adapter.test.ts +115 -0
  250. package/skills/llm-wiki-skill/packages/graph-engine/test/aggregation-fallback-trial-adapter.ts +222 -0
  251. package/skills/llm-wiki-skill/packages/graph-engine/test/anim.test.ts +121 -0
  252. package/skills/llm-wiki-skill/packages/graph-engine/test/architecture.test.ts +63 -0
  253. package/skills/llm-wiki-skill/packages/graph-engine/test/community-cloud-geometry.test.ts +86 -0
  254. package/skills/llm-wiki-skill/packages/graph-engine/test/community-wash.test.ts +97 -0
  255. package/skills/llm-wiki-skill/packages/graph-engine/test/diff.test.ts +133 -0
  256. package/skills/llm-wiki-skill/packages/graph-engine/test/facade.test.ts +1369 -0
  257. package/skills/llm-wiki-skill/packages/graph-engine/test/geometry.test.ts +147 -0
  258. package/skills/llm-wiki-skill/packages/graph-engine/test/gestures.test.ts +768 -0
  259. package/skills/llm-wiki-skill/packages/graph-engine/test/graph-layout.test.ts +110 -0
  260. package/skills/llm-wiki-skill/packages/graph-engine/test/helpers.test.ts +287 -0
  261. package/skills/llm-wiki-skill/packages/graph-engine/test/interaction-contract.test.ts +106 -0
  262. package/skills/llm-wiki-skill/packages/graph-engine/test/keyboard.test.ts +59 -0
  263. package/skills/llm-wiki-skill/packages/graph-engine/test/large-fixtures.test.ts +47 -0
  264. package/skills/llm-wiki-skill/packages/graph-engine/test/large-graph-fixtures.ts +420 -0
  265. package/skills/llm-wiki-skill/packages/graph-engine/test/learning.test.ts +259 -0
  266. package/skills/llm-wiki-skill/packages/graph-engine/test/node-drag-lifecycle.test.ts +166 -0
  267. package/skills/llm-wiki-skill/packages/graph-engine/test/overlays.test.ts +115 -0
  268. package/skills/llm-wiki-skill/packages/graph-engine/test/preview.test.ts +56 -0
  269. package/skills/llm-wiki-skill/packages/graph-engine/test/queue.test.ts +114 -0
  270. package/skills/llm-wiki-skill/packages/graph-engine/test/relation-focus.test.ts +115 -0
  271. package/skills/llm-wiki-skill/packages/graph-engine/test/render-model.test.ts +1030 -0
  272. package/skills/llm-wiki-skill/packages/graph-engine/test/renderer-adapter-contract.test.ts +271 -0
  273. package/skills/llm-wiki-skill/packages/graph-engine/test/renderer-boundary.test.ts +618 -0
  274. package/skills/llm-wiki-skill/packages/graph-engine/test/renderer-lifecycle.test.ts +1711 -0
  275. package/skills/llm-wiki-skill/packages/graph-engine/test/route-continuity.test.ts +284 -0
  276. package/skills/llm-wiki-skill/packages/graph-engine/test/runtime-state.test.ts +281 -0
  277. package/skills/llm-wiki-skill/packages/graph-engine/test/search-and-legend.test.ts +124 -0
  278. package/skills/llm-wiki-skill/packages/graph-engine/test/select.test.ts +168 -0
  279. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-coordinates.test.ts +50 -0
  280. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-global-renderer.test.ts +2210 -0
  281. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-trial-adapter.test.ts +82 -0
  282. package/skills/llm-wiki-skill/packages/graph-engine/test/sigma-trial-adapter.ts +271 -0
  283. package/skills/llm-wiki-skill/packages/graph-engine/test/sim.test.ts +207 -0
  284. package/skills/llm-wiki-skill/packages/graph-engine/test/simulation-bridge.test.ts +96 -0
  285. package/skills/llm-wiki-skill/packages/graph-engine/test/spatial-index.test.ts +222 -0
  286. package/skills/llm-wiki-skill/packages/graph-engine/test/state.test.ts +189 -0
  287. package/skills/llm-wiki-skill/packages/graph-engine/test/summary-contract.test.ts +422 -0
  288. package/skills/llm-wiki-skill/packages/graph-engine/test/themes.test.ts +62 -0
  289. package/skills/llm-wiki-skill/packages/graph-engine/test/toolbar.test.ts +64 -0
  290. package/skills/llm-wiki-skill/packages/graph-engine/test/viewport.test.ts +213 -0
  291. package/skills/llm-wiki-skill/packages/graph-engine/test/vis-network-trial-adapter.test.ts +82 -0
  292. package/skills/llm-wiki-skill/packages/graph-engine/test/vis-network-trial-adapter.ts +186 -0
  293. package/skills/llm-wiki-skill/packages/graph-engine/tsconfig.json +17 -0
  294. package/skills/llm-wiki-skill/packages/graph-engine/vite.config.ts +15 -0
  295. package/skills/llm-wiki-skill/platforms/claude/CLAUDE.md +37 -0
  296. package/skills/llm-wiki-skill/platforms/claude/companions/llm-wiki-upgrade/SKILL.md +95 -0
  297. package/skills/llm-wiki-skill/platforms/codex/AGENTS.md +23 -0
  298. package/skills/llm-wiki-skill/platforms/hermes/README.md +31 -0
  299. package/skills/llm-wiki-skill/platforms/openclaw/README.md +31 -0
  300. package/skills/llm-wiki-skill/scripts/adapter-state.sh +424 -0
  301. package/skills/llm-wiki-skill/scripts/build-graph-data.sh +395 -0
  302. package/skills/llm-wiki-skill/scripts/build-graph-html.sh +459 -0
  303. package/skills/llm-wiki-skill/scripts/cache.sh +352 -0
  304. package/skills/llm-wiki-skill/scripts/create-source-page.sh +100 -0
  305. package/skills/llm-wiki-skill/scripts/delete-helper.sh +68 -0
  306. package/skills/llm-wiki-skill/scripts/graph-analysis.js +732 -0
  307. package/skills/llm-wiki-skill/scripts/hook-session-start.sh +46 -0
  308. package/skills/llm-wiki-skill/scripts/init-wiki.sh +95 -0
  309. package/skills/llm-wiki-skill/scripts/lib/source-signal-eligibility.js +158 -0
  310. package/skills/llm-wiki-skill/scripts/lint-fix.sh +114 -0
  311. package/skills/llm-wiki-skill/scripts/lint-runner.sh +217 -0
  312. package/skills/llm-wiki-skill/scripts/runtime-context.sh +77 -0
  313. package/skills/llm-wiki-skill/scripts/shared-config.sh +83 -0
  314. package/skills/llm-wiki-skill/scripts/source-record-contract.tsv +10 -0
  315. package/skills/llm-wiki-skill/scripts/source-registry.sh +349 -0
  316. package/skills/llm-wiki-skill/scripts/source-registry.tsv +10 -0
  317. package/skills/llm-wiki-skill/scripts/source-signal-coverage.js +83 -0
  318. package/skills/llm-wiki-skill/scripts/validate-step1.sh +144 -0
  319. package/skills/llm-wiki-skill/scripts/wiki-compat.sh +267 -0
  320. package/skills/llm-wiki-skill/setup.sh +8 -0
  321. package/skills/llm-wiki-skill/templates/entity-template.md +30 -0
  322. package/skills/llm-wiki-skill/templates/graph-styles/wash/footer.html +27 -0
  323. package/skills/llm-wiki-skill/templates/graph-styles/wash/graph-wash-helpers.js +1326 -0
  324. package/skills/llm-wiki-skill/templates/graph-styles/wash/graph-wash.js +1009 -0
  325. package/skills/llm-wiki-skill/templates/graph-styles/wash/header.html +2002 -0
  326. package/skills/llm-wiki-skill/templates/index-en-template.md +51 -0
  327. package/skills/llm-wiki-skill/templates/index-template.md +51 -0
  328. package/skills/llm-wiki-skill/templates/log-en-template.md +11 -0
  329. package/skills/llm-wiki-skill/templates/log-template.md +11 -0
  330. package/skills/llm-wiki-skill/templates/overview-en-template.md +41 -0
  331. package/skills/llm-wiki-skill/templates/overview-template.md +41 -0
  332. package/skills/llm-wiki-skill/templates/purpose-en-template.md +16 -0
  333. package/skills/llm-wiki-skill/templates/purpose-template.md +16 -0
  334. package/skills/llm-wiki-skill/templates/query-template.md +19 -0
  335. package/skills/llm-wiki-skill/templates/schema-template.md +185 -0
  336. package/skills/llm-wiki-skill/templates/source-template.md +51 -0
  337. package/skills/llm-wiki-skill/templates/synthesis-template.md +25 -0
  338. package/skills/llm-wiki-skill/templates/topic-template.md +35 -0
  339. package/skills/llm-wiki-skill/tests/adapter-state.sh +338 -0
  340. package/skills/llm-wiki-skill/tests/browser/graph-aggregation-fallback-trial.ts +737 -0
  341. package/skills/llm-wiki-skill/tests/browser/graph-community-node-map.mjs +255 -0
  342. package/skills/llm-wiki-skill/tests/browser/graph-community-wash-interactions.mjs +170 -0
  343. package/skills/llm-wiki-skill/tests/browser/graph-default-behavior.mjs +630 -0
  344. package/skills/llm-wiki-skill/tests/browser/graph-density-preview.mjs +53 -0
  345. package/skills/llm-wiki-skill/tests/browser/graph-html-insights.mjs +41 -0
  346. package/skills/llm-wiki-skill/tests/browser/graph-large-performance.ts +689 -0
  347. package/skills/llm-wiki-skill/tests/browser/graph-node-slim.mjs +75 -0
  348. package/skills/llm-wiki-skill/tests/browser/graph-offline-phase-6.mjs +660 -0
  349. package/skills/llm-wiki-skill/tests/browser/graph-renderer-trial-shared.ts +291 -0
  350. package/skills/llm-wiki-skill/tests/browser/graph-sigma-global-production.ts +1437 -0
  351. package/skills/llm-wiki-skill/tests/browser/graph-sigma-graphology-trial.ts +949 -0
  352. package/skills/llm-wiki-skill/tests/browser/graph-stage-4-5.mjs +815 -0
  353. package/skills/llm-wiki-skill/tests/browser/graph-vis-network-trial.ts +762 -0
  354. package/skills/llm-wiki-skill/tests/browser/graph-workbench-interactions.mjs +730 -0
  355. package/skills/llm-wiki-skill/tests/browser/validate-graph-trial-result.mjs +149 -0
  356. package/skills/llm-wiki-skill/tests/expected/graph-data-empty.json +60 -0
  357. package/skills/llm-wiki-skill/tests/expected/graph-data-sample.json +396 -0
  358. package/skills/llm-wiki-skill/tests/expected/graph-interactive-basic.html +1736 -0
  359. package/skills/llm-wiki-skill/tests/expected/lint-output.txt +44 -0
  360. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/index.md +1 -0
  361. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Alpha.md +10 -0
  362. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Beta.md +7 -0
  363. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Delta.md +8 -0
  364. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/entities/Gamma.md +8 -0
  365. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/queries/20260422-abc.md +10 -0
  366. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/synthesis/Crystal.md +8 -0
  367. package/skills/llm-wiki-skill/tests/fixtures/coverage-sample-wiki/wiki/topics/Overview.md +9 -0
  368. package/skills/llm-wiki-skill/tests/fixtures/graph-data-empty-wiki/wiki/entities/.gitkeep +0 -0
  369. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/purpose.md +3 -0
  370. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Attention.md +7 -0
  371. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Decoder.md +7 -0
  372. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Encoder.md +7 -0
  373. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/GPT.md +7 -0
  374. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/entities/Transformer.md +13 -0
  375. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/sources/paper.md +9 -0
  376. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/topics/arch.md +12 -0
  377. package/skills/llm-wiki-skill/tests/fixtures/graph-data-sample-wiki/wiki/topics/finetune.md +9 -0
  378. package/skills/llm-wiki-skill/tests/fixtures/graph-interactive-basic/wiki/graph-data.json +40 -0
  379. package/skills/llm-wiki-skill/tests/fixtures/graph-interactive-dense/wiki/graph-data.json +3965 -0
  380. package/skills/llm-wiki-skill/tests/fixtures/graph-interactive-multicomm/wiki/graph-data.json +212 -0
  381. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/index.md +14 -0
  382. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/C++.md +6 -0
  383. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/Lonely.md +4 -0
  384. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/Other.md +8 -0
  385. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/entities/Real.md +7 -0
  386. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/sources/2026-01-01-test-source.md +15 -0
  387. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/sources/2026-02-01-inline-img.md +12 -0
  388. package/skills/llm-wiki-skill/tests/fixtures/lint-sample-wiki/wiki/topics/AIbasic.md +3 -0
  389. package/skills/llm-wiki-skill/tests/graph-aggregation-fallback-trial.regression-1.sh +46 -0
  390. package/skills/llm-wiki-skill/tests/graph-analysis-helper.regression-1.sh +237 -0
  391. package/skills/llm-wiki-skill/tests/graph-browser-large-performance.regression-1.sh +39 -0
  392. package/skills/llm-wiki-skill/tests/graph-browser-stage-4-5.regression-1.sh +163 -0
  393. package/skills/llm-wiki-skill/tests/graph-build-failures.regression-1.sh +91 -0
  394. package/skills/llm-wiki-skill/tests/graph-community-node-map.regression-1.sh +107 -0
  395. package/skills/llm-wiki-skill/tests/graph-community-wash-interactions.regression-1.sh +35 -0
  396. package/skills/llm-wiki-skill/tests/graph-data-confidence-merge.regression-1.sh +309 -0
  397. package/skills/llm-wiki-skill/tests/graph-data-source-paths.regression-1.sh +49 -0
  398. package/skills/llm-wiki-skill/tests/graph-default-behavior.regression-1.sh +35 -0
  399. package/skills/llm-wiki-skill/tests/graph-html-a11y.regression-1.sh +50 -0
  400. package/skills/llm-wiki-skill/tests/graph-html-brand-link.regression-1.sh +47 -0
  401. package/skills/llm-wiki-skill/tests/graph-html-density.regression-1.sh +228 -0
  402. package/skills/llm-wiki-skill/tests/graph-html-drawer-neighbors.regression-1.sh +47 -0
  403. package/skills/llm-wiki-skill/tests/graph-html-insights.regression-1.sh +80 -0
  404. package/skills/llm-wiki-skill/tests/graph-html-long-label.regression-1.sh +92 -0
  405. package/skills/llm-wiki-skill/tests/graph-html-minimap.regression-1.sh +51 -0
  406. package/skills/llm-wiki-skill/tests/graph-html-mobile.regression-1.sh +48 -0
  407. package/skills/llm-wiki-skill/tests/graph-html-oriental-atlas-contract.regression-1.sh +94 -0
  408. package/skills/llm-wiki-skill/tests/graph-html-oriental-design-contract.regression-1.sh +82 -0
  409. package/skills/llm-wiki-skill/tests/graph-html-search.regression-1.sh +52 -0
  410. package/skills/llm-wiki-skill/tests/graph-html-styles.regression-1.sh +66 -0
  411. package/skills/llm-wiki-skill/tests/graph-html-toolbar.regression-1.sh +62 -0
  412. package/skills/llm-wiki-skill/tests/graph-offline-phase-6.regression-1.sh +51 -0
  413. package/skills/llm-wiki-skill/tests/graph-sigma-global-production.regression-1.sh +64 -0
  414. package/skills/llm-wiki-skill/tests/graph-sigma-graphology-trial.regression-1.sh +46 -0
  415. package/skills/llm-wiki-skill/tests/graph-vis-network-trial.regression-1.sh +46 -0
  416. package/skills/llm-wiki-skill/tests/graph-workbench-interactions.regression-1.sh +151 -0
  417. package/skills/llm-wiki-skill/tests/js/graph-wash-bootstrap.test.js +110 -0
  418. package/skills/llm-wiki-skill/tests/js/graph-wash-helpers.test.js +296 -0
  419. package/skills/llm-wiki-skill/tests/js/graph-wash-learning.test.js +260 -0
  420. package/skills/llm-wiki-skill/tests/js/graph-wash-queue.test.js +115 -0
  421. package/skills/llm-wiki-skill/tests/js/graph-wash-runtime-state.test.js +282 -0
  422. package/skills/llm-wiki-skill/tests/js/source-signal-coverage.test.js +48 -0
  423. package/skills/llm-wiki-skill/tests/js/source-signal-eligibility.test.js +202 -0
  424. package/skills/llm-wiki-skill/tests/lib/graph-html-engine-helpers.sh +110 -0
  425. package/skills/llm-wiki-skill/tests/lint-output.regression-1.sh +32 -0
  426. package/skills/llm-wiki-skill/tests/regression.sh +1754 -0
  427. package/skills/llm-wiki-skill/workbench/AGENTS.md +110 -0
  428. package/skills/llm-wiki-skill/workbench/CLAUDE.md +55 -0
  429. package/skills/llm-wiki-skill/workbench/LICENSE +21 -0
  430. package/skills/llm-wiki-skill/workbench/PRODUCT.md +1353 -0
  431. package/skills/llm-wiki-skill/workbench/README.md +30 -0
  432. package/skills/llm-wiki-skill/workbench/docs/current-kb-retrieval-design.md +587 -0
  433. package/skills/llm-wiki-skill/workbench/docs/graph-evolution-1-design.md +210 -0
  434. package/skills/llm-wiki-skill/workbench/docs/stage-2-design.md +1139 -0
  435. package/skills/llm-wiki-skill/workbench/docs/stage-3-design.md +1269 -0
  436. package/skills/llm-wiki-skill/workbench/docs/stage-3.5-design.md +1164 -0
  437. package/skills/llm-wiki-skill/workbench/docs/stage-4-design.md +504 -0
  438. package/skills/llm-wiki-skill/workbench/docs/stage-4.5-design.md +195 -0
  439. package/skills/llm-wiki-skill/workbench/docs/superpowers/specs/2026-05-28-resizable-preview-layout-design.md +165 -0
  440. package/skills/llm-wiki-skill/workbench/docs/superpowers/specs/2026-05-28-settings-panel-scroll-fix.md +50 -0
  441. package/skills/llm-wiki-skill/workbench/server/package.json +31 -0
  442. package/skills/llm-wiki-skill/workbench/server/src/agent.ts +457 -0
  443. package/skills/llm-wiki-skill/workbench/server/src/artifacts.ts +248 -0
  444. package/skills/llm-wiki-skill/workbench/server/src/auth.ts +164 -0
  445. package/skills/llm-wiki-skill/workbench/server/src/config.ts +130 -0
  446. package/skills/llm-wiki-skill/workbench/server/src/conversations.test.ts +159 -0
  447. package/skills/llm-wiki-skill/workbench/server/src/conversations.ts +270 -0
  448. package/skills/llm-wiki-skill/workbench/server/src/digest/batch.ts +189 -0
  449. package/skills/llm-wiki-skill/workbench/server/src/digest/concurrency.test.ts +36 -0
  450. package/skills/llm-wiki-skill/workbench/server/src/digest/concurrency.ts +31 -0
  451. package/skills/llm-wiki-skill/workbench/server/src/digest/subagent.ts +91 -0
  452. package/skills/llm-wiki-skill/workbench/server/src/extensions/artifacts.ts +81 -0
  453. package/skills/llm-wiki-skill/workbench/server/src/extensions/knowledge-base.ts +263 -0
  454. package/skills/llm-wiki-skill/workbench/server/src/extensions/new-wiki.ts +42 -0
  455. package/skills/llm-wiki-skill/workbench/server/src/extensions/synthesis.ts +172 -0
  456. package/skills/llm-wiki-skill/workbench/server/src/graph-watcher.test.ts +196 -0
  457. package/skills/llm-wiki-skill/workbench/server/src/graph.test.ts +113 -0
  458. package/skills/llm-wiki-skill/workbench/server/src/graph.ts +351 -0
  459. package/skills/llm-wiki-skill/workbench/server/src/index.ts +1148 -0
  460. package/skills/llm-wiki-skill/workbench/server/src/knowledge-bases.test.ts +30 -0
  461. package/skills/llm-wiki-skill/workbench/server/src/knowledge-bases.ts +321 -0
  462. package/skills/llm-wiki-skill/workbench/server/src/pages.test.ts +41 -0
  463. package/skills/llm-wiki-skill/workbench/server/src/pages.ts +160 -0
  464. package/skills/llm-wiki-skill/workbench/server/src/retrieval.test.ts +151 -0
  465. package/skills/llm-wiki-skill/workbench/server/src/retrieval.ts +427 -0
  466. package/skills/llm-wiki-skill/workbench/server/src/tool-status-events.test.ts +363 -0
  467. package/skills/llm-wiki-skill/workbench/server/src/tool-status-events.ts +616 -0
  468. package/skills/llm-wiki-skill/workbench/server/src/wiki-init.test.ts +19 -0
  469. package/skills/llm-wiki-skill/workbench/server/src/wiki-init.ts +188 -0
  470. package/skills/llm-wiki-skill/workbench/server/tsconfig.json +10 -0
  471. package/skills/llm-wiki-skill/workbench/tsconfig.json +20 -0
  472. package/skills/llm-wiki-skill/workbench/web/README.md +73 -0
  473. package/skills/llm-wiki-skill/workbench/web/components.json +21 -0
  474. package/skills/llm-wiki-skill/workbench/web/eslint.config.js +22 -0
  475. package/skills/llm-wiki-skill/workbench/web/index.html +16 -0
  476. package/skills/llm-wiki-skill/workbench/web/package.json +54 -0
  477. package/skills/llm-wiki-skill/workbench/web/src/App.tsx +1195 -0
  478. package/skills/llm-wiki-skill/workbench/web/src/components/AddExternalDialog.tsx +375 -0
  479. package/skills/llm-wiki-skill/workbench/web/src/components/AppearancePanel.tsx +158 -0
  480. package/skills/llm-wiki-skill/workbench/web/src/components/ArtifactView.tsx +15 -0
  481. package/skills/llm-wiki-skill/workbench/web/src/components/BatchDigestPanel.tsx +117 -0
  482. package/skills/llm-wiki-skill/workbench/web/src/components/ChatPanel.tsx +890 -0
  483. package/skills/llm-wiki-skill/workbench/web/src/components/CommandMenu.tsx +75 -0
  484. package/skills/llm-wiki-skill/workbench/web/src/components/ExportButtons.tsx +58 -0
  485. package/skills/llm-wiki-skill/workbench/web/src/components/GraphPanel.tsx +800 -0
  486. package/skills/llm-wiki-skill/workbench/web/src/components/GraphReader.tsx +52 -0
  487. package/skills/llm-wiki-skill/workbench/web/src/components/GraphSelection.tsx +100 -0
  488. package/skills/llm-wiki-skill/workbench/web/src/components/GraphSummaryDrawer.tsx +269 -0
  489. package/skills/llm-wiki-skill/workbench/web/src/components/MainViewTabs.tsx +69 -0
  490. package/skills/llm-wiki-skill/workbench/web/src/components/MarkdownView.tsx +106 -0
  491. package/skills/llm-wiki-skill/workbench/web/src/components/NewWikiDialog.tsx +103 -0
  492. package/skills/llm-wiki-skill/workbench/web/src/components/RefMenu.tsx +44 -0
  493. package/skills/llm-wiki-skill/workbench/web/src/components/RightDrawer.tsx +298 -0
  494. package/skills/llm-wiki-skill/workbench/web/src/components/SearchPanel.tsx +165 -0
  495. package/skills/llm-wiki-skill/workbench/web/src/components/SettingsPanel.tsx +320 -0
  496. package/skills/llm-wiki-skill/workbench/web/src/components/Sidebar.tsx +354 -0
  497. package/skills/llm-wiki-skill/workbench/web/src/components/ToolHistorySummary.tsx +111 -0
  498. package/skills/llm-wiki-skill/workbench/web/src/components/ToolStatusRunway.tsx +70 -0
  499. package/skills/llm-wiki-skill/workbench/web/src/components/TopBar.tsx +348 -0
  500. package/skills/llm-wiki-skill/workbench/web/src/components/renderers/DownloadOnlyRenderer.tsx +88 -0
  501. package/skills/llm-wiki-skill/workbench/web/src/components/renderers/HtmlRenderer.tsx +92 -0
  502. package/skills/llm-wiki-skill/workbench/web/src/components/ui/button.tsx +52 -0
  503. package/skills/llm-wiki-skill/workbench/web/src/components/ui/command.tsx +50 -0
  504. package/skills/llm-wiki-skill/workbench/web/src/components/ui/dialog.tsx +156 -0
  505. package/skills/llm-wiki-skill/workbench/web/src/components/ui/input.tsx +21 -0
  506. package/skills/llm-wiki-skill/workbench/web/src/components/ui/tooltip.tsx +57 -0
  507. package/skills/llm-wiki-skill/workbench/web/src/index.css +4204 -0
  508. package/skills/llm-wiki-skill/workbench/web/src/lib/api.ts +648 -0
  509. package/skills/llm-wiki-skill/workbench/web/src/lib/appearance.ts +114 -0
  510. package/skills/llm-wiki-skill/workbench/web/src/lib/drawer-layout.ts +41 -0
  511. package/skills/llm-wiki-skill/workbench/web/src/lib/drawer-state.ts +168 -0
  512. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-reader.ts +25 -0
  513. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-selection-drawer.ts +35 -0
  514. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-selection.ts +96 -0
  515. package/skills/llm-wiki-skill/workbench/web/src/lib/graph-summary-actions.ts +184 -0
  516. package/skills/llm-wiki-skill/workbench/web/src/lib/legacy-tool-status.ts +113 -0
  517. package/skills/llm-wiki-skill/workbench/web/src/lib/model-roles.ts +20 -0
  518. package/skills/llm-wiki-skill/workbench/web/src/lib/search-filter.ts +104 -0
  519. package/skills/llm-wiki-skill/workbench/web/src/lib/sse.ts +72 -0
  520. package/skills/llm-wiki-skill/workbench/web/src/lib/tool-status-format.ts +170 -0
  521. package/skills/llm-wiki-skill/workbench/web/src/lib/tool-status-model.ts +392 -0
  522. package/skills/llm-wiki-skill/workbench/web/src/lib/utils.ts +10 -0
  523. package/skills/llm-wiki-skill/workbench/web/src/lib/view-status.ts +16 -0
  524. package/skills/llm-wiki-skill/workbench/web/src/lib/wiki-links.ts +32 -0
  525. package/skills/llm-wiki-skill/workbench/web/src/main.tsx +10 -0
  526. package/skills/llm-wiki-skill/workbench/web/test/api.test.ts +61 -0
  527. package/skills/llm-wiki-skill/workbench/web/test/app-shell-drawer-layout.test.tsx +48 -0
  528. package/skills/llm-wiki-skill/workbench/web/test/appearance-panel.test.tsx +121 -0
  529. package/skills/llm-wiki-skill/workbench/web/test/appearance.test.ts +262 -0
  530. package/skills/llm-wiki-skill/workbench/web/test/chat-host-source.test.tsx +22 -0
  531. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-auto-scroll.test.tsx +311 -0
  532. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-bubbles.test.tsx +48 -0
  533. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-composer.test.tsx +86 -0
  534. package/skills/llm-wiki-skill/workbench/web/test/chat-panel-tool-status.test.tsx +26 -0
  535. package/skills/llm-wiki-skill/workbench/web/test/export-batch-paper.test.tsx +101 -0
  536. package/skills/llm-wiki-skill/workbench/web/test/graph-drawer-state.test.ts +257 -0
  537. package/skills/llm-wiki-skill/workbench/web/test/graph-panel-paper.test.tsx +200 -0
  538. package/skills/llm-wiki-skill/workbench/web/test/graph-reader-request.test.ts +32 -0
  539. package/skills/llm-wiki-skill/workbench/web/test/graph-reader.test.ts +62 -0
  540. package/skills/llm-wiki-skill/workbench/web/test/graph-selection-drawer.test.ts +56 -0
  541. package/skills/llm-wiki-skill/workbench/web/test/graph-selection.test.ts +83 -0
  542. package/skills/llm-wiki-skill/workbench/web/test/graph-summary-actions.test.ts +188 -0
  543. package/skills/llm-wiki-skill/workbench/web/test/main-view-tabs.test.tsx +34 -0
  544. package/skills/llm-wiki-skill/workbench/web/test/markdown-view.test.tsx +34 -0
  545. package/skills/llm-wiki-skill/workbench/web/test/popup-menus.test.tsx +85 -0
  546. package/skills/llm-wiki-skill/workbench/web/test/render.test.tsx +30 -0
  547. package/skills/llm-wiki-skill/workbench/web/test/render.tsx +38 -0
  548. package/skills/llm-wiki-skill/workbench/web/test/right-drawer-graph-summary.test.tsx +287 -0
  549. package/skills/llm-wiki-skill/workbench/web/test/right-drawer-interactions.test.tsx +182 -0
  550. package/skills/llm-wiki-skill/workbench/web/test/search-filter.test.ts +65 -0
  551. package/skills/llm-wiki-skill/workbench/web/test/search-panel.test.tsx +155 -0
  552. package/skills/llm-wiki-skill/workbench/web/test/setup-dom.ts +66 -0
  553. package/skills/llm-wiki-skill/workbench/web/test/sidebar-v2-source.test.ts +17 -0
  554. package/skills/llm-wiki-skill/workbench/web/test/sidebar.test.tsx +110 -0
  555. package/skills/llm-wiki-skill/workbench/web/test/tool-history-summary.test.tsx +150 -0
  556. package/skills/llm-wiki-skill/workbench/web/test/tool-status-format.test.ts +107 -0
  557. package/skills/llm-wiki-skill/workbench/web/test/tool-status-model.test.ts +324 -0
  558. package/skills/llm-wiki-skill/workbench/web/test/tool-status-runway.test.ts +159 -0
  559. package/skills/llm-wiki-skill/workbench/web/test/topbar.test.tsx +248 -0
  560. package/skills/llm-wiki-skill/workbench/web/test/visual/paper-ui.ts +898 -0
  561. package/skills/llm-wiki-skill/workbench/web/test/wiki-links.test.ts +23 -0
  562. package/skills/llm-wiki-skill/workbench/web/test-results/paper-ui/actual/.gitkeep +1 -0
  563. package/skills/llm-wiki-skill/workbench/web/test-results/paper-ui/baseline/.gitkeep +1 -0
  564. package/skills/llm-wiki-skill/workbench/web/tsconfig.app.json +30 -0
  565. package/skills/llm-wiki-skill/workbench/web/tsconfig.json +7 -0
  566. package/skills/llm-wiki-skill/workbench/web/tsconfig.node.json +24 -0
  567. package/skills/llm-wiki-skill/workbench/web/vite.config.ts +28 -0
@@ -0,0 +1,1754 @@
1
+ #!/bin/bash
2
+
3
+ set -euo pipefail
4
+
5
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
6
+
7
+ fail() {
8
+ echo "FAIL: $1" >&2
9
+ exit 1
10
+ }
11
+
12
+ assert_file_contains() {
13
+ local file="$1"
14
+ local text="$2"
15
+
16
+ if ! grep -F -- "$text" "$file" > /dev/null; then
17
+ fail "Expected $file to contain: $text"
18
+ fi
19
+ }
20
+
21
+ assert_file_not_contains() {
22
+ local file="$1"
23
+ local text="$2"
24
+
25
+ if grep -F -- "$text" "$file" > /dev/null; then
26
+ fail "Expected $file to not contain: $text"
27
+ fi
28
+ }
29
+
30
+ assert_text_contains() {
31
+ local text="$1"
32
+ local expected="$2"
33
+
34
+ if ! printf '%s' "$text" | grep -F -- "$expected" > /dev/null; then
35
+ fail "Expected output to contain: $expected"
36
+ fi
37
+ }
38
+
39
+ assert_text_not_contains() {
40
+ local text="$1"
41
+ local unexpected="$2"
42
+
43
+ if printf '%s' "$text" | grep -F -- "$unexpected" > /dev/null; then
44
+ fail "Expected output to not contain: $unexpected"
45
+ fi
46
+ }
47
+
48
+ assert_path_exists() {
49
+ local path="$1"
50
+
51
+ [ -e "$path" ] || fail "Expected path to exist: $path"
52
+ }
53
+
54
+ each_registry_label() {
55
+ local category="$1"
56
+
57
+ bash "$REPO_ROOT/scripts/source-registry.sh" list-by-category "$category" \
58
+ | awk -F '\t' 'NF { print $2 }'
59
+ }
60
+
61
+ assert_registry_labels_present_in_text() {
62
+ local text="$1"
63
+ local category="$2"
64
+ local label
65
+
66
+ while IFS= read -r label; do
67
+ [ -n "$label" ] || continue
68
+ assert_text_contains "$text" "$label"
69
+ done <<EOF
70
+ $(each_registry_label "$category")
71
+ EOF
72
+ }
73
+
74
+ assert_registry_labels_present_in_file() {
75
+ local file="$1"
76
+ local category="$2"
77
+ local label
78
+
79
+ while IFS= read -r label; do
80
+ [ -n "$label" ] || continue
81
+ assert_file_contains "$file" "$label"
82
+ done <<EOF
83
+ $(each_registry_label "$category")
84
+ EOF
85
+ }
86
+
87
+ make_stub() {
88
+ local path="$1"
89
+ local body="$2"
90
+
91
+ printf '%s\n' "$body" > "$path"
92
+ chmod +x "$path"
93
+ }
94
+
95
+ make_path_without_python() {
96
+ local bin_dir="$1"
97
+ local cmd cmd_path
98
+
99
+ mkdir -p "$bin_dir"
100
+ for cmd in bash dirname pwd mkdir tr awk sed sort cut head tail find wc uname date printf grep cat rm cp chmod mktemp; do
101
+ cmd_path="$(PATH="/usr/bin:/bin:/usr/sbin:/sbin" command -v "$cmd" 2>/dev/null || true)"
102
+ [ -n "$cmd_path" ] && ln -s "$cmd_path" "$bin_dir/$cmd"
103
+ done
104
+ }
105
+
106
+ make_repo_copy_without_git() {
107
+ local dest="$1"
108
+
109
+ cp -R "$REPO_ROOT" "$dest"
110
+ rm -rf "$dest/.git"
111
+ }
112
+
113
+ make_legacy_wiki() {
114
+ local wiki_root="$1"
115
+
116
+ mkdir -p "$wiki_root"/raw/{articles,tweets,wechat,pdfs,notes,assets}
117
+ mkdir -p "$wiki_root"/wiki/{entities,topics,sources,comparisons,synthesis}
118
+
119
+ cat > "$wiki_root/.wiki-schema.md" <<'EOF'
120
+ # Wiki Schema(知识库配置规范)
121
+
122
+ - 主题:旧知识库
123
+ - 创建日期:2026-04-01
124
+ EOF
125
+
126
+ printf '# 索引\n' > "$wiki_root/index.md"
127
+ printf '# 日志\n' > "$wiki_root/log.md"
128
+ printf '# 总览\n' > "$wiki_root/wiki/overview.md"
129
+ }
130
+
131
+ test_setup_runs_on_bash_3_2() {
132
+ local tmp_dir output
133
+ tmp_dir="$(mktemp -d)"
134
+ trap 'rm -rf "$tmp_dir"' RETURN
135
+
136
+ mkdir -p "$tmp_dir/home/.claude/skills" "$tmp_dir/bin"
137
+
138
+ make_stub "$tmp_dir/bin/bun" "#!/bin/sh
139
+ printf '%s\n' bun >> \"$tmp_dir/tool.log\"
140
+ mkdir -p node_modules
141
+ exit 0"
142
+
143
+ make_stub "$tmp_dir/bin/lsof" '#!/bin/sh
144
+ exit 1'
145
+
146
+ make_stub "$tmp_dir/bin/uv" "#!/bin/sh
147
+ printf '%s\n' uv >> \"$tmp_dir/tool.log\"
148
+ exit 0"
149
+
150
+ output="$(
151
+ HOME="$tmp_dir/home" \
152
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
153
+ bash "$REPO_ROOT/setup.sh" 2>&1
154
+ )" || fail "setup.sh should run successfully under bash 3.2"
155
+
156
+ assert_path_exists "$tmp_dir/home/.claude/skills/llm-wiki/SKILL.md"
157
+ assert_path_exists "$tmp_dir/home/.claude/skills/llm-wiki-upgrade/SKILL.md"
158
+ assert_path_exists "$tmp_dir/home/.claude/skills/llm-wiki/deps/baoyu-url-to-markdown"
159
+ [ ! -d "$tmp_dir/home/.claude/skills/baoyu-url-to-markdown" ] || fail "Did not expect optional adapters to be enabled by default"
160
+ [ ! -f "$tmp_dir/tool.log" ] || fail "Did not expect bun or uv to run for core-only setup"
161
+
162
+ assert_text_contains "$output" "当前只准备了知识库核心主线"
163
+ assert_text_contains "$output" "--with-optional-adapters"
164
+ assert_text_contains "$output" "/llm-wiki-upgrade"
165
+ }
166
+
167
+ test_install_with_optional_adapters_bootstraps_dependencies() {
168
+ local tmp_dir output
169
+ tmp_dir="$(mktemp -d)"
170
+ trap 'rm -rf "$tmp_dir"' RETURN
171
+
172
+ mkdir -p "$tmp_dir/home/.claude/skills" "$tmp_dir/bin"
173
+
174
+ make_stub "$tmp_dir/bin/bun" '#!/bin/sh
175
+ mkdir -p node_modules
176
+ exit 0'
177
+
178
+ make_stub "$tmp_dir/bin/lsof" '#!/bin/sh
179
+ exit 1'
180
+
181
+ make_stub "$tmp_dir/bin/uv" "#!/bin/sh
182
+ printf '%s\n' \"\$*\" >> \"$tmp_dir/uv.log\"
183
+ printf '%s\n' '#!/bin/sh' 'exit 0' > \"$tmp_dir/bin/wechat-article-to-markdown\"
184
+ chmod +x \"$tmp_dir/bin/wechat-article-to-markdown\"
185
+ exit 0"
186
+
187
+ output="$(
188
+ HOME="$tmp_dir/home" \
189
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
190
+ bash "$REPO_ROOT/install.sh" --platform claude --with-optional-adapters 2>&1
191
+ )" || fail "install.sh should support explicit optional adapter bootstrap"
192
+
193
+ [ -d "$tmp_dir/home/.claude/skills/baoyu-url-to-markdown" ] || fail "Expected baoyu-url-to-markdown to be installed when explicitly enabled"
194
+ [ -d "$tmp_dir/home/.claude/skills/youtube-transcript" ] || fail "Expected youtube-transcript to be installed when explicitly enabled"
195
+ assert_path_exists "$tmp_dir/bin/wechat-article-to-markdown"
196
+ assert_file_contains "$tmp_dir/uv.log" "tool install git+https://github.com/jackwener/wechat-article-to-markdown.git"
197
+
198
+ assert_text_contains "$output" "未检测到 Chrome 调试端口 9222"
199
+ assert_text_contains "$output" "仍可自动拉起临时浏览器"
200
+ assert_text_contains "$output" "open -na \"Google Chrome\" --args --remote-debugging-port=9222"
201
+ assert_text_contains "$output" "wechat-article-to-markdown 安装完成"
202
+ }
203
+
204
+ test_install_dry_run_for_claude() {
205
+ local tmp_dir output
206
+ tmp_dir="$(mktemp -d)"
207
+ trap 'rm -rf "$tmp_dir"' RETURN
208
+
209
+ mkdir -p "$tmp_dir/home/.claude/skills"
210
+
211
+ output="$(
212
+ HOME="$tmp_dir/home" \
213
+ bash "$REPO_ROOT/install.sh" --platform claude --dry-run 2>&1
214
+ )" || fail "install.sh dry-run for Claude should succeed"
215
+
216
+ assert_text_contains "$output" "平台:claude"
217
+ assert_text_contains "$output" "$tmp_dir/home/.claude/skills/llm-wiki"
218
+ assert_text_contains "$output" "--with-optional-adapters"
219
+ assert_text_not_contains "$output" "uv tool install"
220
+ assert_text_not_contains "$output" "bun install"
221
+ }
222
+
223
+ test_install_help_does_not_require_python() {
224
+ local tmp_dir output
225
+ tmp_dir="$(mktemp -d)"
226
+ trap 'rm -rf "$tmp_dir"' RETURN
227
+
228
+ make_path_without_python "$tmp_dir/bin"
229
+
230
+ output="$(
231
+ PATH="$tmp_dir/bin" \
232
+ bash "$REPO_ROOT/install.sh" --help 2>&1
233
+ )" || fail "install.sh --help should not require Python"
234
+
235
+ assert_text_contains "$output" "用法:"
236
+ }
237
+
238
+ test_install_dry_run_does_not_require_python() {
239
+ local tmp_dir output
240
+ tmp_dir="$(mktemp -d)"
241
+ trap 'rm -rf "$tmp_dir"' RETURN
242
+
243
+ mkdir -p "$tmp_dir/home/.codex/skills"
244
+ make_path_without_python "$tmp_dir/bin"
245
+
246
+ output="$(
247
+ HOME="$tmp_dir/home" \
248
+ PATH="$tmp_dir/bin" \
249
+ bash "$REPO_ROOT/install.sh" --platform codex --dry-run 2>&1
250
+ )" || fail "install.sh dry-run should not require Python"
251
+
252
+ assert_text_contains "$output" "install.ps1"
253
+ assert_text_contains "$output" "llm-wiki 已准备完成"
254
+ }
255
+
256
+ test_install_auto_refuses_ambiguous_platforms() {
257
+ local tmp_dir output
258
+ tmp_dir="$(mktemp -d)"
259
+ trap 'rm -rf "$tmp_dir"' RETURN
260
+
261
+ mkdir -p "$tmp_dir/home/.claude/skills" "$tmp_dir/home/.codex/skills" "$tmp_dir/home/.hermes/skills"
262
+
263
+ if output="$(
264
+ HOME="$tmp_dir/home" \
265
+ bash "$REPO_ROOT/install.sh" --platform auto 2>&1
266
+ )"; then
267
+ fail "install.sh auto should fail when multiple platform homes are present"
268
+ fi
269
+
270
+ assert_text_contains "$output" "检测到多个可用平台"
271
+ assert_text_contains "$output" "--platform"
272
+ }
273
+
274
+ test_install_openclaw_copies_bundle() {
275
+ local tmp_dir
276
+ tmp_dir="$(mktemp -d)"
277
+ trap 'rm -rf "$tmp_dir"' RETURN
278
+
279
+ mkdir -p "$tmp_dir/home/.openclaw/skills" "$tmp_dir/bin"
280
+
281
+ make_stub "$tmp_dir/bin/bun" '#!/bin/sh
282
+ mkdir -p node_modules
283
+ exit 0'
284
+
285
+ make_stub "$tmp_dir/bin/lsof" '#!/bin/sh
286
+ exit 1'
287
+
288
+ HOME="$tmp_dir/home" \
289
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
290
+ bash "$REPO_ROOT/install.sh" --platform openclaw > /dev/null 2>&1 || fail "install.sh should install for OpenClaw"
291
+
292
+ assert_path_exists "$tmp_dir/home/.openclaw/skills/llm-wiki/SKILL.md"
293
+ assert_path_exists "$tmp_dir/home/.openclaw/skills/llm-wiki/install.sh"
294
+ assert_path_exists "$tmp_dir/home/.openclaw/skills/llm-wiki/scripts/source-registry.sh"
295
+ assert_path_exists "$tmp_dir/home/.openclaw/skills/llm-wiki/deps/baoyu-url-to-markdown"
296
+ [ ! -d "$tmp_dir/home/.openclaw/skills/llm-wiki-upgrade" ] || fail "Did not expect Claude-only companion skill to be installed for OpenClaw"
297
+ [ ! -d "$tmp_dir/home/.openclaw/skills/baoyu-url-to-markdown" ] || fail "Did not expect optional adapters to be enabled by default"
298
+ }
299
+
300
+ test_install_hermes_copies_bundle() {
301
+ local tmp_dir
302
+ tmp_dir="$(mktemp -d)"
303
+ trap 'rm -rf "$tmp_dir"' RETURN
304
+
305
+ mkdir -p "$tmp_dir/home/.hermes/skills" "$tmp_dir/bin"
306
+
307
+ make_stub "$tmp_dir/bin/bun" '#!/bin/sh
308
+ mkdir -p node_modules
309
+ exit 0'
310
+
311
+ make_stub "$tmp_dir/bin/lsof" '#!/bin/sh
312
+ exit 1'
313
+
314
+ HOME="$tmp_dir/home" \
315
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
316
+ bash "$REPO_ROOT/install.sh" --platform hermes > /dev/null 2>&1 || fail "install.sh should install for Hermes"
317
+
318
+ assert_path_exists "$tmp_dir/home/.hermes/skills/llm-wiki/SKILL.md"
319
+ assert_path_exists "$tmp_dir/home/.hermes/skills/llm-wiki/HERMES.md"
320
+ assert_path_exists "$tmp_dir/home/.hermes/skills/llm-wiki/platforms/hermes/README.md"
321
+ assert_path_exists "$tmp_dir/home/.hermes/skills/llm-wiki/scripts/source-registry.sh"
322
+ [ ! -d "$tmp_dir/home/.hermes/skills/baoyu-url-to-markdown" ] || fail "Did not expect optional adapters to be enabled by default"
323
+ }
324
+
325
+ test_upgrade_refreshes_claude_companion_skill() {
326
+ local tmp_dir output repo_copy
327
+ tmp_dir="$(mktemp -d)"
328
+ trap 'rm -rf "$tmp_dir"' RETURN
329
+
330
+ repo_copy="$tmp_dir/repo"
331
+ make_repo_copy_without_git "$repo_copy"
332
+
333
+ mkdir -p "$tmp_dir/home/.claude/skills/llm-wiki" "$tmp_dir/home/.claude/skills/llm-wiki-upgrade"
334
+ printf '# Changelog\n\n## v2.3.0 (2026-04-15)\n' > "$tmp_dir/home/.claude/skills/llm-wiki/CHANGELOG.md"
335
+ printf 'old\n' > "$tmp_dir/home/.claude/skills/llm-wiki-upgrade/SKILL.md"
336
+
337
+ output="$(
338
+ HOME="$tmp_dir/home" \
339
+ bash "$repo_copy/install.sh" --upgrade --platform claude 2>&1
340
+ )" || fail "install.sh --upgrade should refresh the Claude companion skill"
341
+
342
+ assert_text_contains "$output" "/llm-wiki-upgrade"
343
+ assert_path_exists "$tmp_dir/home/.claude/skills/llm-wiki-upgrade/SKILL.md"
344
+ assert_file_contains "$tmp_dir/home/.claude/skills/llm-wiki-upgrade/SKILL.md" "--with-optional-adapters"
345
+ assert_file_contains "$tmp_dir/home/.claude/skills/llm-wiki-upgrade/SKILL.md" 'bash "$TMP_DIR/llm-wiki-skill/install.sh" --upgrade --platform claude'
346
+ }
347
+
348
+ test_init_fills_language_placeholder() {
349
+ local tmp_dir wiki_root
350
+ tmp_dir="$(mktemp -d)"
351
+ trap 'rm -rf "$tmp_dir"' RETURN
352
+
353
+ wiki_root="$tmp_dir/Test Wiki"
354
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "测试主题" "English" > /dev/null
355
+
356
+ assert_file_contains "$wiki_root/.wiki-schema.md" "- 语言:English"
357
+ assert_file_not_contains "$wiki_root/.wiki-schema.md" "{{LANGUAGE}}"
358
+ }
359
+
360
+ test_phase1_templates_exist() {
361
+ assert_path_exists "$REPO_ROOT/templates/purpose-template.md"
362
+ assert_path_exists "$REPO_ROOT/templates/purpose-en-template.md"
363
+ assert_path_exists "$REPO_ROOT/templates/query-template.md"
364
+
365
+ assert_file_contains "$REPO_ROOT/templates/purpose-template.md" "# 研究目的与方向"
366
+ assert_file_contains "$REPO_ROOT/templates/purpose-template.md" "## 核心目标"
367
+ assert_file_contains "$REPO_ROOT/templates/purpose-en-template.md" "# Research Purpose and Direction"
368
+ assert_file_contains "$REPO_ROOT/templates/purpose-en-template.md" "## Core Goal"
369
+ assert_file_contains "$REPO_ROOT/templates/query-template.md" "type: query"
370
+ assert_file_contains "$REPO_ROOT/templates/query-template.md" "derived: true"
371
+ }
372
+
373
+ test_init_creates_purpose_and_cache_files() {
374
+ local tmp_dir wiki_root
375
+ tmp_dir="$(mktemp -d)"
376
+ trap 'rm -rf "$tmp_dir"' RETURN
377
+
378
+ wiki_root="$tmp_dir/English Wiki"
379
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "测试主题" "English" > /dev/null
380
+
381
+ assert_path_exists "$wiki_root/purpose.md"
382
+ assert_path_exists "$wiki_root/.wiki-cache.json"
383
+ assert_file_contains "$wiki_root/purpose.md" "# Research Purpose and Direction"
384
+ assert_file_contains "$wiki_root/.wiki-cache.json" '"version": 1'
385
+ assert_file_contains "$wiki_root/.wiki-cache.json" '"entries": {}'
386
+ }
387
+
388
+ test_cache_script_handles_miss_hit_and_invalidate() {
389
+ local tmp_dir wiki_root file_path output
390
+ tmp_dir="$(mktemp -d)"
391
+ trap 'rm -rf "$tmp_dir"' RETURN
392
+
393
+ wiki_root="$tmp_dir/cache-wiki"
394
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "缓存测试" "中文" > /dev/null
395
+
396
+ file_path="$wiki_root/raw/articles/example.md"
397
+ printf '缓存测试内容\n' > "$file_path"
398
+
399
+ output="$(
400
+ bash "$REPO_ROOT/scripts/cache.sh" check "$file_path" 2>&1
401
+ )" || fail "cache.sh check should work for uncached files"
402
+ [ "$output" = "MISS:no_entry" ] || fail "Expected initial cache check to be MISS:no_entry, got: $output"
403
+
404
+ # source 页面必须有 source_path frontmatter,自愈才能验证通过
405
+ cat > "$wiki_root/wiki/sources/example.md" <<'SRCEOF'
406
+ ---
407
+ source_path: raw/articles/example.md
408
+ ---
409
+ # 来源页
410
+ SRCEOF
411
+ bash "$REPO_ROOT/scripts/cache.sh" update "$file_path" "wiki/sources/example.md" > /dev/null 2>&1 \
412
+ || fail "cache.sh update should succeed"
413
+
414
+ output="$(
415
+ bash "$REPO_ROOT/scripts/cache.sh" check "$file_path" 2>&1
416
+ )" || fail "cache.sh check should work for cached files"
417
+ [ "$output" = "HIT" ] || fail "Expected updated cache check to be HIT, got: $output"
418
+
419
+ bash "$REPO_ROOT/scripts/cache.sh" invalidate "$file_path" > /dev/null 2>&1 \
420
+ || fail "cache.sh invalidate should succeed"
421
+
422
+ output="$(
423
+ bash "$REPO_ROOT/scripts/cache.sh" check "$file_path" 2>&1
424
+ )" || fail "cache.sh check should work after invalidation"
425
+ # 自愈:invalidate 后 source 页面仍存在,stem + source_path 匹配 → HIT(repaired)
426
+ [ "$output" = "HIT(repaired)" ] || fail "Expected invalidated cache check to be HIT(repaired), got: $output"
427
+ }
428
+
429
+ test_skill_md_phase2_init_mentions_purpose_and_cache() {
430
+ local section
431
+ section="$(sed -n '/## 工作流 1:init/,/## 工作流 2:ingest/p' "$REPO_ROOT/SKILL.md")"
432
+
433
+ assert_text_contains "$section" "purpose.md"
434
+ assert_text_contains "$section" ".wiki-cache.json"
435
+ assert_text_contains "$section" "填写核心目标和关键问题"
436
+ }
437
+
438
+ test_skill_md_phase2_ingest_mentions_two_step_cache_and_confidence() {
439
+ local section
440
+ section="$(sed -n '/## 工作流 2:ingest/,/## 工作流 3:batch-ingest/p' "$REPO_ROOT/SKILL.md")"
441
+
442
+ assert_text_contains "$section" '`purpose.md` > `.wiki-schema.md` > `index.md`'
443
+ assert_text_contains "$section" 'bash ${SKILL_DIR}/scripts/cache.sh check'
444
+ assert_text_contains "$section" "Step 1:结构化分析"
445
+ assert_text_contains "$section" "Step 2:页面生成"
446
+ assert_text_contains "$section" '"confidence": "EXTRACTED"'
447
+ assert_text_contains "$section" "<!-- confidence: UNVERIFIED -->"
448
+ assert_text_contains "$section" "页面顶部加注释说明本次处理因格式问题降级"
449
+ }
450
+
451
+ test_skill_md_phase2_batch_ingest_mentions_cache_skip_summary() {
452
+ local section
453
+ section="$(sed -n '/## 工作流 3:batch-ingest/,/## 工作流 4:query/p' "$REPO_ROOT/SKILL.md")"
454
+
455
+ assert_text_contains "$section" '每个文件先 `cache check`'
456
+ assert_text_contains "$section" "已跳过 N 个(无变化),处理 M 个(新增/更新)"
457
+ }
458
+
459
+ test_skill_md_phase2_status_mentions_purpose_presence() {
460
+ local section
461
+ section="$(sed -n '/## 工作流 6:status/,/## 工作流 7:digest/p' "$REPO_ROOT/SKILL.md")"
462
+
463
+ assert_text_contains "$section" "purpose.md 是否存在"
464
+ }
465
+
466
+ test_skill_md_phase2_has_delete_workflow_and_route() {
467
+ local route_section delete_section
468
+ route_section="$(sed -n '/## 工作流路由/,/## 通用前置检查/p' "$REPO_ROOT/SKILL.md")"
469
+ delete_section="$(sed -n '/## 工作流 9:delete/,$p' "$REPO_ROOT/SKILL.md")"
470
+
471
+ assert_text_contains "$route_section" '"删除素材"、"remove"、"delete source"、"移除"'
472
+ assert_text_contains "$route_section" "→ **delete**"
473
+ assert_text_contains "$delete_section" "影响超过 5 个页面时"
474
+ assert_text_contains "$delete_section" 'bash ${SKILL_DIR}/scripts/delete-helper.sh scan-refs'
475
+ assert_text_contains "$delete_section" "cache.sh invalidate"
476
+ }
477
+
478
+ test_delete_helper_scans_reference_files() {
479
+ local tmp_dir wiki_root output
480
+ tmp_dir="$(mktemp -d)"
481
+ trap 'rm -rf "$tmp_dir"' RETURN
482
+
483
+ wiki_root="$tmp_dir/delete-wiki"
484
+ mkdir -p "$wiki_root"/raw/articles
485
+ mkdir -p "$wiki_root"/wiki/{sources,entities,topics}
486
+
487
+ printf '原文\n' > "$wiki_root/raw/articles/2024-01-15-ai-article.md"
488
+ cat > "$wiki_root/wiki/sources/2024-01-15-ai-article.md" <<'EOF'
489
+ ---
490
+ sources: ["raw/articles/2024-01-15-ai-article.md"]
491
+ ---
492
+
493
+ [source: AI 文章](../raw/articles/2024-01-15-ai-article.md)
494
+ EOF
495
+ printf '见 raw/articles/2024-01-15-ai-article.md\n' > "$wiki_root/wiki/entities/AI-Agent.md"
496
+ printf '引用 [source: AI 文章](../raw/articles/2024-01-15-ai-article.md)\n' > "$wiki_root/wiki/topics/大语言模型.md"
497
+
498
+ output="$(
499
+ bash "$REPO_ROOT/scripts/delete-helper.sh" scan-refs "$wiki_root" "2024-01-15-ai-article.md" 2>&1
500
+ )" || fail "delete-helper scan-refs should succeed"
501
+
502
+ assert_text_contains "$output" "wiki/entities/AI-Agent.md"
503
+ assert_text_contains "$output" "wiki/sources/2024-01-15-ai-article.md"
504
+ assert_text_contains "$output" "wiki/topics/大语言模型.md"
505
+ }
506
+
507
+ test_skill_md_phase3_query_mentions_persistence_and_duplicate_handling() {
508
+ local section
509
+ section="$(sed -n '/## 工作流 4:query/,/## 工作流 5:lint/p' "$REPO_ROOT/SKILL.md")"
510
+
511
+ assert_text_contains "$section" "wiki/queries/{date}-{short-hash}.md"
512
+ assert_text_contains "$section" "derived: true"
513
+ assert_text_contains "$section" "引用了 3 个及以上来源"
514
+ assert_text_contains "$section" "通过 frontmatter tags 和 title 匹配"
515
+ assert_text_contains "$section" "superseded-by"
516
+ assert_text_contains "$section" "不作为主要知识来源"
517
+ }
518
+
519
+ test_hook_session_start_outputs_context_when_wiki_exists() {
520
+ local tmp_dir output wiki_root
521
+ tmp_dir="$(mktemp -d)"
522
+ trap 'rm -rf "$tmp_dir"' RETURN
523
+
524
+ wiki_root="$tmp_dir/my-wiki"
525
+ mkdir -p "$tmp_dir/home" "$wiki_root"
526
+ printf '# schema\n' > "$wiki_root/.wiki-schema.md"
527
+ printf '# index\n' > "$wiki_root/index.md"
528
+ printf '%s\n' "$wiki_root" > "$tmp_dir/home/.llm-wiki-path"
529
+
530
+ output="$(
531
+ HOME="$tmp_dir/home" \
532
+ bash "$REPO_ROOT/scripts/hook-session-start.sh" 2>&1
533
+ )" || fail "hook-session-start.sh should succeed when wiki exists"
534
+
535
+ assert_text_contains "$output" "hookSpecificOutput"
536
+ assert_text_contains "$output" "SessionStart"
537
+ assert_text_contains "$output" "[llm-wiki] 检测到知识库"
538
+ }
539
+
540
+ test_hook_session_start_returns_empty_json_without_wiki() {
541
+ local tmp_dir output
542
+ tmp_dir="$(mktemp -d)"
543
+ trap 'rm -rf "$tmp_dir"' RETURN
544
+
545
+ mkdir -p "$tmp_dir/home"
546
+
547
+ output="$(
548
+ HOME="$tmp_dir/home" \
549
+ bash "$REPO_ROOT/scripts/hook-session-start.sh" 2>&1
550
+ )" || fail "hook-session-start.sh should succeed without wiki"
551
+
552
+ [ "$output" = "{}" ] || fail "Expected hook-session-start.sh to return {} without wiki"
553
+ }
554
+
555
+ test_hook_session_start_without_wiki_does_not_require_python() {
556
+ local tmp_dir output
557
+ tmp_dir="$(mktemp -d)"
558
+ trap 'rm -rf "$tmp_dir"' RETURN
559
+
560
+ mkdir -p "$tmp_dir/home"
561
+ make_path_without_python "$tmp_dir/bin"
562
+
563
+ output="$(
564
+ HOME="$tmp_dir/home" \
565
+ PATH="$tmp_dir/bin" \
566
+ bash "$REPO_ROOT/scripts/hook-session-start.sh" 2>&1
567
+ )" || fail "hook-session-start.sh should return {} without Python when no wiki exists"
568
+
569
+ [ "$output" = "{}" ] || fail "Expected hook-session-start.sh to return {} without wiki, got: $output"
570
+ }
571
+
572
+ test_install_registers_and_uninstalls_session_start_hook() {
573
+ local tmp_dir settings_path output
574
+ tmp_dir="$(mktemp -d)"
575
+ trap 'rm -rf "$tmp_dir"' RETURN
576
+
577
+ mkdir -p "$tmp_dir/home/.claude/skills" "$tmp_dir/bin"
578
+ settings_path="$tmp_dir/home/.claude/settings.json"
579
+ cat > "$settings_path" <<'EOF'
580
+ {
581
+ "enabledPlugins": {
582
+ "demo": true
583
+ }
584
+ }
585
+ EOF
586
+
587
+ make_stub "$tmp_dir/bin/bun" '#!/bin/sh
588
+ mkdir -p node_modules
589
+ exit 0'
590
+
591
+ make_stub "$tmp_dir/bin/lsof" '#!/bin/sh
592
+ exit 1'
593
+
594
+ make_stub "$tmp_dir/bin/uv" '#!/bin/sh
595
+ printf "%s\n" "#!/bin/sh" "exit 0" > "'"$tmp_dir"'/bin/wechat-article-to-markdown"
596
+ chmod +x "'"$tmp_dir"'/bin/wechat-article-to-markdown"
597
+ exit 0'
598
+
599
+ HOME="$tmp_dir/home" \
600
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
601
+ bash "$REPO_ROOT/install.sh" --platform claude --install-hooks > /dev/null 2>&1 \
602
+ || fail "install.sh should register session hook for Claude"
603
+
604
+ assert_path_exists "$tmp_dir/home/.claude/settings.json.bak.llm-wiki"
605
+ assert_file_contains "$settings_path" '"SessionStart"'
606
+ assert_file_contains "$settings_path" "$tmp_dir/home/.claude/skills/llm-wiki/scripts/hook-session-start.sh"
607
+
608
+ HOME="$tmp_dir/home" \
609
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
610
+ bash "$REPO_ROOT/install.sh" --uninstall-hooks > /dev/null 2>&1 \
611
+ || fail "install.sh should remove session hook"
612
+
613
+ assert_file_not_contains "$settings_path" "hook-session-start.sh"
614
+ assert_file_contains "$settings_path" '"enabledPlugins"'
615
+ }
616
+
617
+ test_platform_entries_mention_hook_and_wiki_context() {
618
+ assert_file_contains "$REPO_ROOT/platforms/claude/CLAUDE.md" "/llm-wiki-upgrade"
619
+ assert_file_contains "$REPO_ROOT/platforms/claude/CLAUDE.md" "--install-hooks"
620
+ assert_file_contains "$REPO_ROOT/platforms/codex/AGENTS.md" "优先查阅 wiki/index.md"
621
+ assert_file_contains "$REPO_ROOT/platforms/openclaw/README.md" "--upgrade --platform openclaw --target-dir <你的技能目录>/llm-wiki"
622
+ assert_file_contains "$REPO_ROOT/platforms/hermes/README.md" "--platform hermes"
623
+ assert_file_contains "$REPO_ROOT/platforms/hermes/README.md" "~/.hermes/skills/llm-wiki"
624
+ }
625
+
626
+ test_root_entries_explain_core_only_optional_and_target_dir() {
627
+ assert_file_contains "$REPO_ROOT/AGENTS.md" "--with-optional-adapters"
628
+ assert_file_contains "$REPO_ROOT/AGENTS.md" "默认只准备知识库核心主线"
629
+ assert_file_contains "$REPO_ROOT/AGENTS.md" "--target-dir <你的技能目录>/llm-wiki"
630
+ assert_file_contains "$REPO_ROOT/CLAUDE.md" "/llm-wiki-upgrade"
631
+ assert_file_contains "$REPO_ROOT/CLAUDE.md" "--with-optional-adapters"
632
+ assert_file_contains "$REPO_ROOT/CLAUDE.md" "默认只准备知识库核心主线"
633
+ assert_file_contains "$REPO_ROOT/HERMES.md" "--platform hermes"
634
+ assert_file_contains "$REPO_ROOT/HERMES.md" "~/.hermes/skills/llm-wiki"
635
+ }
636
+
637
+ test_skill_md_phase5_lint_mentions_confidence_audit() {
638
+ local section
639
+ section="$(sed -n '/## 工作流 5:lint/,/## 工作流 6:status/p' "$REPO_ROOT/SKILL.md")"
640
+
641
+ assert_text_contains "$section" "置信度报告"
642
+ assert_text_contains "$section" "AMBIGUOUS"
643
+ assert_text_contains "$section" "抽查标注为 EXTRACTED 的条目"
644
+ }
645
+
646
+ test_changelog_mentions_wiki_core_upgrades() {
647
+ assert_file_contains "$REPO_ROOT/CHANGELOG.md" "purpose.md"
648
+ assert_file_contains "$REPO_ROOT/CHANGELOG.md" "SessionStart hook"
649
+ assert_file_contains "$REPO_ROOT/CHANGELOG.md" "delete 工作流"
650
+ assert_file_contains "$REPO_ROOT/CHANGELOG.md" "/llm-wiki-upgrade"
651
+ assert_file_contains "$REPO_ROOT/CHANGELOG.md" "--with-optional-adapters"
652
+ assert_file_contains "$REPO_ROOT/CHANGELOG.md" "核心主线"
653
+ }
654
+
655
+ test_readme_sections() {
656
+ assert_file_contains "$REPO_ROOT/README.md" "## 30 秒上手"
657
+ assert_file_contains "$REPO_ROOT/README.md" "<summary><strong>常见问题</strong></summary>"
658
+ assert_file_contains "$REPO_ROOT/README.md" "bash install.sh --platform claude"
659
+ assert_file_contains "$REPO_ROOT/README.md" "bash install.sh --platform codex"
660
+ assert_file_contains "$REPO_ROOT/README.md" "bash install.sh --platform openclaw"
661
+ assert_file_contains "$REPO_ROOT/README.md" "bash install.sh --platform hermes"
662
+ assert_file_contains "$REPO_ROOT/README.md" "--with-optional-adapters"
663
+ assert_file_contains "$REPO_ROOT/README.md" "/llm-wiki-upgrade"
664
+ assert_file_contains "$REPO_ROOT/README.md" "--target-dir <你的技能目录>/llm-wiki"
665
+ assert_file_contains "$REPO_ROOT/README.md" "--upgrade --platform openclaw --target-dir <你的技能目录>/llm-wiki"
666
+ assert_file_contains "$REPO_ROOT/README.md" "--upgrade --platform hermes --target-dir <你的技能目录>/llm-wiki"
667
+ assert_file_contains "$REPO_ROOT/README.md" "wechat-article-to-markdown"
668
+ assert_file_contains "$REPO_ROOT/README.md" "HERMES.md"
669
+ assert_file_not_contains "$REPO_ROOT/README.md" "bash setup.sh"
670
+ assert_file_not_contains "$REPO_ROOT/README.md" "x-article-extractor"
671
+ assert_file_not_contains "$REPO_ROOT/README.md" "baoyu-danger-x-to-markdown"
672
+ }
673
+
674
+ test_claude_upgrade_companion_source_exists() {
675
+ assert_path_exists "$REPO_ROOT/platforms/claude/companions/llm-wiki-upgrade/SKILL.md"
676
+ assert_file_contains "$REPO_ROOT/platforms/claude/companions/llm-wiki-upgrade/SKILL.md" "--with-optional-adapters"
677
+ assert_file_contains "$REPO_ROOT/platforms/claude/companions/llm-wiki-upgrade/SKILL.md" 'bash "$TMP_DIR/llm-wiki-skill/install.sh" --upgrade --platform claude'
678
+ }
679
+
680
+ test_uv_tool_install_failure_is_graceful() {
681
+ local tmp_dir output
682
+ tmp_dir="$(mktemp -d)"
683
+ trap 'rm -rf "$tmp_dir"' RETURN
684
+
685
+ mkdir -p "$tmp_dir/home/.claude/skills" "$tmp_dir/bin"
686
+
687
+ make_stub "$tmp_dir/bin/bun" '#!/bin/sh
688
+ mkdir -p node_modules
689
+ exit 0'
690
+
691
+ make_stub "$tmp_dir/bin/lsof" '#!/bin/sh
692
+ exit 1'
693
+
694
+ make_stub "$tmp_dir/bin/uv" '#!/bin/sh
695
+ exit 1'
696
+
697
+ output="$(
698
+ HOME="$tmp_dir/home" \
699
+ PATH="$tmp_dir/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
700
+ bash "$REPO_ROOT/install.sh" --platform claude --with-optional-adapters 2>&1
701
+ )" || fail "install.sh should keep going when optional adapter bootstrap fails"
702
+
703
+ assert_text_contains "$output" "wechat-article-to-markdown 安装失败"
704
+ assert_text_contains "$output" "llm-wiki 已准备完成"
705
+ assert_path_exists "$tmp_dir/home/.claude/skills/llm-wiki/SKILL.md"
706
+ }
707
+
708
+ test_upgrade_auto_refuses_ambiguous_installed_platforms() {
709
+ local tmp_dir output
710
+ tmp_dir="$(mktemp -d)"
711
+ trap 'rm -rf "$tmp_dir"' RETURN
712
+
713
+ mkdir -p "$tmp_dir/home/.claude/skills/llm-wiki" "$tmp_dir/home/.codex/skills/llm-wiki" "$tmp_dir/home/.hermes/skills/llm-wiki"
714
+
715
+ if output="$(
716
+ HOME="$tmp_dir/home" \
717
+ bash "$REPO_ROOT/install.sh" --upgrade --platform auto 2>&1
718
+ )"; then
719
+ fail "install.sh --upgrade --platform auto should fail when multiple installs exist"
720
+ fi
721
+
722
+ assert_text_contains "$output" "检测到多个已安装平台"
723
+ assert_text_contains "$output" "--platform"
724
+ }
725
+
726
+ test_upgrade_uses_explicit_target_dir() {
727
+ local tmp_dir output custom_target repo_copy
728
+ tmp_dir="$(mktemp -d)"
729
+ trap 'rm -rf "$tmp_dir"' RETURN
730
+
731
+ repo_copy="$tmp_dir/repo"
732
+ make_repo_copy_without_git "$repo_copy"
733
+
734
+ custom_target="$tmp_dir/custom/llm-wiki"
735
+ mkdir -p "$tmp_dir/home/.openclaw/skills" "$custom_target"
736
+ printf 'stale\n' > "$custom_target/README.md"
737
+
738
+ output="$(
739
+ HOME="$tmp_dir/home" \
740
+ bash "$repo_copy/install.sh" --upgrade --platform openclaw --target-dir "$custom_target" 2>&1
741
+ )" || fail "install.sh --upgrade should support explicit target directories"
742
+
743
+ assert_text_contains "$output" "目标目录:$custom_target"
744
+ assert_text_contains "$output" "llm-wiki 升级完成"
745
+ assert_path_exists "$custom_target/install.sh"
746
+ cmp -s "$custom_target/README.md" "$repo_copy/README.md" \
747
+ || fail "Expected explicit target dir upgrade to refresh README.md"
748
+ [ ! -d "$tmp_dir/home/.openclaw/skills/llm-wiki" ] || fail "Did not expect upgrade to write into the default OpenClaw skill path"
749
+ }
750
+
751
+ test_upgrade_fails_when_explicit_target_dir_is_missing() {
752
+ local tmp_dir output custom_target repo_copy
753
+ tmp_dir="$(mktemp -d)"
754
+ trap 'rm -rf "$tmp_dir"' RETURN
755
+
756
+ repo_copy="$tmp_dir/repo"
757
+ make_repo_copy_without_git "$repo_copy"
758
+
759
+ custom_target="$tmp_dir/custom/llm-wiki"
760
+ mkdir -p "$tmp_dir/home/.openclaw/skills"
761
+
762
+ if output="$(
763
+ HOME="$tmp_dir/home" \
764
+ bash "$repo_copy/install.sh" --upgrade --platform openclaw --target-dir "$custom_target" 2>&1
765
+ )"; then
766
+ fail "install.sh --upgrade should fail when the explicit target directory is missing"
767
+ fi
768
+
769
+ assert_text_contains "$output" "尚未安装 llm-wiki"
770
+ assert_text_contains "$output" "升级失败"
771
+ }
772
+
773
+ test_skill_md_routes_wechat_to_new_tool() {
774
+ assert_file_contains "$REPO_ROOT/SKILL.md" "scripts/source-registry.sh match-url"
775
+ assert_file_contains "$REPO_ROOT/SKILL.md" "scripts/source-registry.sh match-file"
776
+ assert_file_contains "$REPO_ROOT/SKILL.md" '`adapter_name`'
777
+ assert_file_not_contains "$REPO_ROOT/SKILL.md" "x-article-extractor"
778
+ }
779
+
780
+ test_templates_have_no_empty_links() {
781
+ assert_file_not_contains "$REPO_ROOT/templates/entity-template.md" "- [[]]"
782
+ assert_file_not_contains "$REPO_ROOT/templates/source-template.md" "- [[]]"
783
+ assert_file_not_contains "$REPO_ROOT/templates/topic-template.md" "- [[]]"
784
+ }
785
+
786
+ test_batch_ingest_has_step_two() {
787
+ local section
788
+ section="$(sed -n '/## 工作流 3:batch-ingest/,/## 工作流 4:query/p' "$REPO_ROOT/SKILL.md")"
789
+
790
+ assert_text_contains "$section" "1. **确认知识库路径**"
791
+ assert_text_contains "$section" "2. **列出所有可处理文件**"
792
+ assert_text_contains "$section" "3. **展示文件列表**"
793
+ }
794
+
795
+ test_english_templates_exist_and_have_placeholders() {
796
+ assert_path_exists "$REPO_ROOT/templates/index-en-template.md"
797
+ assert_path_exists "$REPO_ROOT/templates/overview-en-template.md"
798
+ assert_path_exists "$REPO_ROOT/templates/log-en-template.md"
799
+
800
+ assert_file_contains "$REPO_ROOT/templates/index-en-template.md" "{{DATE}}"
801
+ assert_file_contains "$REPO_ROOT/templates/index-en-template.md" "{{TOPIC}}"
802
+ assert_file_contains "$REPO_ROOT/templates/overview-en-template.md" "{{DATE}}"
803
+ assert_file_contains "$REPO_ROOT/templates/overview-en-template.md" "{{TOPIC}}"
804
+ assert_file_contains "$REPO_ROOT/templates/log-en-template.md" "{{DATE}}"
805
+ assert_file_contains "$REPO_ROOT/templates/log-en-template.md" "{{TOPIC}}"
806
+ }
807
+
808
+ test_english_templates_have_no_empty_links() {
809
+ assert_file_not_contains "$REPO_ROOT/templates/index-en-template.md" "[[]]"
810
+ assert_file_not_contains "$REPO_ROOT/templates/overview-en-template.md" "[[]]"
811
+ assert_file_not_contains "$REPO_ROOT/templates/log-en-template.md" "[[]]"
812
+ }
813
+
814
+ test_skill_md_has_shared_preflight_and_language_rules() {
815
+ assert_file_contains "$REPO_ROOT/SKILL.md" "## 通用前置检查"
816
+ assert_file_contains "$REPO_ROOT/SKILL.md" "## 输出语言规则"
817
+ assert_file_contains "$REPO_ROOT/SKILL.md" "素材 → Source"
818
+ assert_file_contains "$REPO_ROOT/SKILL.md" "知识图谱 → Knowledge Graph"
819
+ }
820
+
821
+ test_skill_md_uses_external_english_templates_and_no_english_output_blocks() {
822
+ assert_file_contains "$REPO_ROOT/SKILL.md" "templates/index-en-template.md"
823
+ assert_file_contains "$REPO_ROOT/SKILL.md" "templates/overview-en-template.md"
824
+ assert_file_contains "$REPO_ROOT/SKILL.md" "templates/log-en-template.md"
825
+ assert_file_not_contains "$REPO_ROOT/SKILL.md" "**English(en)**:"
826
+ }
827
+
828
+ test_setup_wrapper_is_marked_deprecated() {
829
+ assert_file_contains "$REPO_ROOT/setup.sh" "已废弃:请使用 bash install.sh --platform claude"
830
+ }
831
+
832
+ test_source_registry_contract_is_frozen() {
833
+ local output
834
+
835
+ output="$(
836
+ bash "$REPO_ROOT/scripts/source-registry.sh" fields 2>&1
837
+ )" || fail "source-registry fields should be readable"
838
+
839
+ assert_text_contains "$output" "source_id"
840
+ assert_text_contains "$output" "source_label"
841
+ assert_text_contains "$output" "source_category"
842
+ assert_text_contains "$output" "input_mode"
843
+ assert_text_contains "$output" "raw_dir"
844
+ assert_text_contains "$output" "original_ref"
845
+ assert_text_contains "$output" "ingest_text"
846
+ assert_text_contains "$output" "adapter_name"
847
+ assert_text_contains "$output" "fallback_hint"
848
+ }
849
+
850
+ test_source_registry_groups_core_optional_and_manual_sources() {
851
+ local output
852
+
853
+ output="$(
854
+ bash "$REPO_ROOT/scripts/source-registry.sh" list 2>&1
855
+ )" || fail "source-registry list should be readable"
856
+
857
+ assert_text_contains "$output" "core_builtin"
858
+ assert_text_contains "$output" "optional_adapter"
859
+ assert_text_contains "$output" "manual_only"
860
+ assert_text_contains "$output" "local_pdf"
861
+ assert_text_contains "$output" "plain_text"
862
+ assert_text_contains "$output" "web_article"
863
+ assert_text_contains "$output" "wechat_article"
864
+ assert_text_contains "$output" "xiaohongshu_post"
865
+ }
866
+
867
+ test_source_registry_exposes_install_dependency_groups() {
868
+ local bundled_output install_time_output
869
+
870
+ bundled_output="$(
871
+ bash "$REPO_ROOT/scripts/source-registry.sh" unique-dependencies bundled 2>&1
872
+ )" || fail "source-registry should list bundled dependencies"
873
+
874
+ install_time_output="$(
875
+ bash "$REPO_ROOT/scripts/source-registry.sh" unique-dependencies install_time 2>&1
876
+ )" || fail "source-registry should list install-time dependencies"
877
+
878
+ assert_text_contains "$bundled_output" "baoyu-url-to-markdown"
879
+ assert_text_contains "$bundled_output" "youtube-transcript"
880
+ assert_text_contains "$install_time_output" "wechat-article-to-markdown"
881
+ }
882
+
883
+ test_source_registry_validation_passes() {
884
+ bash "$REPO_ROOT/scripts/source-registry.sh" validate > /dev/null 2>&1 \
885
+ || fail "source-registry validate should succeed"
886
+ }
887
+
888
+ test_source_registry_matches_urls_and_files_from_shared_table() {
889
+ local output
890
+
891
+ output="$(
892
+ bash "$REPO_ROOT/scripts/source-registry.sh" match-url "https://x.com/openai/status/1" 2>&1
893
+ )" || fail "source-registry should match X/Twitter URLs"
894
+ assert_text_contains "$output" "x_twitter"
895
+
896
+ output="$(
897
+ bash "$REPO_ROOT/scripts/source-registry.sh" match-url "https://mp.weixin.qq.com/s/example" 2>&1
898
+ )" || fail "source-registry should match WeChat URLs"
899
+ assert_text_contains "$output" "wechat_article"
900
+
901
+ output="$(
902
+ bash "$REPO_ROOT/scripts/source-registry.sh" match-url "https://example.com/post" 2>&1
903
+ )" || fail "source-registry should match generic web URLs"
904
+ assert_text_contains "$output" "web_article"
905
+
906
+ output="$(
907
+ bash "$REPO_ROOT/scripts/source-registry.sh" match-file "/tmp/example.md" 2>&1
908
+ )" || fail "source-registry should match local document files"
909
+ assert_text_contains "$output" "local_document"
910
+
911
+ output="$(
912
+ bash "$REPO_ROOT/scripts/source-registry.sh" match-file "/tmp/paper.pdf" 2>&1
913
+ )" || fail "source-registry should match PDF files"
914
+ assert_text_contains "$output" "local_pdf"
915
+ }
916
+
917
+ test_legacy_wiki_defaults_missing_fields_without_forcing_migration() {
918
+ local tmp_dir wiki_root output
919
+ tmp_dir="$(mktemp -d)"
920
+ trap 'rm -rf "$tmp_dir"' RETURN
921
+
922
+ wiki_root="$tmp_dir/legacy-wiki"
923
+ make_legacy_wiki "$wiki_root"
924
+
925
+ output="$(
926
+ bash "$REPO_ROOT/scripts/wiki-compat.sh" inspect "$wiki_root" 2>&1
927
+ )" || fail "legacy wiki inspect should succeed without migration"
928
+
929
+ assert_text_contains "$output" "schema_version=1.0"
930
+ assert_text_contains "$output" "language=zh"
931
+ assert_text_contains "$output" "migration_required=no"
932
+ assert_text_contains "$output" "missing_optional_raw_dirs=raw/xiaohongshu,raw/zhihu"
933
+ assert_text_contains "$output" "purpose_file=missing"
934
+ assert_text_contains "$output" "cache_file=missing"
935
+
936
+ bash "$REPO_ROOT/scripts/wiki-compat.sh" validate "$wiki_root" > /dev/null 2>&1 \
937
+ || fail "legacy wiki validate should accept the old layout"
938
+ }
939
+
940
+ test_legacy_wiki_lazily_creates_new_source_dirs_without_moving_old_materials() {
941
+ local tmp_dir wiki_root output
942
+ tmp_dir="$(mktemp -d)"
943
+ trap 'rm -rf "$tmp_dir"' RETURN
944
+
945
+ wiki_root="$tmp_dir/legacy-wiki"
946
+ make_legacy_wiki "$wiki_root"
947
+ printf '旧素材\n' > "$wiki_root/raw/articles/2026-04-01-old-source.md"
948
+
949
+ bash "$REPO_ROOT/scripts/wiki-compat.sh" ensure-source-dir "$wiki_root" xiaohongshu_post > /dev/null 2>&1 \
950
+ || fail "legacy wiki should lazily create missing source directories"
951
+
952
+ assert_path_exists "$wiki_root/raw/xiaohongshu"
953
+ assert_path_exists "$wiki_root/raw/articles/2026-04-01-old-source.md"
954
+
955
+ output="$(
956
+ bash "$REPO_ROOT/scripts/wiki-compat.sh" inspect "$wiki_root" 2>&1
957
+ )" || fail "inspect should still succeed after lazily creating a source directory"
958
+
959
+ assert_text_contains "$output" "missing_optional_raw_dirs=raw/zhihu"
960
+ }
961
+
962
+ test_new_wiki_compat_reports_purpose_and_cache_present() {
963
+ local tmp_dir wiki_root output
964
+ tmp_dir="$(mktemp -d)"
965
+ trap 'rm -rf "$tmp_dir"' RETURN
966
+
967
+ wiki_root="$tmp_dir/new-wiki"
968
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "新知识库" "中文" > /dev/null
969
+
970
+ output="$(
971
+ bash "$REPO_ROOT/scripts/wiki-compat.sh" inspect "$wiki_root" 2>&1
972
+ )" || fail "new wiki inspect should succeed"
973
+
974
+ assert_text_contains "$output" "purpose_file=present"
975
+ assert_text_contains "$output" "cache_file=present"
976
+ }
977
+
978
+ test_readme_aligns_source_boundary_to_registry() {
979
+ assert_file_contains "$REPO_ROOT/README.md" "## 素材来源"
980
+ assert_file_contains "$REPO_ROOT/README.md" "| 核心 |"
981
+ assert_file_contains "$REPO_ROOT/README.md" "| 可选 |"
982
+ assert_file_contains "$REPO_ROOT/README.md" "| 手动 |"
983
+ assert_file_contains "$REPO_ROOT/README.md" "PDF"
984
+ assert_file_contains "$REPO_ROOT/README.md" "Markdown"
985
+ assert_file_contains "$REPO_ROOT/README.md" "HTML"
986
+ assert_file_contains "$REPO_ROOT/README.md" "纯文本粘贴"
987
+ assert_file_contains "$REPO_ROOT/README.md" "网页文章"
988
+ assert_file_contains "$REPO_ROOT/README.md" "X/Twitter"
989
+ assert_file_contains "$REPO_ROOT/README.md" "微信公众号"
990
+ assert_file_contains "$REPO_ROOT/README.md" "YouTube"
991
+ assert_file_contains "$REPO_ROOT/README.md" "知乎"
992
+ assert_file_contains "$REPO_ROOT/README.md" "小红书"
993
+ }
994
+
995
+ test_skill_status_and_ingest_align_to_registry() {
996
+ assert_file_contains "$REPO_ROOT/SKILL.md" "scripts/source-registry.sh list"
997
+ assert_file_contains "$REPO_ROOT/SKILL.md" "scripts/source-registry.sh get"
998
+ assert_file_contains "$REPO_ROOT/SKILL.md" "source_id"
999
+ assert_file_contains "$REPO_ROOT/SKILL.md" "recovery_action"
1000
+ assert_file_contains "$REPO_ROOT/SKILL.md" "install_hint"
1001
+ assert_file_contains "$REPO_ROOT/SKILL.md" '按来源总表中的 `source_label` 和 `raw_dir`'
1002
+ assert_file_contains "$REPO_ROOT/SKILL.md" "外挂状态直接使用"
1003
+ }
1004
+
1005
+ test_schema_template_aligns_source_boundary_to_registry() {
1006
+ assert_file_contains "$REPO_ROOT/templates/schema-template.md" "核心主线"
1007
+ assert_file_contains "$REPO_ROOT/templates/schema-template.md" "可选外挂"
1008
+ assert_file_contains "$REPO_ROOT/templates/schema-template.md" "手动入口"
1009
+ assert_registry_labels_present_in_file "$REPO_ROOT/templates/schema-template.md" "core_builtin"
1010
+ assert_registry_labels_present_in_file "$REPO_ROOT/templates/schema-template.md" "optional_adapter"
1011
+ assert_registry_labels_present_in_file "$REPO_ROOT/templates/schema-template.md" "manual_only"
1012
+ }
1013
+
1014
+ test_install_prints_source_boundary_from_registry() {
1015
+ local tmp_dir output
1016
+ tmp_dir="$(mktemp -d)"
1017
+ trap 'rm -rf "$tmp_dir"' RETURN
1018
+
1019
+ mkdir -p "$tmp_dir/home/.claude/skills"
1020
+
1021
+ output="$(
1022
+ HOME="$tmp_dir/home" \
1023
+ bash "$REPO_ROOT/install.sh" --platform claude --dry-run 2>&1
1024
+ )" || fail "install.sh dry-run should print shared source boundary"
1025
+
1026
+ assert_text_contains "$output" "来源边界"
1027
+ assert_text_contains "$output" "核心主线"
1028
+ assert_text_contains "$output" "可选外挂"
1029
+ assert_text_contains "$output" "手动入口"
1030
+ assert_registry_labels_present_in_text "$output" "core_builtin"
1031
+ assert_registry_labels_present_in_text "$output" "optional_adapter"
1032
+ assert_registry_labels_present_in_text "$output" "manual_only"
1033
+ assert_text_contains "$output" "--with-optional-adapters"
1034
+ }
1035
+
1036
+ test_install_warns_when_managed_source_is_missing() {
1037
+ assert_file_contains "$REPO_ROOT/install.sh" "安装源文件缺失,跳过"
1038
+ }
1039
+
1040
+ test_validate_step1_no_args_exits_with_usage() {
1041
+ local output
1042
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" 2>&1)"; then
1043
+ fail "validate-step1.sh should exit non-zero with no args"
1044
+ fi
1045
+ assert_text_contains "$output" "usage"
1046
+ }
1047
+
1048
+ test_validate_step1_valid_json_passes() {
1049
+ local tmp_dir
1050
+ tmp_dir="$(mktemp -d)"
1051
+ trap 'rm -rf "$tmp_dir"' RETURN
1052
+ printf '%s\n' '{"entities":[{"name":"test","type":"concept","confidence":"EXTRACTED"}],"topics":[],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1053
+ > "$tmp_dir/step1.json"
1054
+ bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/step1.json" \
1055
+ || fail "validate-step1.sh should pass with valid JSON"
1056
+ }
1057
+
1058
+ test_validate_step1_missing_confidence_fails() {
1059
+ local tmp_dir output
1060
+ tmp_dir="$(mktemp -d)"
1061
+ trap 'rm -rf "$tmp_dir"' RETURN
1062
+ printf '%s\n' '{"entities":[{"name":"test","type":"concept"}],"topics":[],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1063
+ > "$tmp_dir/step1.json"
1064
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/step1.json" 2>&1)"; then
1065
+ fail "validate-step1.sh should fail when confidence is missing"
1066
+ fi
1067
+ assert_text_contains "$output" "missing required fields"
1068
+ }
1069
+
1070
+ test_validate_step1_invalid_confidence_value_fails() {
1071
+ local tmp_dir output
1072
+ tmp_dir="$(mktemp -d)"
1073
+ trap 'rm -rf "$tmp_dir"' RETURN
1074
+ printf '%s\n' '{"entities":[{"name":"test","type":"concept","confidence":"HIGH"}],"topics":[],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1075
+ > "$tmp_dir/step1.json"
1076
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/step1.json" 2>&1)"; then
1077
+ fail "validate-step1.sh should fail with invalid confidence value"
1078
+ fi
1079
+ assert_text_contains "$output" "HIGH"
1080
+ }
1081
+
1082
+ test_validate_step1_entities_not_array_fails() {
1083
+ local tmp_dir output
1084
+ tmp_dir="$(mktemp -d)"
1085
+ trap 'rm -rf "$tmp_dir"' RETURN
1086
+ printf '%s\n' '{"entities":{},"topics":[],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1087
+ > "$tmp_dir/step1.json"
1088
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/step1.json" 2>&1)"; then
1089
+ fail "validate-step1.sh should fail when entities is not an array"
1090
+ fi
1091
+ assert_text_contains "$output" "entities"
1092
+ }
1093
+
1094
+ test_validate_step1_empty_entities_array_passes() {
1095
+ local tmp_dir
1096
+ tmp_dir="$(mktemp -d)"
1097
+ trap 'rm -rf "$tmp_dir"' RETURN
1098
+ printf '%s\n' '{"entities":[],"topics":[],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1099
+ > "$tmp_dir/step1.json"
1100
+ bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/step1.json" \
1101
+ || fail "validate-step1.sh should pass with empty entities array"
1102
+ }
1103
+
1104
+ test_validate_step1_rejects_non_object_items() {
1105
+ local tmp_dir output
1106
+ tmp_dir="$(mktemp -d)"
1107
+ trap 'rm -rf "$tmp_dir"' RETURN
1108
+
1109
+ printf '%s\n' '{"entities":["not-an-object"],"topics":[],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1110
+ > "$tmp_dir/entity.json"
1111
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/entity.json" 2>&1)"; then
1112
+ fail "validate-step1.sh should fail when an entity item is not an object"
1113
+ fi
1114
+ assert_text_contains "$output" "entity"
1115
+
1116
+ printf '%s\n' '{"entities":[],"topics":["not-an-object"],"connections":[],"contradictions":[],"new_vs_existing":{}}' \
1117
+ > "$tmp_dir/topic.json"
1118
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/topic.json" 2>&1)"; then
1119
+ fail "validate-step1.sh should fail when a topic item is not an object"
1120
+ fi
1121
+ assert_text_contains "$output" "topic"
1122
+
1123
+ printf '%s\n' '{"entities":[],"topics":[],"connections":["not-an-object"],"contradictions":[],"new_vs_existing":{}}' \
1124
+ > "$tmp_dir/connection.json"
1125
+ if output="$(bash "$REPO_ROOT/scripts/validate-step1.sh" "$tmp_dir/connection.json" 2>&1)"; then
1126
+ fail "validate-step1.sh should fail when a connection item is not an object"
1127
+ fi
1128
+ assert_text_contains "$output" "connection"
1129
+ }
1130
+
1131
+ test_lint_runner_accepts_index_alias_entries() {
1132
+ local tmp_dir output
1133
+ tmp_dir="$(mktemp -d)"
1134
+ trap 'rm -rf "$tmp_dir"' RETURN
1135
+
1136
+ mkdir -p "$tmp_dir/wiki/entities"
1137
+ printf '# Index\n\n## 实体页\n- [[Real|真实页面]]\n' > "$tmp_dir/index.md"
1138
+ printf '# Real\n' > "$tmp_dir/wiki/entities/Real.md"
1139
+
1140
+ output="$(bash "$REPO_ROOT/scripts/lint-runner.sh" "$tmp_dir" 2>&1)" \
1141
+ || fail "lint-runner.sh should run on alias index fixture"
1142
+ assert_text_not_contains "$output" "未收录: entities/Real"
1143
+ }
1144
+
1145
+ test_lint_fix_does_not_require_macos_sed_in_place() {
1146
+ local tmp_dir wiki_root real_sed
1147
+ tmp_dir="$(mktemp -d)"
1148
+ trap 'rm -rf "$tmp_dir"' RETURN
1149
+ wiki_root="$tmp_dir/wiki-root"
1150
+ real_sed="$(command -v sed)"
1151
+
1152
+ mkdir -p "$wiki_root/wiki/entities" "$tmp_dir/bin"
1153
+ printf '# Index\n\n## 实体页\n' > "$wiki_root/index.md"
1154
+ printf '# Lonely\n' > "$wiki_root/wiki/entities/Lonely.md"
1155
+
1156
+ make_stub "$tmp_dir/bin/sed" "#!/bin/sh
1157
+ if [ \"\${1:-}\" = \"-i\" ] && [ \"\${2+x}\" = \"x\" ] && [ \"\$2\" = \"\" ]; then
1158
+ echo 'sed -i empty extension is not portable' >&2
1159
+ exit 2
1160
+ fi
1161
+ exec \"$real_sed\" \"\$@\"
1162
+ "
1163
+
1164
+ PATH="$tmp_dir/bin:$PATH" bash "$REPO_ROOT/scripts/lint-fix.sh" "$wiki_root" > /dev/null \
1165
+ || fail "lint-fix.sh should not depend on macOS sed -i syntax"
1166
+ assert_file_contains "$wiki_root/index.md" "- [[Lonely]]"
1167
+ }
1168
+
1169
+ test_skill_md_ingest_has_confidence_assignment_rules() {
1170
+ local section
1171
+ section="$(sed -n '/## 工作流 2:ingest/,/## 工作流 3:batch-ingest/p' "$REPO_ROOT/SKILL.md")"
1172
+ assert_text_contains "$section" "EXTRACTED:信息直接出现在原文里"
1173
+ assert_text_contains "$section" "INFERRED:"
1174
+ assert_text_contains "$section" "AMBIGUOUS:"
1175
+ assert_text_contains "$section" "UNVERIFIED:"
1176
+ assert_text_contains "$section" "validate-step1.sh"
1177
+ }
1178
+
1179
+ test_skill_md_has_crystallize_workflow_and_route() {
1180
+ local route_section crystallize_section
1181
+ route_section="$(sed -n '/## 工作流路由/,/## 通用前置检查/p' "$REPO_ROOT/SKILL.md")"
1182
+ crystallize_section="$(sed -n '/## 工作流 10:crystallize/,$p' "$REPO_ROOT/SKILL.md")"
1183
+ assert_text_contains "$route_section" "crystallize"
1184
+ assert_text_contains "$crystallize_section" "wiki/synthesis/sessions/"
1185
+ assert_text_contains "$crystallize_section" "INFERRED"
1186
+ }
1187
+
1188
+ test_init_creates_synthesis_sessions_subdir() {
1189
+ local tmp_dir
1190
+ tmp_dir="$(mktemp -d)"
1191
+ trap 'rm -rf "$tmp_dir"' RETURN
1192
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$tmp_dir/wiki" "测试知识库" > /dev/null 2>&1 \
1193
+ || fail "init-wiki.sh should succeed"
1194
+ assert_path_exists "$tmp_dir/wiki/wiki/synthesis/sessions"
1195
+ }
1196
+
1197
+ test_create_source_page_writes_and_updates_cache() {
1198
+ local tmp_dir wiki_root raw_file content_file output
1199
+ tmp_dir="$(mktemp -d)"
1200
+ trap 'rm -rf "$tmp_dir"' RETURN
1201
+
1202
+ wiki_root="$tmp_dir/wiki"
1203
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "缓存测试" "中文" > /dev/null
1204
+
1205
+ raw_file="$wiki_root/raw/articles/test-article.md"
1206
+ printf '素材原始内容\n' > "$raw_file"
1207
+
1208
+ content_file="$tmp_dir/content.tmp"
1209
+ printf '# 测试摘要页\n\n这是测试内容。\n' > "$content_file"
1210
+
1211
+ output="$(
1212
+ bash "$REPO_ROOT/scripts/create-source-page.sh" "$raw_file" "wiki/sources/test-article.md" "$content_file" 2>&1
1213
+ )" || fail "create-source-page.sh should succeed"
1214
+ assert_text_contains "$output" "SUCCESS"
1215
+
1216
+ # 验证页面文件已写入
1217
+ assert_path_exists "$wiki_root/wiki/sources/test-article.md"
1218
+ assert_file_contains "$wiki_root/wiki/sources/test-article.md" "这是测试内容"
1219
+
1220
+ # 验证缓存已更新(check 返回 HIT)
1221
+ output="$(
1222
+ bash "$REPO_ROOT/scripts/cache.sh" check "$raw_file" 2>&1
1223
+ )"
1224
+ [ "$output" = "HIT" ] || fail "Expected HIT after create-source-page, got: $output"
1225
+ }
1226
+
1227
+ test_create_source_page_rollback_on_cache_failure() {
1228
+ local tmp_dir wiki_root raw_file content_file
1229
+ tmp_dir="$(mktemp -d)"
1230
+ trap 'rm -rf "$tmp_dir"' RETURN
1231
+
1232
+ wiki_root="$tmp_dir/wiki"
1233
+ mkdir -p "$wiki_root/raw/articles" "$wiki_root/wiki/sources"
1234
+ # 使用损坏的 cache 文件(不是合法 JSON),让 cache update 失败
1235
+ printf 'not json\n' > "$wiki_root/.wiki-cache.json"
1236
+ printf '# Schema\n' > "$wiki_root/.wiki-schema.md"
1237
+
1238
+ raw_file="$wiki_root/raw/articles/test-article.md"
1239
+ printf '素材原始内容\n' > "$raw_file"
1240
+
1241
+ content_file="$tmp_dir/content.tmp"
1242
+ printf '# 测试摘要页\n' > "$content_file"
1243
+
1244
+ bash "$REPO_ROOT/scripts/create-source-page.sh" "$raw_file" "wiki/sources/test-article.md" "$content_file" 2>/dev/null \
1245
+ && fail "create-source-page.sh should fail when cache update fails"
1246
+
1247
+ # 验证页面文件已被回滚删除
1248
+ [ ! -f "$wiki_root/wiki/sources/test-article.md" ] || fail "Output file should be rolled back on cache failure"
1249
+ }
1250
+
1251
+ test_cache_check_self_heals_with_matching_stem() {
1252
+ local tmp_dir wiki_root raw_file output
1253
+ tmp_dir="$(mktemp -d)"
1254
+ trap 'rm -rf "$tmp_dir"' RETURN
1255
+
1256
+ wiki_root="$tmp_dir/wiki"
1257
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "缓存测试" "中文" > /dev/null
1258
+
1259
+ raw_file="$wiki_root/raw/articles/rlhf-paper.md"
1260
+ printf 'RLHF 论文内容\n' > "$raw_file"
1261
+
1262
+ # 直接写 source 页面(不通过脚本,模拟 AI 忘了 update)
1263
+ cat > "$wiki_root/wiki/sources/rlhf-paper.md" <<'SRCEOF'
1264
+ ---
1265
+ source_path: raw/articles/rlhf-paper.md
1266
+ ---
1267
+ # RLHF 摘要
1268
+ SRCEOF
1269
+
1270
+ # cache check 应该自愈
1271
+ output="$(
1272
+ bash "$REPO_ROOT/scripts/cache.sh" check "$raw_file" 2>&1
1273
+ )"
1274
+ [ "$output" = "HIT(repaired)" ] || fail "Expected HIT(repaired) for self-heal, got: $output"
1275
+
1276
+ # 再次 check 应返回普通 HIT
1277
+ output="$(
1278
+ bash "$REPO_ROOT/scripts/cache.sh" check "$raw_file" 2>&1
1279
+ )"
1280
+ [ "$output" = "HIT" ] || fail "Expected HIT after repair, got: $output"
1281
+ }
1282
+
1283
+ test_cache_check_miss_no_source_when_page_deleted() {
1284
+ local tmp_dir wiki_root raw_file output
1285
+ tmp_dir="$(mktemp -d)"
1286
+ trap 'rm -rf "$tmp_dir"' RETURN
1287
+
1288
+ wiki_root="$tmp_dir/wiki"
1289
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "缓存测试" "中文" > /dev/null
1290
+
1291
+ raw_file="$wiki_root/raw/articles/example.md"
1292
+ printf '原始内容\n' > "$raw_file"
1293
+
1294
+ # 正常建 cache
1295
+ printf '# 来源页\n' > "$wiki_root/wiki/sources/example.md"
1296
+ bash "$REPO_ROOT/scripts/cache.sh" update "$raw_file" "wiki/sources/example.md" > /dev/null 2>&1
1297
+
1298
+ # 删掉 source 页面
1299
+ rm "$wiki_root/wiki/sources/example.md"
1300
+
1301
+ output="$(
1302
+ bash "$REPO_ROOT/scripts/cache.sh" check "$raw_file" 2>&1
1303
+ )"
1304
+ [ "$output" = "MISS:no_source" ] || fail "Expected MISS:no_source, got: $output"
1305
+ }
1306
+
1307
+ test_cache_check_miss_hash_changed_when_content_differs() {
1308
+ local tmp_dir wiki_root raw_file output
1309
+ tmp_dir="$(mktemp -d)"
1310
+ trap 'rm -rf "$tmp_dir"' RETURN
1311
+
1312
+ wiki_root="$tmp_dir/wiki"
1313
+ bash "$REPO_ROOT/scripts/init-wiki.sh" "$wiki_root" "缓存测试" "中文" > /dev/null
1314
+
1315
+ raw_file="$wiki_root/raw/articles/example.md"
1316
+ printf '原始内容 v1\n' > "$raw_file"
1317
+
1318
+ printf '# 来源页\n' > "$wiki_root/wiki/sources/example.md"
1319
+ bash "$REPO_ROOT/scripts/cache.sh" update "$raw_file" "wiki/sources/example.md" > /dev/null 2>&1
1320
+
1321
+ # 修改素材内容
1322
+ printf '原始内容 v2 已变化\n' > "$raw_file"
1323
+
1324
+ output="$(
1325
+ bash "$REPO_ROOT/scripts/cache.sh" check "$raw_file" 2>&1
1326
+ )"
1327
+ [ "$output" = "MISS:hash_changed" ] || fail "Expected MISS:hash_changed, got: $output"
1328
+ }
1329
+
1330
+ test_skill_md_ingest_uses_create_source_page() {
1331
+ local section
1332
+ section="$(sed -n '/## 工作流 2:ingest/,/## 工作流 3:batch-ingest/p' "$REPO_ROOT/SKILL.md")"
1333
+
1334
+ assert_text_contains "$section" "create-source-page.sh"
1335
+ assert_text_contains "$section" "MISS:no_entry"
1336
+ assert_text_contains "$section" "MISS:hash_changed"
1337
+ assert_text_contains "$section" "MISS:no_source"
1338
+ assert_text_contains "$section" "HIT(repaired)"
1339
+ }
1340
+
1341
+ test_skill_md_step12_does_not_call_cache_update() {
1342
+ local section step12
1343
+ section="$(sed -n '/## 工作流 2:ingest/,/## 工作流 3:batch-ingest/p' "$REPO_ROOT/SKILL.md")"
1344
+ step12="$(printf '%s' "$section" | sed -n '/^12\. \*\*更新 log\.md/,/^13\./p')"
1345
+
1346
+ # Step 12 不应包含可执行的 cache.sh update 代码块(只在说明文字里提到)
1347
+ # 检查没有以 "bash" 开头并含 "cache.sh update" 的行
1348
+ if printf '%s' "$step12" | grep -E 'bash.*cache\.sh update' > /dev/null; then
1349
+ fail "Step 12 should not contain executable cache.sh update call"
1350
+ fi
1351
+ # 应提到缓存已由 create-source-page.sh 完成
1352
+ assert_text_contains "$step12" "create-source-page.sh"
1353
+ }
1354
+
1355
+ test_setup_runs_on_bash_3_2
1356
+ test_install_with_optional_adapters_bootstraps_dependencies
1357
+ test_install_dry_run_for_claude
1358
+ test_install_help_does_not_require_python
1359
+ test_install_dry_run_does_not_require_python
1360
+ test_install_auto_refuses_ambiguous_platforms
1361
+ test_upgrade_auto_refuses_ambiguous_installed_platforms
1362
+ test_upgrade_uses_explicit_target_dir
1363
+ test_upgrade_fails_when_explicit_target_dir_is_missing
1364
+ test_upgrade_refreshes_claude_companion_skill
1365
+ test_install_openclaw_copies_bundle
1366
+ test_install_hermes_copies_bundle
1367
+ test_init_fills_language_placeholder
1368
+ test_phase1_templates_exist
1369
+ test_init_creates_purpose_and_cache_files
1370
+ test_cache_script_handles_miss_hit_and_invalidate
1371
+ test_skill_md_phase2_init_mentions_purpose_and_cache
1372
+ test_skill_md_phase2_ingest_mentions_two_step_cache_and_confidence
1373
+ test_skill_md_phase2_batch_ingest_mentions_cache_skip_summary
1374
+ test_skill_md_phase2_status_mentions_purpose_presence
1375
+ test_skill_md_phase2_has_delete_workflow_and_route
1376
+ test_delete_helper_scans_reference_files
1377
+ test_skill_md_phase3_query_mentions_persistence_and_duplicate_handling
1378
+ test_hook_session_start_outputs_context_when_wiki_exists
1379
+ test_hook_session_start_returns_empty_json_without_wiki
1380
+ test_hook_session_start_without_wiki_does_not_require_python
1381
+ test_install_registers_and_uninstalls_session_start_hook
1382
+ test_platform_entries_mention_hook_and_wiki_context
1383
+ test_root_entries_explain_core_only_optional_and_target_dir
1384
+ test_skill_md_phase5_lint_mentions_confidence_audit
1385
+ test_changelog_mentions_wiki_core_upgrades
1386
+ test_readme_sections
1387
+ test_claude_upgrade_companion_source_exists
1388
+ test_uv_tool_install_failure_is_graceful
1389
+ test_skill_md_routes_wechat_to_new_tool
1390
+ test_templates_have_no_empty_links
1391
+ test_batch_ingest_has_step_two
1392
+ test_english_templates_exist_and_have_placeholders
1393
+ test_english_templates_have_no_empty_links
1394
+ test_skill_md_has_shared_preflight_and_language_rules
1395
+ test_skill_md_uses_external_english_templates_and_no_english_output_blocks
1396
+ test_setup_wrapper_is_marked_deprecated
1397
+ test_source_registry_contract_is_frozen
1398
+ test_source_registry_groups_core_optional_and_manual_sources
1399
+ test_source_registry_exposes_install_dependency_groups
1400
+ test_source_registry_validation_passes
1401
+ test_source_registry_matches_urls_and_files_from_shared_table
1402
+ test_legacy_wiki_defaults_missing_fields_without_forcing_migration
1403
+ test_legacy_wiki_lazily_creates_new_source_dirs_without_moving_old_materials
1404
+ test_new_wiki_compat_reports_purpose_and_cache_present
1405
+ test_readme_aligns_source_boundary_to_registry
1406
+ test_skill_status_and_ingest_align_to_registry
1407
+ test_schema_template_aligns_source_boundary_to_registry
1408
+ test_install_prints_source_boundary_from_registry
1409
+ test_install_warns_when_managed_source_is_missing
1410
+ test_validate_step1_no_args_exits_with_usage
1411
+ test_validate_step1_valid_json_passes
1412
+ test_validate_step1_missing_confidence_fails
1413
+ test_validate_step1_invalid_confidence_value_fails
1414
+ test_validate_step1_entities_not_array_fails
1415
+ test_validate_step1_empty_entities_array_passes
1416
+ test_validate_step1_rejects_non_object_items
1417
+ test_lint_runner_accepts_index_alias_entries
1418
+ test_lint_fix_does_not_require_macos_sed_in_place
1419
+ test_skill_md_ingest_has_confidence_assignment_rules
1420
+ test_skill_md_has_crystallize_workflow_and_route
1421
+ test_init_creates_synthesis_sessions_subdir
1422
+ test_create_source_page_writes_and_updates_cache
1423
+ test_create_source_page_rollback_on_cache_failure
1424
+ test_cache_check_self_heals_with_matching_stem
1425
+ test_cache_check_miss_no_source_when_page_deleted
1426
+ test_cache_check_miss_hash_changed_when_content_differs
1427
+ test_skill_md_ingest_uses_create_source_page
1428
+ test_skill_md_step12_does_not_call_cache_update
1429
+
1430
+ # ─── 交互式图谱测试 ────────────────────────────────────────────
1431
+
1432
+ GRAPH_DATA_SAMPLE="tests/fixtures/graph-data-sample-wiki"
1433
+ GRAPH_DATA_EMPTY="tests/fixtures/graph-data-empty-wiki"
1434
+ GRAPH_HTML_BASIC="tests/fixtures/graph-interactive-basic"
1435
+ trap - RETURN
1436
+ source "$REPO_ROOT/tests/lib/graph-html-engine-helpers.sh"
1437
+
1438
+ test_graph_data_sample_wiki_matches_expected() {
1439
+ local tmp_dir
1440
+ tmp_dir="$(mktemp -d)"
1441
+ trap 'rm -rf "$tmp_dir"' RETURN
1442
+
1443
+ LLM_WIKI_TEST_MODE=1 \
1444
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1445
+ "$REPO_ROOT/$GRAPH_DATA_SAMPLE" \
1446
+ "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
1447
+ || fail "build-graph-data.sh should succeed on sample wiki"
1448
+
1449
+ jq 'del(.nodes[].source_path)' "$tmp_dir/graph-data.json" > "$tmp_dir/normalized.json"
1450
+ diff "$tmp_dir/normalized.json" "$REPO_ROOT/tests/expected/graph-data-sample.json" \
1451
+ || fail "sample wiki graph-data output differs from expected"
1452
+ }
1453
+
1454
+ test_graph_data_empty_wiki_matches_expected() {
1455
+ local tmp_dir
1456
+ tmp_dir="$(mktemp -d)"
1457
+ trap 'rm -rf "$tmp_dir"' RETURN
1458
+
1459
+ LLM_WIKI_TEST_MODE=1 \
1460
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1461
+ "$REPO_ROOT/$GRAPH_DATA_EMPTY" \
1462
+ "$tmp_dir/graph-data.json" > /dev/null 2>&1 \
1463
+ || fail "build-graph-data.sh should succeed on empty wiki"
1464
+
1465
+ diff "$tmp_dir/graph-data.json" "$REPO_ROOT/tests/expected/graph-data-empty.json" \
1466
+ || fail "empty wiki graph-data output differs from expected"
1467
+ }
1468
+
1469
+ test_graph_data_test_mode_is_stable() {
1470
+ local tmp_dir
1471
+ tmp_dir="$(mktemp -d)"
1472
+ trap 'rm -rf "$tmp_dir"' RETURN
1473
+
1474
+ LLM_WIKI_TEST_MODE=1 \
1475
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1476
+ "$REPO_ROOT/$GRAPH_DATA_SAMPLE" \
1477
+ "$tmp_dir/run1.json" > /dev/null 2>&1
1478
+
1479
+ LLM_WIKI_TEST_MODE=1 \
1480
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1481
+ "$REPO_ROOT/$GRAPH_DATA_SAMPLE" \
1482
+ "$tmp_dir/run2.json" > /dev/null 2>&1
1483
+
1484
+ diff "$tmp_dir/run1.json" "$tmp_dir/run2.json" \
1485
+ || fail "TEST_MODE output should be identical across runs"
1486
+ }
1487
+
1488
+ test_graph_data_has_three_confidence_types() {
1489
+ local tmp_dir edges
1490
+ tmp_dir="$(mktemp -d)"
1491
+ trap 'rm -rf "$tmp_dir"' RETURN
1492
+
1493
+ LLM_WIKI_TEST_MODE=1 \
1494
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1495
+ "$REPO_ROOT/$GRAPH_DATA_SAMPLE" \
1496
+ "$tmp_dir/graph-data.json" > /dev/null 2>&1
1497
+
1498
+ edges=$(jq -r '.edges[].type' "$tmp_dir/graph-data.json" | sort -u)
1499
+ assert_text_contains "$edges" "EXTRACTED"
1500
+ assert_text_contains "$edges" "INFERRED"
1501
+ assert_text_contains "$edges" "AMBIGUOUS"
1502
+ }
1503
+
1504
+ test_graph_data_community_clustering() {
1505
+ local tmp_dir arch_nodes finetune_nodes paper_comm attention_comm
1506
+ tmp_dir="$(mktemp -d)"
1507
+ trap 'rm -rf "$tmp_dir"' RETURN
1508
+
1509
+ LLM_WIKI_TEST_MODE=1 \
1510
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1511
+ "$REPO_ROOT/$GRAPH_DATA_SAMPLE" \
1512
+ "$tmp_dir/graph-data.json" > /dev/null 2>&1
1513
+
1514
+ arch_nodes=$(jq -r '.nodes[] | select(.community == "arch") | .id' "$tmp_dir/graph-data.json" | sort | tr '\n' ' ')
1515
+ assert_text_contains "$arch_nodes" "Decoder"
1516
+ assert_text_contains "$arch_nodes" "Encoder"
1517
+ assert_text_contains "$arch_nodes" "Transformer"
1518
+ assert_text_contains "$arch_nodes" "arch"
1519
+
1520
+ finetune_nodes=$(jq -r '.nodes[] | select(.community == "finetune") | .id' "$tmp_dir/graph-data.json" | sort | tr '\n' ' ')
1521
+ assert_text_contains "$finetune_nodes" "GPT"
1522
+ assert_text_contains "$finetune_nodes" "finetune"
1523
+
1524
+ attention_comm=$(jq -r '.nodes[] | select(.community == "Attention") | .id' "$tmp_dir/graph-data.json" | sort | tr '\n' ' ')
1525
+ assert_text_contains "$attention_comm" "Attention"
1526
+ assert_text_contains "$attention_comm" "paper"
1527
+
1528
+ paper_comm=$(jq -r '.nodes[] | select(.id == "paper") | .community' "$tmp_dir/graph-data.json")
1529
+ [ "$paper_comm" = "Attention" ] || fail "Expected paper community to be Attention, got: $paper_comm"
1530
+ }
1531
+
1532
+ test_graph_data_empty_wiki_has_zero_nodes_and_edges() {
1533
+ local tmp_dir nodes_count edges_count
1534
+ tmp_dir="$(mktemp -d)"
1535
+ trap 'rm -rf "$tmp_dir"' RETURN
1536
+
1537
+ LLM_WIKI_TEST_MODE=1 \
1538
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" \
1539
+ "$REPO_ROOT/$GRAPH_DATA_EMPTY" \
1540
+ "$tmp_dir/graph-data.json" > /dev/null 2>&1
1541
+
1542
+ nodes_count=$(jq '.meta.total_nodes' "$tmp_dir/graph-data.json")
1543
+ edges_count=$(jq '.meta.total_edges' "$tmp_dir/graph-data.json")
1544
+ [ "$nodes_count" = "0" ] || fail "Expected 0 nodes in empty wiki, got: $nodes_count"
1545
+ [ "$edges_count" = "0" ] || fail "Expected 0 edges in empty wiki, got: $edges_count"
1546
+ }
1547
+
1548
+ test_graph_html_basic_assembly() {
1549
+ local tmp_dir output_dir
1550
+ tmp_dir="$(mktemp -d)"
1551
+ trap 'rm -rf "$tmp_dir"' RETURN
1552
+
1553
+ output_dir="$tmp_dir/wiki"
1554
+ mkdir -p "$output_dir"
1555
+ cp "$REPO_ROOT/$GRAPH_HTML_BASIC/wiki/graph-data.json" "$output_dir/graph-data.json"
1556
+
1557
+ bash "$REPO_ROOT/scripts/build-graph-html.sh" \
1558
+ "$tmp_dir" > /dev/null 2>&1 \
1559
+ || fail "build-graph-html.sh should succeed on basic fixture"
1560
+
1561
+ # HTML 文件存在
1562
+ assert_path_exists "$output_dir/knowledge-graph.html"
1563
+
1564
+ # 包含品牌栏占位符替换结果
1565
+ assert_file_contains "$output_dir/knowledge-graph.html" "HTML测试知识库"
1566
+ assert_file_contains "$output_dir/knowledge-graph.html" "3"
1567
+ assert_file_contains "$output_dir/knowledge-graph.html" "2"
1568
+
1569
+ # 共享 graph-engine 单文件产物:旧 wash/vendor 外部资产不再复制
1570
+ assert_single_file_engine_output "$output_dir"
1571
+ assert_file_contains "$output_dir/knowledge-graph.html" "国风知识库·数字山水图"
1572
+ assert_file_contains "$output_dir/knowledge-graph.html" "graph-reader"
1573
+ assert_file_contains "$output_dir/knowledge-graph.html" "node-layer"
1574
+ assert_file_contains "$output_dir/knowledge-graph.html" "mini-map"
1575
+ }
1576
+
1577
+ test_graph_html_escapes_script_tag_in_content() {
1578
+ local tmp_dir output_dir html
1579
+ tmp_dir="$(mktemp -d)"
1580
+ trap 'rm -rf "$tmp_dir"' RETURN
1581
+
1582
+ output_dir="$tmp_dir/wiki"
1583
+ mkdir -p "$output_dir"
1584
+ cp "$REPO_ROOT/$GRAPH_HTML_BASIC/wiki/graph-data.json" "$output_dir/graph-data.json"
1585
+
1586
+ bash "$REPO_ROOT/scripts/build-graph-html.sh" \
1587
+ "$tmp_dir" > /dev/null 2>&1
1588
+
1589
+ # </script> 必须被转义为 <\/script>
1590
+ html=$(cat "$output_dir/knowledge-graph.html")
1591
+ assert_text_not_contains "$html" '</script> 标签'
1592
+ assert_text_contains "$html" '<\/script> 标签'
1593
+ }
1594
+
1595
+ test_graph_html_escapes_title_text() {
1596
+ local tmp_dir output_dir html
1597
+ tmp_dir="$(mktemp -d)"
1598
+ trap 'rm -rf "$tmp_dir"' RETURN
1599
+
1600
+ output_dir="$tmp_dir/wiki"
1601
+ mkdir -p "$output_dir"
1602
+ jq '.meta.wiki_title = "<img src=x onerror=alert(1)>"' \
1603
+ "$REPO_ROOT/$GRAPH_HTML_BASIC/wiki/graph-data.json" \
1604
+ > "$output_dir/graph-data.json"
1605
+
1606
+ bash "$REPO_ROOT/scripts/build-graph-html.sh" \
1607
+ "$tmp_dir" > /dev/null 2>&1 \
1608
+ || fail "build-graph-html.sh should succeed with HTML-like title text"
1609
+
1610
+ html=$(cat "$output_dir/knowledge-graph.html")
1611
+ assert_text_contains "$html" '<title>知识图谱 · &lt;img src=x onerror=alert(1)&gt;</title>'
1612
+ assert_text_contains "$html" '<h1>&lt;img src=x onerror=alert(1)&gt; 知识舆图</h1>'
1613
+ assert_text_not_contains "$html" '<h1><img src=x onerror=alert(1)> 知识舆图</h1>'
1614
+ }
1615
+
1616
+ test_graph_html_missing_data_exits_with_error() {
1617
+ local tmp_dir
1618
+ tmp_dir="$(mktemp -d)"
1619
+ trap 'rm -rf "$tmp_dir"' RETURN
1620
+
1621
+ mkdir -p "$tmp_dir/wiki"
1622
+
1623
+ if bash "$REPO_ROOT/scripts/build-graph-html.sh" "$tmp_dir" > /dev/null 2>&1; then
1624
+ fail "build-graph-html.sh should fail when graph-data.json is missing"
1625
+ fi
1626
+ }
1627
+
1628
+ test_graph_html_missing_engine_assets_exits_with_error() {
1629
+ local tmp_dir
1630
+ tmp_dir="$(mktemp -d)"
1631
+ trap 'rm -rf "$tmp_dir"' RETURN
1632
+
1633
+ mkdir -p "$tmp_dir/wiki/scripts" "$tmp_dir/wiki"
1634
+ printf '{}' > "$tmp_dir/wiki/graph-data.json"
1635
+
1636
+ # 把脚本复制到临时目录,让它找不到 graph-engine/deps 产物
1637
+ cp "$REPO_ROOT/scripts/build-graph-html.sh" "$tmp_dir/wiki/scripts/"
1638
+ chmod +x "$tmp_dir/wiki/scripts/build-graph-html.sh"
1639
+
1640
+ if bash "$tmp_dir/wiki/scripts/build-graph-html.sh" "$tmp_dir/wiki" > /dev/null 2>&1; then
1641
+ fail "build-graph-html.sh should fail when engine assets are missing"
1642
+ fi
1643
+ }
1644
+
1645
+ test_graph_data_dead_links_are_ignored() {
1646
+ local tmp_dir output
1647
+ tmp_dir="$(mktemp -d)"
1648
+ trap 'rm -rf "$tmp_dir"' RETURN
1649
+
1650
+ mkdir -p "$tmp_dir/wiki/entities"
1651
+
1652
+ printf '# Alpha\n\n链接到 [[NonExistent]] 实体。\n' > "$tmp_dir/wiki/entities/Alpha.md"
1653
+
1654
+ LLM_WIKI_TEST_MODE=1 \
1655
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" "$tmp_dir" "$tmp_dir/wiki/graph-data.json" > /dev/null 2>&1
1656
+
1657
+ # 应该只有 Alpha 一个节点,0 条边(NonExistent 不存在)
1658
+ local node_count edge_count
1659
+ node_count=$(jq '.meta.total_nodes' "$tmp_dir/wiki/graph-data.json")
1660
+ edge_count=$(jq '.meta.total_edges' "$tmp_dir/wiki/graph-data.json")
1661
+ [ "$node_count" = "1" ] || fail "Expected 1 node, got: $node_count"
1662
+ [ "$edge_count" = "0" ] || fail "Expected 0 edges (dead link ignored), got: $edge_count"
1663
+ }
1664
+
1665
+ test_graph_data_self_links_are_ignored() {
1666
+ local tmp_dir
1667
+ tmp_dir="$(mktemp -d)"
1668
+ trap 'rm -rf "$tmp_dir"' RETURN
1669
+
1670
+ mkdir -p "$tmp_dir/wiki/entities"
1671
+
1672
+ printf '# Self\n\n自引用 [[Self]]。\n' > "$tmp_dir/wiki/entities/Self.md"
1673
+
1674
+ LLM_WIKI_TEST_MODE=1 \
1675
+ bash "$REPO_ROOT/scripts/build-graph-data.sh" "$tmp_dir" "$tmp_dir/wiki/graph-data.json" > /dev/null 2>&1
1676
+
1677
+ local edge_count
1678
+ edge_count=$(jq '.meta.total_edges' "$tmp_dir/wiki/graph-data.json")
1679
+ [ "$edge_count" = "0" ] || fail "Expected 0 edges (self-link ignored), got: $edge_count"
1680
+ }
1681
+
1682
+ test_graph_data_exits_without_jq() {
1683
+ local tmp_dir output
1684
+ tmp_dir="$(mktemp -d)"
1685
+ trap 'rm -rf "$tmp_dir"' RETURN
1686
+
1687
+ mkdir -p "$tmp_dir/wiki/entities"
1688
+ mkdir -p "$tmp_dir/bin"
1689
+ ln -s /bin/bash "$tmp_dir/bin/bash"
1690
+
1691
+ # 用只包含 bash 的隔离 PATH 来测试 jq 依赖缺失,避免宿主机把 jq 装进 /bin
1692
+ if output="$(PATH="$tmp_dir/bin" "$tmp_dir/bin/bash" "$REPO_ROOT/scripts/build-graph-data.sh" "$tmp_dir" 2>&1)"; then
1693
+ fail "build-graph-data.sh should fail when jq is not available"
1694
+ fi
1695
+ assert_text_contains "$output" "jq"
1696
+ }
1697
+
1698
+ test_graph_data_wiki_dir_missing_exits() {
1699
+ local tmp_dir
1700
+ tmp_dir="$(mktemp -d)"
1701
+ trap 'rm -rf "$tmp_dir"' RETURN
1702
+
1703
+ if bash "$REPO_ROOT/scripts/build-graph-data.sh" "$tmp_dir" 2>/dev/null; then
1704
+ fail "build-graph-data.sh should fail when wiki/ directory is missing"
1705
+ fi
1706
+ }
1707
+
1708
+ test_graph_data_sample_wiki_matches_expected
1709
+ test_graph_data_empty_wiki_matches_expected
1710
+ test_graph_data_test_mode_is_stable
1711
+ test_graph_data_has_three_confidence_types
1712
+ test_graph_data_community_clustering
1713
+ test_graph_data_empty_wiki_has_zero_nodes_and_edges
1714
+ test_graph_html_basic_assembly
1715
+ test_graph_html_escapes_script_tag_in_content
1716
+ test_graph_html_missing_data_exits_with_error
1717
+ test_graph_html_missing_engine_assets_exits_with_error
1718
+ bash "$REPO_ROOT/tests/graph-analysis-helper.regression-1.sh" || fail "graph-analysis-helper.regression-1.sh 测试失败"
1719
+ bash "$REPO_ROOT/tests/graph-build-failures.regression-1.sh" || fail "graph-build-failures.regression-1.sh 测试失败"
1720
+ bash "$REPO_ROOT/tests/graph-data-confidence-merge.regression-1.sh" || fail "graph-data-confidence-merge.regression-1.sh 测试失败"
1721
+ bash "$REPO_ROOT/tests/graph-data-source-paths.regression-1.sh" || fail "graph-data-source-paths.regression-1.sh 测试失败"
1722
+ bash "$REPO_ROOT/tests/graph-html-brand-link.regression-1.sh" || fail "graph-html-brand-link.regression-1.sh 测试失败"
1723
+ bash "$REPO_ROOT/tests/graph-html-long-label.regression-1.sh" || fail "graph-html-long-label.regression-1.sh 测试失败"
1724
+ bash "$REPO_ROOT/tests/graph-html-minimap.regression-1.sh" || fail "graph-html-minimap.regression-1.sh 测试失败"
1725
+ bash "$REPO_ROOT/tests/graph-html-toolbar.regression-1.sh" || fail "graph-html-toolbar.regression-1.sh 测试失败"
1726
+ bash "$REPO_ROOT/tests/graph-html-drawer-neighbors.regression-1.sh" || fail "graph-html-drawer-neighbors.regression-1.sh 测试失败"
1727
+ bash "$REPO_ROOT/tests/graph-html-density.regression-1.sh" || fail "graph-html-density.regression-1.sh 测试失败"
1728
+ bash "$REPO_ROOT/tests/graph-html-insights.regression-1.sh" || fail "graph-html-insights.regression-1.sh 测试失败"
1729
+ bash "$REPO_ROOT/tests/graph-html-a11y.regression-1.sh" || fail "graph-html-a11y.regression-1.sh 测试失败"
1730
+ bash "$REPO_ROOT/tests/graph-html-styles.regression-1.sh" || fail "graph-html-styles.regression-1.sh 测试失败"
1731
+ bash "$REPO_ROOT/tests/graph-html-search.regression-1.sh" || fail "graph-html-search.regression-1.sh 测试失败"
1732
+ bash "$REPO_ROOT/tests/graph-html-mobile.regression-1.sh" || fail "graph-html-mobile.regression-1.sh 测试失败"
1733
+ bash "$REPO_ROOT/tests/graph-html-oriental-atlas-contract.regression-1.sh" || fail "graph-html-oriental-atlas-contract.regression-1.sh 测试失败"
1734
+ bash "$REPO_ROOT/tests/graph-html-oriental-design-contract.regression-1.sh" || fail "graph-html-oriental-design-contract.regression-1.sh 测试失败"
1735
+ test_graph_data_dead_links_are_ignored
1736
+ test_graph_data_self_links_are_ignored
1737
+ test_graph_data_exits_without_jq
1738
+ test_graph_data_wiki_dir_missing_exits
1739
+
1740
+ bash "$REPO_ROOT/tests/adapter-state.sh" || fail "adapter-state.sh 测试失败"
1741
+
1742
+ # ─── JS 单测 ──────────────────────────────────────────────────────
1743
+ node --test "$REPO_ROOT/tests/js/source-signal-eligibility.test.js" || fail "source-signal-eligibility unit tests failed"
1744
+ node --test "$REPO_ROOT/tests/js/source-signal-coverage.test.js" || fail "source-signal-coverage integration tests failed"
1745
+ node --test "$REPO_ROOT/tests/js/graph-wash-helpers.test.js" || fail "graph-wash-helpers unit tests failed"
1746
+ node --test "$REPO_ROOT/tests/js/graph-wash-bootstrap.test.js" || fail "graph-wash bootstrap unit tests failed"
1747
+ node --test "$REPO_ROOT/tests/js/graph-wash-queue.test.js" || fail "graph-wash queue unit tests failed"
1748
+ node --test "$REPO_ROOT/tests/js/graph-wash-learning.test.js" || fail "graph-wash learning unit tests failed"
1749
+ node --test "$REPO_ROOT/tests/js/graph-wash-runtime-state.test.js" || fail "graph-wash runtime state unit tests failed"
1750
+
1751
+ # ─── Lint 回归 ────────────────────────────────────────────────────
1752
+ bash "$REPO_ROOT/tests/lint-output.regression-1.sh" || fail "lint output regression failed"
1753
+
1754
+ echo "All regression checks passed."