@huicai/cli 2026.814.0 → 2026.1021.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 (493) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +139 -8
  3. package/bin/huicai.js +155 -74
  4. package/bin/update.js +97 -0
  5. package/package.json +27 -8
  6. package/skills/ab-testing/trigger-cases.json +30 -0
  7. package/skills/academic-writing/trigger-cases.json +30 -0
  8. package/skills/accounting-ops/trigger-cases.json +30 -0
  9. package/skills/ai-agent/trigger-cases.json +30 -0
  10. package/skills/ai-aiops/trigger-cases.json +30 -0
  11. package/skills/ai-coding/SKILL.md +2 -0
  12. package/skills/ai-coding/trigger-cases.json +30 -0
  13. package/skills/ai-compliance/trigger-cases.json +30 -0
  14. package/skills/ai-eval/trigger-cases.json +30 -0
  15. package/skills/ai-llm-app/trigger-cases.json +30 -0
  16. package/skills/ai-mlops/trigger-cases.json +30 -0
  17. package/skills/ai-multimodal/trigger-cases.json +30 -0
  18. package/skills/ai-prompt/trigger-cases.json +30 -0
  19. package/skills/ai-rag/trigger-cases.json +30 -0
  20. package/skills/anomaly-detection/trigger-cases.json +30 -0
  21. package/skills/api-design/trigger-cases.json +31 -0
  22. package/skills/api-integration/trigger-cases.json +31 -0
  23. package/skills/arch-design/trigger-cases.json +12 -0
  24. package/skills/arch-diagram/SKILL.md +118 -0
  25. package/skills/arch-diagram/trigger-cases.json +30 -0
  26. package/skills/arch-distributed/trigger-cases.json +30 -0
  27. package/skills/arch-multitenant/trigger-cases.json +30 -0
  28. package/skills/audio-brief/SKILL.md +65 -0
  29. package/skills/audio-brief/trigger-cases.json +30 -0
  30. package/skills/audit-assist/trigger-cases.json +30 -0
  31. package/skills/audit-chain/trigger-cases.json +31 -0
  32. package/skills/banking-credit/trigger-cases.json +30 -0
  33. package/skills/brand-design/trigger-cases.json +30 -0
  34. package/skills/business-plan/trigger-cases.json +30 -0
  35. package/skills/capital-ops/trigger-cases.json +30 -0
  36. package/skills/cicd-pipeline/trigger-cases.json +31 -0
  37. package/skills/code-archaeology/SKILL.md +55 -0
  38. package/skills/code-archaeology/trigger-cases.json +9 -0
  39. package/skills/code-quality/SKILL.md +138 -0
  40. package/skills/code-quality/references/languages.md +107 -0
  41. package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
  42. package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
  43. package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
  44. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
  45. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
  46. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
  47. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
  48. package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
  49. package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
  50. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
  51. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
  52. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
  53. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
  54. package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
  55. package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
  56. package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
  57. package/skills/code-quality/scripts/code_battery.py +1072 -0
  58. package/skills/code-quality/scripts/code_check.py +1269 -0
  59. package/skills/code-quality/scripts/code_csharp.py +371 -0
  60. package/skills/code-quality/scripts/code_fix.py +201 -0
  61. package/skills/code-quality/scripts/code_java.py +695 -0
  62. package/skills/code-quality/scripts/code_jsts.py +313 -0
  63. package/skills/code-quality/scripts/code_lens.py +1079 -0
  64. package/skills/code-quality/scripts/code_python.py +536 -0
  65. package/skills/code-quality/scripts/code_testplan.py +239 -0
  66. package/skills/code-quality/trigger-cases.json +45 -0
  67. package/skills/code-review/SKILL.md +24 -0
  68. package/skills/code-review/trigger-cases.json +35 -0
  69. package/skills/commit-and-pr/trigger-cases.json +31 -0
  70. package/skills/content-creation/trigger-cases.json +30 -0
  71. package/skills/corporate-governance/trigger-cases.json +30 -0
  72. package/skills/crawl-chain/trigger-cases.json +31 -0
  73. package/skills/customer-service/trigger-cases.json +30 -0
  74. package/skills/data-analysis/trigger-cases.json +30 -0
  75. package/skills/data-chain/SKILL.md +1 -1
  76. package/skills/data-chain/trigger-cases.json +31 -0
  77. package/skills/data-governance/trigger-cases.json +30 -0
  78. package/skills/data-report-pro/trigger-cases.json +30 -0
  79. package/skills/data-streaming/trigger-cases.json +30 -0
  80. package/skills/data-wrangling/SKILL.md +1 -1
  81. package/skills/data-wrangling/trigger-cases.json +30 -0
  82. package/skills/db-es/trigger-cases.json +30 -0
  83. package/skills/db-migration/trigger-cases.json +30 -0
  84. package/skills/db-mysql/trigger-cases.json +30 -0
  85. package/skills/db-postgres/trigger-cases.json +30 -0
  86. package/skills/db-redis/trigger-cases.json +30 -0
  87. package/skills/debug-root-cause/trigger-cases.json +30 -0
  88. package/skills/dependency-audit/SKILL.md +56 -0
  89. package/skills/dependency-audit/trigger-cases.json +7 -0
  90. package/skills/dependency-upgrade/trigger-cases.json +31 -0
  91. package/skills/dev-cpp/trigger-cases.json +30 -0
  92. package/skills/dev-cross-platform/trigger-cases.json +30 -0
  93. package/skills/dev-csharp/SKILL.md +23 -0
  94. package/skills/dev-csharp/trigger-cases.json +30 -0
  95. package/skills/dev-frontend/SKILL.md +19 -0
  96. package/skills/dev-frontend/trigger-cases.json +30 -0
  97. package/skills/dev-go/trigger-cases.json +30 -0
  98. package/skills/dev-harmony/trigger-cases.json +30 -0
  99. package/skills/dev-java/SKILL.md +20 -0
  100. package/skills/dev-java/trigger-cases.json +30 -0
  101. package/skills/dev-mobile/trigger-cases.json +30 -0
  102. package/skills/dev-node/trigger-cases.json +30 -0
  103. package/skills/dev-php/trigger-cases.json +30 -0
  104. package/skills/dev-python/SKILL.md +21 -0
  105. package/skills/dev-python/trigger-cases.json +30 -0
  106. package/skills/dev-rust/trigger-cases.json +30 -0
  107. package/skills/dev-toolchain/trigger-cases.json +31 -0
  108. package/skills/doc-chain/trigger-cases.json +31 -0
  109. package/skills/doc-compare/trigger-cases.json +30 -0
  110. package/skills/docx-author/SKILL.md +127 -25
  111. package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
  112. package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
  113. package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
  114. package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
  115. package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
  116. package/skills/docx-author/scripts/docx_battery.py +586 -0
  117. package/skills/docx-author/scripts/docx_build.py +995 -0
  118. package/skills/docx-author/scripts/docx_check.py +1306 -0
  119. package/skills/docx-author/scripts/docx_doc.py +14 -154
  120. package/skills/docx-author/scripts/docx_layout.py +490 -0
  121. package/skills/docx-author/scripts/zh_prose.py +639 -0
  122. package/skills/docx-author/trigger-cases.json +58 -0
  123. package/skills/echo-brief/SKILL.md +84 -0
  124. package/skills/echo-brief/references/echo-format.md +68 -0
  125. package/skills/echo-brief/trigger-cases.json +40 -0
  126. package/skills/ecommerce-ops/trigger-cases.json +30 -0
  127. package/skills/education-training/trigger-cases.json +30 -0
  128. package/skills/ehs-safety/trigger-cases.json +30 -0
  129. package/skills/email-writing/trigger-cases.json +30 -0
  130. package/skills/eng-ai/trigger-cases.json +30 -0
  131. package/skills/eng-architecture/trigger-cases.json +31 -0
  132. package/skills/eng-automation/trigger-cases.json +31 -0
  133. package/skills/eng-collaboration/trigger-cases.json +30 -0
  134. package/skills/eng-complex-env/trigger-cases.json +30 -0
  135. package/skills/eng-data-platform/trigger-cases.json +31 -0
  136. package/skills/eng-database/trigger-cases.json +31 -0
  137. package/skills/eng-develop/SKILL.md +9 -1
  138. package/skills/eng-develop/references/dod-checklist.md +3 -0
  139. package/skills/eng-develop/references/engineering-workflow.md +37 -0
  140. package/skills/eng-develop/trigger-cases.json +30 -0
  141. package/skills/eng-docs/SKILL.md +24 -0
  142. package/skills/eng-docs/trigger-cases.json +30 -0
  143. package/skills/eng-efficiency/trigger-cases.json +30 -0
  144. package/skills/eng-infra/trigger-cases.json +30 -0
  145. package/skills/eng-integration/trigger-cases.json +31 -0
  146. package/skills/eng-legacy/trigger-cases.json +31 -0
  147. package/skills/eng-observability/trigger-cases.json +31 -0
  148. package/skills/eng-ops/trigger-cases.json +31 -0
  149. package/skills/eng-performance/trigger-cases.json +31 -0
  150. package/skills/eng-planning/trigger-cases.json +31 -0
  151. package/skills/eng-quality/SKILL.md +8 -0
  152. package/skills/eng-quality/trigger-cases.json +31 -0
  153. package/skills/eng-release/trigger-cases.json +31 -0
  154. package/skills/eng-reliability/trigger-cases.json +31 -0
  155. package/skills/eng-requirements/trigger-cases.json +12 -0
  156. package/skills/eng-research/trigger-cases.json +30 -0
  157. package/skills/eng-security/trigger-cases.json +31 -0
  158. package/skills/eng-ux/trigger-cases.json +30 -0
  159. package/skills/engineering-chain/trigger-cases.json +30 -0
  160. package/skills/env-offline/trigger-cases.json +31 -0
  161. package/skills/env-private-deploy/trigger-cases.json +31 -0
  162. package/skills/env-xinchuang/trigger-cases.json +31 -0
  163. package/skills/equipment-tpm/trigger-cases.json +30 -0
  164. package/skills/esg-report/trigger-cases.json +30 -0
  165. package/skills/event-management/trigger-cases.json +30 -0
  166. package/skills/excel-analysis-chain/SKILL.md +17 -4
  167. package/skills/excel-analysis-chain/trigger-cases.json +31 -0
  168. package/skills/explain-codebase/trigger-cases.json +11 -0
  169. package/skills/file-ingest/trigger-cases.json +30 -0
  170. package/skills/finance-analysis/trigger-cases.json +31 -0
  171. package/skills/finance-chain/trigger-cases.json +31 -0
  172. package/skills/financial-modeling/trigger-cases.json +31 -0
  173. package/skills/find-simplifications/SKILL.md +43 -0
  174. package/skills/find-simplifications/trigger-cases.json +23 -0
  175. package/skills/foreign-trade/trigger-cases.json +30 -0
  176. package/skills/funnel-cohort/trigger-cases.json +30 -0
  177. package/skills/gov-document/trigger-cases.json +30 -0
  178. package/skills/gui-change-evidence/SKILL.md +47 -0
  179. package/skills/gui-change-evidence/trigger-cases.json +23 -0
  180. package/skills/hr-chain/trigger-cases.json +31 -0
  181. package/skills/hr-operations/trigger-cases.json +30 -0
  182. package/skills/info-disclosure/trigger-cases.json +31 -0
  183. package/skills/infra-gateway/trigger-cases.json +31 -0
  184. package/skills/infra-k8s/trigger-cases.json +31 -0
  185. package/skills/infra-mq/trigger-cases.json +30 -0
  186. package/skills/insurance-advisory/trigger-cases.json +30 -0
  187. package/skills/internal-audit/trigger-cases.json +30 -0
  188. package/skills/ip-management/trigger-cases.json +31 -0
  189. package/skills/ipo-listing/trigger-cases.json +31 -0
  190. package/skills/it-audit/trigger-cases.json +31 -0
  191. package/skills/itsm-ops/trigger-cases.json +31 -0
  192. package/skills/knowledge-sop/trigger-cases.json +31 -0
  193. package/skills/legal-chain/trigger-cases.json +31 -0
  194. package/skills/legal-contract/trigger-cases.json +30 -0
  195. package/skills/litigation-support/trigger-cases.json +31 -0
  196. package/skills/logistics-warehouse/trigger-cases.json +30 -0
  197. package/skills/long-doc-analysis/trigger-cases.json +31 -0
  198. package/skills/management-accounting/trigger-cases.json +30 -0
  199. package/skills/manufacturing-chain/trigger-cases.json +31 -0
  200. package/skills/manufacturing-quality/trigger-cases.json +30 -0
  201. package/skills/marketing-analytics/trigger-cases.json +31 -0
  202. package/skills/marketing-chain/trigger-cases.json +31 -0
  203. package/skills/meeting-minutes/trigger-cases.json +30 -0
  204. package/skills/metrics-system/trigger-cases.json +31 -0
  205. package/skills/negotiation-prep/trigger-cases.json +30 -0
  206. package/skills/obs-prometheus/trigger-cases.json +31 -0
  207. package/skills/office-automation/trigger-cases.json +31 -0
  208. package/skills/okr-review/trigger-cases.json +30 -0
  209. package/skills/ops-fleet/trigger-cases.json +31 -0
  210. package/skills/ops-linux/trigger-cases.json +31 -0
  211. package/skills/optimization-solver/trigger-cases.json +30 -0
  212. package/skills/patent-disclosure/SKILL.md +105 -0
  213. package/skills/patent-disclosure/references/figures.md +110 -0
  214. package/skills/patent-disclosure/references/outline.md +162 -0
  215. package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
  216. package/skills/patent-disclosure/trigger-cases.json +31 -0
  217. package/skills/patent-drafting/SKILL.md +108 -0
  218. package/skills/patent-drafting/trigger-cases.json +31 -0
  219. package/skills/patent-portfolio/SKILL.md +81 -0
  220. package/skills/patent-portfolio/trigger-cases.json +31 -0
  221. package/skills/pdf-toolkit/trigger-cases.json +31 -0
  222. package/skills/perf-optimize/trigger-cases.json +31 -0
  223. package/skills/pivot-crosstab/trigger-cases.json +30 -0
  224. package/skills/policy-analysis/trigger-cases.json +31 -0
  225. package/skills/portfolio-analysis/trigger-cases.json +31 -0
  226. package/skills/ppt-chain/SKILL.md +14 -6
  227. package/skills/ppt-chain/trigger-cases.json +31 -0
  228. package/skills/pptx-author/SKILL.md +86 -33
  229. package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
  230. package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
  231. package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
  232. package/skills/pptx-author/scripts/pptx_battery.py +178 -0
  233. package/skills/pptx-author/scripts/pptx_build.py +119 -0
  234. package/skills/pptx-author/scripts/pptx_check.py +1182 -0
  235. package/skills/pptx-author/scripts/pptx_deck.py +736 -145
  236. package/skills/pptx-author/scripts/pptx_layout.py +384 -0
  237. package/skills/pptx-author/trigger-cases.json +15 -0
  238. package/skills/pr-comms/trigger-cases.json +30 -0
  239. package/skills/procurement-supply/trigger-cases.json +31 -0
  240. package/skills/product-design/trigger-cases.json +31 -0
  241. package/skills/production-planning/trigger-cases.json +31 -0
  242. package/skills/project-management/trigger-cases.json +31 -0
  243. package/skills/project-proposal/trigger-cases.json +31 -0
  244. package/skills/prose-standard/SKILL.md +53 -0
  245. package/skills/prose-standard/trigger-cases.json +23 -0
  246. package/skills/qa-api-automation/trigger-cases.json +31 -0
  247. package/skills/qa-mobile-automation/trigger-cases.json +31 -0
  248. package/skills/qa-ui-automation/trigger-cases.json +31 -0
  249. package/skills/quant-analysis/trigger-cases.json +31 -0
  250. package/skills/rag-triage/SKILL.md +64 -0
  251. package/skills/rag-triage/evals.json +57 -0
  252. package/skills/rag-triage/references/triage-playbook.md +75 -0
  253. package/skills/rag-triage/trigger-cases.json +31 -0
  254. package/skills/real-estate-property/trigger-cases.json +30 -0
  255. package/skills/refactor-safely/SKILL.md +1 -1
  256. package/skills/refactor-safely/trigger-cases.json +30 -0
  257. package/skills/regression-forecast/trigger-cases.json +31 -0
  258. package/skills/release-checklist/trigger-cases.json +31 -0
  259. package/skills/report-craft/SKILL.md +76 -0
  260. package/skills/report-craft/references/feasibility.md +118 -0
  261. package/skills/report-craft/references/review-checklist.md +51 -0
  262. package/skills/report-craft/references/visuals.md +114 -0
  263. package/skills/report-craft/references/writing.md +80 -0
  264. package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
  265. package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
  266. package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
  267. package/skills/report-craft/scripts/chart_render.py +1222 -0
  268. package/skills/report-craft/scripts/fin_model.py +602 -0
  269. package/skills/report-craft/scripts/project_facts.py +580 -0
  270. package/skills/report-craft/scripts/report_battery.py +349 -0
  271. package/skills/report-craft/trigger-cases.json +15 -0
  272. package/skills/retail-store-ops/trigger-cases.json +30 -0
  273. package/skills/risk-compliance/trigger-cases.json +31 -0
  274. package/skills/scope-creep/SKILL.md +45 -0
  275. package/skills/scope-creep/trigger-cases.json +7 -0
  276. package/skills/sec-auth/trigger-cases.json +31 -0
  277. package/skills/sec-compliance/trigger-cases.json +31 -0
  278. package/skills/securities-chain/trigger-cases.json +31 -0
  279. package/skills/securities-research/trigger-cases.json +31 -0
  280. package/skills/security-audit/trigger-cases.json +31 -0
  281. package/skills/slides-html/SKILL.md +25 -10
  282. package/skills/slides-html/trigger-cases.json +11 -0
  283. package/skills/sql-analysis/SKILL.md +110 -0
  284. package/skills/sql-analysis/trigger-cases.json +34 -0
  285. package/skills/sql-tuning/trigger-cases.json +31 -0
  286. package/skills/statistical-testing/trigger-cases.json +31 -0
  287. package/skills/strategy-planning/trigger-cases.json +30 -0
  288. package/skills/supply-chain/trigger-cases.json +31 -0
  289. package/skills/tax-compliance/trigger-cases.json +30 -0
  290. package/skills/tech-doc/SKILL.md +262 -0
  291. package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
  292. package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
  293. package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
  294. package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
  295. package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
  296. package/skills/tech-doc/scripts/archive_battery.py +860 -0
  297. package/skills/tech-doc/scripts/archive_build.py +599 -0
  298. package/skills/tech-doc/scripts/archive_check.py +1307 -0
  299. package/skills/tech-doc/scripts/archive_spec.py +499 -0
  300. package/skills/tech-doc/scripts/doc_templates.py +624 -0
  301. package/skills/tech-doc/trigger-cases.json +57 -0
  302. package/skills/tech-research/trigger-cases.json +31 -0
  303. package/skills/tender-bidding/trigger-cases.json +31 -0
  304. package/skills/translation-pro/trigger-cases.json +30 -0
  305. package/skills/treasury-mgmt/trigger-cases.json +31 -0
  306. package/skills/trim-cot-leakage/SKILL.md +58 -0
  307. package/skills/trim-cot-leakage/trigger-cases.json +23 -0
  308. package/skills/web-craft/SKILL.md +103 -0
  309. package/skills/web-craft/references/craft-checklist.md +75 -0
  310. package/skills/web-craft/references/design-direction.md +122 -0
  311. package/skills/web-craft/references/h5-mobile.md +100 -0
  312. package/skills/web-craft/references/motion.md +136 -0
  313. package/skills/web-craft/references/recipes.md +74 -0
  314. package/skills/web-craft/templates/wedding-invitation.html +1180 -0
  315. package/skills/web-craft/trigger-cases.json +14 -0
  316. package/skills/web-scraping/trigger-cases.json +34 -0
  317. package/skills/web-search/SKILL.md +11 -10
  318. package/skills/web-search/trigger-cases.json +31 -0
  319. package/skills/weekly-report/SKILL.md +134 -0
  320. package/skills/weekly-report/trigger-cases.json +11 -0
  321. package/skills/write-tests/trigger-cases.json +31 -0
  322. package/skills/xlsx-analysis/SKILL.md +143 -77
  323. package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
  324. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
  325. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
  326. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
  327. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
  328. package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
  329. package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
  330. package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
  331. package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
  332. package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
  333. package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
  334. package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
  335. package/skills/xlsx-analysis/trigger-cases.json +59 -0
  336. package/squads/hc-defense.md +47 -0
  337. package/squads/hc-rdarchive.md +110 -0
  338. package/squads/hc-redteam.md +50 -0
  339. package/squads/hc-weekly.md +125 -0
  340. package/src/account.js +1 -1
  341. package/src/acp.js +1 -0
  342. package/src/advisor.js +1 -1
  343. package/src/agent.js +1 -1
  344. package/src/api.js +1 -1
  345. package/src/archaeology.js +1 -0
  346. package/src/ask-ui.js +1 -0
  347. package/src/assets-import.js +1 -1
  348. package/src/autofix.js +1 -1
  349. package/src/automation-tools.js +1 -1
  350. package/src/automation.js +1 -1
  351. package/src/autorun.js +1 -1
  352. package/src/banner.js +1 -1
  353. package/src/capability-probe.js +1 -0
  354. package/src/checkpoints.js +1 -1
  355. package/src/code-tools.js +1 -0
  356. package/src/codeindex.js +1 -1
  357. package/src/codemode.js +1 -0
  358. package/src/command-bridge.js +1 -0
  359. package/src/command-ui.js +1 -0
  360. package/src/commands.js +1 -1
  361. package/src/compaction.js +1 -1
  362. package/src/computer.js +1 -1
  363. package/src/config.js +1 -1
  364. package/src/consult.js +1 -1
  365. package/src/context-report.js +1 -1
  366. package/src/context-window.js +1 -0
  367. package/src/context.js +1 -1
  368. package/src/crew.js +1 -0
  369. package/src/crush.js +1 -0
  370. package/src/daemon.js +1 -1
  371. package/src/decisions.js +1 -1
  372. package/src/depaudit.js +1 -0
  373. package/src/device.js +1 -1
  374. package/src/diagram-draw.js +1 -0
  375. package/src/diagram-layouts.js +1 -0
  376. package/src/diagram-raster.js +1 -0
  377. package/src/diagram.js +1 -0
  378. package/src/dictation.js +1 -0
  379. package/src/diffview.js +1 -1
  380. package/src/doctor.js +1 -1
  381. package/src/docx-tools.js +1 -0
  382. package/src/engineering.js +1 -0
  383. package/src/errkind.js +1 -1
  384. package/src/evidence.js +1 -0
  385. package/src/executors.js +1 -1
  386. package/src/expert-catalog.js +1 -1
  387. package/src/experts-store.js +1 -1
  388. package/src/experts.js +1 -1
  389. package/src/external-agents.js +1 -0
  390. package/src/forks.js +1 -1
  391. package/src/fs-observe.js +1 -0
  392. package/src/gitflow.js +1 -1
  393. package/src/goalround.js +1 -0
  394. package/src/hooks.js +1 -1
  395. package/src/ide.js +1 -1
  396. package/src/input.js +1 -1
  397. package/src/insights.js +1 -1
  398. package/src/jobs.js +1 -0
  399. package/src/kbsync.js +1 -1
  400. package/src/lsp.js +1 -0
  401. package/src/mcp-apps.js +1 -0
  402. package/src/mcp.js +1 -1
  403. package/src/memory.js +1 -1
  404. package/src/notify.js +1 -1
  405. package/src/permissions.js +1 -1
  406. package/src/plan-limits.js +1 -0
  407. package/src/policy.js +1 -0
  408. package/src/pptx-tools.js +1 -0
  409. package/src/presence.js +1 -1
  410. package/src/privatedir.js +1 -0
  411. package/src/pyruntime.js +1 -1
  412. package/src/radar.js +1 -0
  413. package/src/render.js +1 -1
  414. package/src/repl.js +1 -1
  415. package/src/report-tools.js +1 -0
  416. package/src/routing.js +1 -0
  417. package/src/sandbox-probe.js +1 -0
  418. package/src/sandbox-win-launcher.mjs +156 -0
  419. package/src/sandbox.js +1 -0
  420. package/src/scan.js +1 -1
  421. package/src/schedule.js +1 -1
  422. package/src/scopecreep.js +1 -0
  423. package/src/sdk.js +1 -0
  424. package/src/selection.js +1 -0
  425. package/src/session-events.js +1 -0
  426. package/src/session.js +1 -1
  427. package/src/sessionindex.js +1 -0
  428. package/src/sessionlog.js +1 -0
  429. package/src/skill-catalog.js +1 -0
  430. package/src/skill-lint.js +1 -0
  431. package/src/skill-optimize.js +1 -0
  432. package/src/skill-scan.js +1 -0
  433. package/src/skills.js +1 -1
  434. package/src/spill.js +1 -0
  435. package/src/squad.js +1 -1
  436. package/src/subagent.js +1 -1
  437. package/src/surface-state.js +1 -0
  438. package/src/tapd.js +1 -0
  439. package/src/terminal-select.js +1 -0
  440. package/src/terminal.js +1 -0
  441. package/src/test-runner.js +1 -0
  442. package/src/todo.js +1 -1
  443. package/src/toolpipe.js +1 -0
  444. package/src/tools.js +1 -1
  445. package/src/toon.js +1 -0
  446. package/src/transcript.js +1 -1
  447. package/src/tui/app.js +1 -1
  448. package/src/tui/command-menu.js +1 -0
  449. package/src/tui/progress.js +1 -0
  450. package/src/tui/scroll.js +1 -0
  451. package/src/tui/welcome.js +1 -0
  452. package/src/tui/workbench.js +1 -0
  453. package/src/ui-spec.js +1 -0
  454. package/src/uitree.js +1 -0
  455. package/src/uninstall.js +1 -1
  456. package/src/vision.js +1 -1
  457. package/src/visual.js +1 -1
  458. package/src/voice-form.js +1 -0
  459. package/src/voice.js +1 -0
  460. package/src/watch.js +1 -1
  461. package/src/web-audit.js +1 -0
  462. package/src/web-cdp.js +1 -0
  463. package/src/web-inspect.js +1 -0
  464. package/src/websearch.js +1 -0
  465. package/src/weekly.js +1 -0
  466. package/src/workflow.js +1 -0
  467. package/src/worklog.js +1 -0
  468. package/src/xlsx-tools.js +1 -0
  469. package/ui/command-picker.js +79 -0
  470. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  471. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  472. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  473. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  474. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  475. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  476. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  477. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  478. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  479. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  480. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  481. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  482. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  483. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  484. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  485. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  486. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  487. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  488. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  489. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  490. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  491. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  492. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  493. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
@@ -1 +1 @@
1
- import{buildModelMessages as t,estimateTokens as o,DEFAULT_AUTOCOMPACT_TOKENS as e}from"./compaction.js";import{c as r}from"./colors.js";export{o as estimateTokens};function s(t){if("string"==typeof t.content)return o(t.content);if(Array.isArray(t.content)){let e=0;for(const r of t.content)"text"===r?.type&&r.text?e+=o(r.text):"image_url"===r?.type&&(e+=300);return e}return 0}function a(t){return(t=Math.round(Number(t)||0))>=1e6?(t/1e6).toFixed(t>=1e7?0:1)+"M":t>=1e3?(t/1e3).toFixed(1)+"k":String(t)}function n(t,o=14){const e=Math.max(0,Math.min(o,Math.round(t*o)));return"█".repeat(e)+"░".repeat(o-e)}export function contextReport({messages:l,compaction:u,specs:c,lastPromptTokens:p,autoCompactTokens:i,platform:m}){const y=m?l:t(l,u||{}),k=!!(u&&u.summary&&u.upTo>1),f={system:{label:"系统提示词",tok:0},tools:{label:"工具定义",tok:0},summary:{label:"压缩摘要",tok:0},user:{label:"用户消息",tok:0},assistant:{label:"助手回复",tok:0},toolcall:{label:"工具调用",tok:0},toolresult:{label:"工具结果",tok:0}};y.forEach((t,e)=>{0!==e||"system"!==t.role?1===e&&k&&"user"===t.role?f.summary.tok+=s(t):"user"===t.role?f.user.tok+=s(t):"assistant"===t.role?(f.assistant.tok+=s(t),t.tool_calls&&(f.toolcall.tok+=o(JSON.stringify(t.tool_calls)))):"tool"===t.role&&(f.toolresult.tok+=s(t)):f.system.tok+=s(t)}),c&&c.length&&(f.tools.tok=o(JSON.stringify(c)));const h=["system","tools","summary","user","assistant","toolcall","toolresult"];let g=h.reduce((t,o)=>t+f[o].tok,0),d=1;p>0&&g>0&&(d=p/g);const b=t=>f[t].tok*d,x=p>0?p:g,M=[];M.push(r.bold("上下文占用")+r.gray("(当前发给模型的视图 · 估算)"));for(const t of h){const o=b(t);if(o<1&&0===f[t].tok)continue;const e=x>0?o/x:0;M.push(" "+f[t].label.padEnd(6," ")+" "+("~"+a(o)).padStart(7)+" "+r.cyan(n(e))+" "+String(Math.round(100*e)).padStart(3)+"%")}M.push(r.gray(" "+"─".repeat(34)));const S=Number(i)||e,N=Math.min(100,Math.round(x/S*100));return M.push(" 合计   "+("~"+a(x)).padStart(7)+r.gray(` 自动压缩阈值 ${a(S)}(${N}%)`)),p>0?M.push(r.gray(` 实测上一轮 prompt_tokens:${a(p)}(分类按此校准)`)):M.push(r.gray(" (尚无实测值,纯估算;发生一轮对话后更准)")),k&&M.push(r.gray(" 已启用上下文压缩:更早的历史以摘要形式计入,原文完整保留。")),M.push(r.gray(" /compact 立即压缩 · /context 再次查看")),M.join("\n")}
1
+ import{buildModelMessages as t,estimateTokens as o,DEFAULT_AUTOCOMPACT_TOKENS as r}from"./compaction.js";import{catalogDigestOf as e}from"./skill-catalog.js";import{platformPromptTokens as s,MEMBER_TRIGGER_RATIO as a}from"./context-window.js";import{c as n}from"./colors.js";export{o as estimateTokens};function l(t){if("string"==typeof t.content)return o(t.content);if(Array.isArray(t.content)){let r=0;for(const e of t.content)"text"===e?.type&&e.text?r+=o(e.text):"image_url"===e?.type&&(r+=300);return r}return 0}function u(t){return(t=Math.round(Number(t)||0))>=1e6?parseFloat((t/1e6).toFixed(t>=1e7?0:1))+"M":t>=1e3?parseFloat((t/1e3).toFixed(1))+"k":String(t)}function i(t,o=14){const r=Math.max(0,Math.min(o,Math.round(t*o)));return"█".repeat(r)+"░".repeat(o-r)}export function contextReport({messages:c,compaction:p,specs:m,lastPromptTokens:k,autoCompactTokens:f,memberLimit:h,platform:y,lastUsage:d}){const g=y?c:t(c,p||{}),b=!!(p&&p.summary&&p.upTo>1),x={system:{label:"系统提示词",tok:0},skills:{label:"技能目录",tok:0},tools:{label:"工具定义",tok:0},summary:{label:"压缩摘要",tok:0},user:{label:"用户消息",tok:0},assistant:{label:"助手回复",tok:0},toolcall:{label:"工具调用",tok:0},toolresult:{label:"工具结果",tok:0}};let M=0;g.forEach((t,r)=>{if(0!==r||"system"!==t.role){if(1!==r||!b||"user"!==t.role)return e(t)?(x.skills.tok+=l(t),void M++):void("user"===t.role?x.user.tok+=l(t):"assistant"===t.role?(x.assistant.tok+=l(t),t.tool_calls&&(x.toolcall.tok+=o(JSON.stringify(t.tool_calls)))):"tool"===t.role&&(x.toolresult.tok+=l(t)));x.summary.tok+=l(t)}else x.system.tok+=l(t)}),m&&m.length&&(x.tools.tok=o(JSON.stringify(m)));const _=["system","skills","tools","summary","user","assistant","toolcall","toolresult"];let $=_.reduce((t,o)=>t+x[o].tok,0),N=1;k>0&&$>0&&(N=k/$);const S=t=>x[t].tok*N,j=k>0?k:$,F=[];F.push(n.bold("上下文占用")+n.gray("(当前发给模型的视图 · 估算)"));for(const t of _){const o=S(t);if(o<1&&0===x[t].tok)continue;const r=j>0?o/j:0;F.push(" "+x[t].label.padEnd(6," ")+" "+("~"+u(o)).padStart(7)+" "+n.cyan(i(r))+" "+String(Math.round(100*r)).padStart(3)+"%")}F.push(n.gray(" "+"─".repeat(34)));const T=Number(f)||r,v=Math.min(100,Math.round(j/T*100));F.push(" 合计   "+("~"+u(j)).padStart(7)+n.gray(` 自动压缩阈值 ${u(T)}(${v}%)`));const w=Number(h)||0;if(w>0){const t=s(g);F.push(" 会员窗口 "+("~"+u(t)).padStart(7)+n.gray(` 会员等级上下文上限 ${u(w)}(${Math.round(t/w*100)}%,按平台口径;到 ${Math.round(100*a)}% 自动压缩,超出会被平台拒绝)`))}k>0?F.push(n.gray(` 实测上一轮prompt_tokens:${u(k)}(分类按此校准)`)):F.push(n.gray(" (尚无实测值,纯估算;发生一轮对话后更准)")),b&&F.push(n.gray(" 已启用上下文压缩:更早的历史以摘要形式计入,原文完整保留。")),M>1&&F.push(n.gray(` 技能目录出现 ${M} 次:会话中途技能清单变过,每次变化追加一份完整替换目录(旧的保留为历史)。`));const A=function(t){if(!t)return"";const o=Number(t.prompt_cache_hit_tokens??t.prompt_tokens_details?.cached_tokens);if(!Number.isFinite(o))return"";const r=Number(t.prompt_tokens)||0;if(r<=0)return"";const e=Math.round(o/r*100);return n.gray(` 上一轮前缀缓存命中:${u(o)} / ${u(r)}(${e}%)——命中的部分按缓存价计费`)}(d);return A&&F.push(A),F.push(n.gray(" /compact立即压缩 · /context再次查看")),F.join("\n")}
@@ -0,0 +1 @@
1
+ import{contextLimitHint as t,contextLimitOf as e,currentPlan as o,noteContextLimit as n}from"./plan-limits.js";const r=new Set(["image_url","input_image","image","file","input_file","input_audio","audio","video_url","input_video"]);export const MEMBER_TRIGGER_RATIO=.9;export function memberContextLimit(n){if(!n)return 0;let r=0;try{r=e(o(n))}catch{r=0}const i=t(n);return i>0&&(0===r||i<r)?i:r}const i=/会员等级的上下文上限\s*(\d+)\s*token/;export function noteMemberLimitFromError(t,e){const o=i.exec(String(e||"")),r=o?Number(o[1]):0;return r>0&&n(t,r),r}function c(t){const e="string"==typeof t?t.length:0;return e?Math.max(1,Math.floor(e/3)):0}const u=new WeakMap;function s(t){if(!t||"object"!=typeof t)return 0;const e=function(t){const e=t.content;return 31*("string"==typeof e||Array.isArray(e)?e.length:0)+(t.tool_calls?t.tool_calls.length:0)}(t),o=u.get(t);if(o&&o.key===e)return o.tok;let n=0;if("string"==typeof t.content)n+=c(t.content);else if(Array.isArray(t.content))for(const e of t.content)e&&"object"==typeof e&&("string"==typeof e.text?n+=c(e.text):r.has(e.type)&&(n+=1e3));return t.tool_calls&&(n+=c(JSON.stringify(t.tool_calls))),u.set(t,{key:e,tok:n}),n}export function platformPromptTokens(t){let e=0;for(const o of t||[])e+=s(o);return e}export function platformMessageTokens(t){return s(t)}function f(t,e,o,n){const r=memberContextLimit(t);return r>0?Math.min(o,Math.max(n,Math.floor(r*e*3))):o}export function toolResultCharBudget(t,e){return f(t,.3,e,2e3)}export function summaryCharBudget(t,e){return f(t,.6,e,4e3)}export function catalogTokenBudget(t){const e=memberContextLimit(t);return e>0?Math.floor(.15*e):1/0}
package/src/context.js CHANGED
@@ -1 +1 @@
1
- import n from"node:fs";import e from"node:path";import t from"node:os";import{execSync as r}from"node:child_process";import{loadMemory as i}from"./memory.js";import{skillCatalog as o}from"./skills.js";import{buildRepoMap as s}from"./codeindex.js";import{loadSquads as a}from"./squad.js";import{loadAgents as c}from"./subagent.js";import{COMPUTER_PROMPT_HINT as l,computerUseEnabled as p}from"./computer.js";const m=["- 先了解现状再动手:并行使用 read_file/glob/grep/list_files 收集信息(一轮可同时调多个只读工具),不要臆测文件内容。","- read_file 输出的「行号→」前缀仅供定位,不是文件内容;写 edit_file 的 old_string/new_string 时绝不能包含行号前缀。","- 多步骤任务先用 todo_write 列出计划,并随进度更新状态(同一时刻至多一项 in_progress);单步小事不必用。","- 大而可拆的任务优先并行铺开,别一个人串行啃:互相独立的调研/检索/分析子任务用 dispatch_agents 一次派出多路(每个子任务须自足,多路共用的背景放 shared_context,不要逐条重复抄);单个独立子任务用 dispatch_agent;跨角色的复杂任务用 dispatch_member 按专家团分工派发,独立任务同一轮多次调用即并行。子智能体只读、在隔离上下文中执行、只回传结论,因此主线上下文保持精简、总耗时按最慢的一路算。(这些工具未挂载时就自己做;简单任务直接做,不要为拆而拆——派发本身也有开销。)","- 收到多路子任务结论后,先交叉核对再采信:结论之间矛盾、或与你已知事实冲突时,用本机工具亲自复核关键点,不要把子智能体的说法当既成事实直接写进产物。","- 改代码优先用 edit_file 做最小精确改动;新建文件才用 write_file。遵循项目既有风格与命名。","- 用 run_bash 执行构建/测试/git 等;改动后尽量运行测试或构建自检,失败要定位修复而不是忽略。","- 处理数据/表格/文档/各类文件时,用 run_python(内置 pandas/openpyxl/xlrd/pypdf/python-docx/python-pptx/matplotlib 等)在本机精确处理,绝不要只凭肉眼或纯文本摘要臆断数字;先摸清结构(表头在第几行/多 Sheet/合并单元格/金额是否文本/日期格式/编码)再算,关键中间量 print 出来核对。","- 复杂数据任务优先载入并复用现成技能脚本,别从零手搓易错的解析:读任意文件(Excel/PDF/Word/PPT/OFD/RTF/图片…)用 file-ingest 的 read_any;Excel 分析/透视/对账用 xlsx-analysis(smart_read/sheet_map/reconcile);多表关联匹配用 data-wrangling(safe_join)、维度透视交叉用 pivot-crosstab、统计检验用 statistical-testing、回归预测用 regression-forecast。跨文档/跨表交叉分析务必口径一致、逐步可复核。","- 需要在线资料(文档/API 说明)可用 fetch_url;带明确目的使用,不要漫游。","- 环境与应用自动化:install_packages 装 Node.js 包、setup_environment 一键配置运行环境、install_plugin 装 VS Code/JetBrains 插件、open_browser/open_path/open_app 打开浏览器/文件管理器/任意应用。这些操作会先征询用户「是否继续」;被拒绝时不要重试,改为说明手动做法。","- 破坏性或不可逆操作(删除、强推、重置等)前先说明意图;写/执行类工具会请用户授权,被拒绝时据反馈调整方案。","- 不要把密钥、令牌等敏感值回显到输出或写进代码。","- 简洁直接,用中文回答;完成后用一两句话总结改了什么、如何验证的。","- 深度思考/推理过程(reasoning、思维链)默认也用简体中文书写,除非用户明确要求其他语言。"];export function buildSystemPrompt(a,c={}){const l=function(n){const e=e=>r(e,{cwd:n,stdio:["ignore","pipe","ignore"]}).toString().trim();try{const n=e("git rev-parse --abbrev-ref HEAD");let t="";try{t=e("git status --short")}catch{}return{branch:n,status:t}}catch{return null}}(a),p=function(t){for(const r of["HUICAI.md","AGENTS.md","CLAUDE.md"])try{const i=n.readFileSync(e.join(t,r),"utf8");if(i.trim())return{name:r,content:i.slice(0,8e3)}}catch{}return null}(a),d=function(){try{const r=n.readFileSync(e.join(t.homedir(),".huicai","HUICAI.md"),"utf8");return r.trim()?r.slice(0,4e3):null}catch{return null}}();let u="";if(c.persona&&c.persona.trim()?(u+="# 你的角色设定\n"+c.persona.trim()+"\n\n",u+="# 运行方式\n",u+="你现在运行在用户终端里,可通过工具在用户本机的项目里读写文件、搜索、执行 shell 命令来真正完成任务。请在保持上述角色设定的前提下遵循以下准则:\n"):(u+="你是 huicai,一个运行在用户终端里的专业自治编码与自动化智能体。\n",u+="你能通过工具在用户本机的项目里读写文件、搜索、执行 shell 命令,从而真正完成任务,而不仅是给建议。\n\n",u+="# 工作准则\n"),u+=m.join("\n")+"\n\n",u+="# 运行环境\n",u+=`- 工作目录: ${a}\n`,u+=`- 操作系统: ${t.platform()} ${t.release()} (${t.arch()})\n`,u+=`- 日期: ${(new Date).toISOString().slice(0,10)}\n`,l&&(u+=`- Git 分支: ${l.branch}\n`,l.status)){u+=`- 未提交改动:\n${l.status.split("\n").slice(0,30).map(n=>" "+n).join("\n")}\n`}const _=function(e){try{return n.readdirSync(e,{withFileTypes:!0}).filter(n=>".git"!==n.name&&"node_modules"!==n.name).slice(0,60).map(n=>n.isDirectory()?n.name+"/":n.name).join(" ")}catch{return""}}(a);_&&(u+=`- 顶层条目: ${_}\n`);const f=function(){const n=process.env.HUICAI_IDE_FILE,e=process.env.HUICAI_IDE_SELECTION,t=process.env.HUICAI_IDE_DIAGNOSTICS;if(!n&&!e&&!t)return"";let r="";n&&(r+=`- 用户当前正在编辑的文件: ${n}\n`);if(e){r+="- 用户当前选中的代码片段:\n```\n"+(e.length>4e3?e.slice(0,4e3)+"\n…(选区已截断)":e)+"\n```\n"}if(t){r+="- 编辑器诊断(错误/警告,来自 Problems 面板):\n```\n"+(t.length>4e3?t.slice(0,4e3)+"\n…(诊断已截断)":t)+"\n```\n"}return r}();if(f&&(u+="\n# 编辑器上下文(来自 IDE 集成)\n"+f),d&&(u+=`\n# 全局用户说明(~/.huicai/HUICAI.md)\n${d}\n`),p&&(u+=`\n# 项目说明(${p.name})\n${p.content}\n`),!1!==c.repoMap)try{const n=s(a);n&&(u+=`\n# 项目地图(关键文件与主要符号;用 search_code 按概念检索相关实现)\n${n}\n`)}catch{}const g=o(a);if(g&&(u+=`\n# 可用技能(本地 Agent Skills)\n${g}\n`),u+=capabilityCatalog(a,c),!1!==c.memory){const n=i(a);n&&(u+=`\n# 长期记忆(此前会话经 memory_write 记录,跨会话生效)\n${n}\n`,u+="(发现新的、值得跨会话记住的稳定事实/偏好/结论时,用 memory_write 记录。)\n")}return c.append&&String(c.append).trim()&&(u+="\n# 附加指示\n"+String(c.append).trim()+"\n"),u}export function capabilityCatalog(n,e={}){let t="",r=[];try{r=a(n)}catch{}r.length&&(t+="\n# 可召唤的专家团(本地,dispatch_member 派发成员)\n",t+=r.slice(0,20).map(n=>{const e=String(n.when||n.description||"").replace(/\s+/g," ").slice(0,100),t=(n.members||[]).map(n=>n.callName||n.id).filter(Boolean).slice(0,6).join("、");return`- ${n.name}${e?":"+e:""}${t?`(成员:${t})`:""}`}).join("\n")+"\n",t+="跨角色、要分工协作的复杂任务,直接用 dispatch_member 把子任务派给对应成员,不必等用户点名。\n");let i=[];try{i=c(n)}catch{}return i.length&&(t+="\n# 可调用的子智能体人格(本地,dispatch_agent 指定 agent 参数)\n",t+=i.slice(0,20).map(n=>`- ${n.name}${n.description?":"+String(n.description).replace(/\s+/g," ").slice(0,100):""}`).join("\n")+"\n"),Array.isArray(e.experts)&&e.experts.length&&(t+="\n# 可咨询的平台专家(consult_expert / consult_experts)\n",t+=e.experts.slice(0,20).map(n=>`- [${n.type||""}]${n.name}${n.description?":"+String(n.description).slice(0,80):""}`).join("\n")+"\n"),e.mcpCatalog&&String(e.mcpCatalog).trim()&&(t+="\n# 外部能力连接器(MCP)\n"+String(e.mcpCatalog).trim()+"\n需要当前工具覆盖不到的能力时,先 list_mcp_servers 看有什么,再 connect_mcp 连上——连接后新工具下一轮即可调用,无需重开会话。\n"),!1!==e.computerUse&&p(e.config||{})&&(t+=l+"\n"),!1!==e.automation&&(t+="\n# 自动化与提醒(主动使用,不必等用户明说)\n- 用户表达出「以后每天/每周/每隔多久…」这类周期性意图时,直接用 create_automation 把它落成定时任务,再向用户复述频率与内容确认;要改/停/删已有任务先 list_automations 定位。\n- 当前任务需要「等一会儿再做」(等构建完成、等对方回复、明早再跑)时,用 schedule_followup 给自己排后续,不要让用户干等,也不要假装已经完成。\n- 长任务跑完、或遇到必须由人拍板的岔路时,用 send_notification 把用户叫回来(用户可能已经离开电脑)。\n- 预计要跑很久的任务,开始前 set_wake_lock(on=true) 防止电脑休眠导致半途中断,结束后释放。\n"),t}
1
+ import e from"node:fs";import n from"node:path";import t from"node:os";import{execSync as r}from"node:child_process";import{loadMemory as i}from"./memory.js";import{buildRepoMap as o}from"./codeindex.js";import{loadSquads as s}from"./squad.js";import{loadAgents as a}from"./subagent.js";import{COMPUTER_PROMPT_HINT as c,computerUseEnabled as l}from"./computer.js";import{CREW_GUIDELINE as p}from"./crew.js";const m=["- 先了解现状再动手:并行使用read_file/glob/grep/list_files收集信息(一轮可同时调多个只读工具),不要臆测文件内容。","- 编码前必须通读需求涉及的源码上下文、调用链、项目规范、文档与约定;表格用read_document或file-ingest脚本解析,图片用可用视觉工具查看。必要时先分析全库;不得以搜索片段代替通读。",'- 编程任务先load_skill("eng-develop"),用engineering_plan登记实际读过的references、拟改targets、调用链、规范、体验考虑及验收checks;新建项目也先调研目录。工具未挂载时照此执行并如实留证。',"- 任何代码改动后必须真实跑单测、启动项目、冒烟和受影响接口/功能(含正常、错误、边界路径);用engineering_verify逐项运行,构建通过不等于启动成功。涉及客户端即使只改共享核心也要client=true,在真实宿主或设备模拟器走交互并保留证据,模拟视口不等于原生设备验证。","- shell、脚本、终端、MCP和自动修复同样遵守先分析后修改;将其涉及的文件列入targets,不得绕过流程。修改后重跑受影响验证;环境/账号/设备缺失属于未验证,不得用mock、静态检查或口头声明替代。","- read_file输出的「行号→」前缀仅供定位,不是文件内容;写edit_file的old_string/new_string时绝不能包含行号前缀。","- 多步骤任务先用todo_write列出计划,并随进度更新状态(同一时刻至多一项in_progress);单步小事不必用。","- 大而可拆的任务优先并行铺开,别一个人串行啃:互相独立的调研/检索/分析子任务用dispatch_agents一次派出多路(每个子任务须自足,多路共用的背景放shared_context,不要逐条重复抄);单个独立子任务用dispatch_agent;跨角色的复杂任务用dispatch_member按专家团分工派发,独立任务同一轮多次调用即并行。子智能体只读、在隔离上下文中执行、只回传结论,因此主线上下文保持精简、总耗时按最慢的一路算。(这些工具未挂载时就自己做;简单任务直接做,不要为拆而拆——派发本身也有开销。)",p,"- 收到多路子任务结论后,先交叉核对再采信:结论之间矛盾、或与你已知事实冲突时,用本机工具亲自复核关键点,不要把子智能体的说法当既成事实直接写进产物。","- 改代码优先用edit_file做最小精确改动;新建文件才用write_file。遵循项目既有风格与命名。","- 用run_bash执行构建/git等;改动后要运行测试自检,失败要定位修复而不是忽略。","- **跑单元测试用run_tests,不要用run_bash**:它自动探测框架(Maven/Gradle/pytest/Node)并把结果解析成「通过/失败数 + 每个失败用例的期望值与实际值 + 堆栈里第一个非测试非框架的位置(也就是你哪一行业务代码出的问题)」。run_bash只会给你几千行原始输出,要你自己从噪声里找——那正是「测试失败了,可能是X的问题」这类猜测结论的来源。定位问题时用filter只跑相关用例,比全量跑快得多。",'- 写完或改完代码后用code_inspect自查(逻辑/注释与实现是否一致/可读性/结构/测试/安全/一致性七类),**必修项清零再说完成**;肉眼扫一遍不算自查——空捕获、注释里写着早已改名的形参、测试里的 .only残留,这些都是"看起来很对"的,读第二遍也发现不了。fix=true只能修五类改法唯一的问题,逻辑与安全类它不碰、只报给你,要你自己判断怎么改。','- 要补单元测试时先test_plan算清该测什么(哪些函数没覆盖、每个有几条路、参数边界在哪、项目用什么框架放哪个目录),再照着写用例。直接开写的产出多半是"调一次、断言不为空"——覆盖率上去了,缺陷一个也拦不住。',"- 处理数据/表格/文档/各类文件时,用run_python(内置pandas/openpyxl/xlrd/pypdf/python-docx/python-pptx/matplotlib等)在本机精确处理,绝不要只凭肉眼或纯文本摘要臆断数字;先摸清结构(表头在第几行/多Sheet/合并单元格/金额是否文本/日期格式/编码)再算,关键中间量print出来核对。","- 复杂数据任务优先载入并复用现成技能脚本,别从零手搓易错的解析:读任意文件(Excel/PDF/Word/PPT/OFD/RTF/图片…)用file-ingest的read_any;Excel分析/透视/对账用xlsx-analysis(smart_read/sheet_map/reconcile);多表关联匹配用data-wrangling(safe_join)、维度透视交叉用pivot-crosstab、统计检验用statistical-testing、回归预测用regression-forecast。跨文档/跨表交叉分析务必口径一致、逐步可复核。",'- 需要在**对话里**呈现**看板/仪表盘/指标卡/数据表格+图表/检查清单/时间线/摘要卡**时用render_ui:你只给组件树与数据(靠 {"path":"字段"} 绑定),排版与呈现由引擎负责,桌面端渲染为可视化面板、其它终端自动降级为等价文本。**不要手写HTML/CSS糊一个界面,也不要拿Markdown表格凑数**。组件只能用注册过的那些——组件名与必填字段先调ui_catalog看清单,别猜。(这条只管对话内的数据呈现;用户要的交付物本身就是网页/H5/前端项目时,照常写真实的HTML/CSS/JS文件,见下一条。)','- 做网页/H5/落地页/邀请函/活动页/前端界面这类「用户会直接打开、直接用」的交付物:先load_skill("web-craft"),按它定下设计方向与设计令牌(配色/字体/间距/动效基调)再写码。交付要达到**作品级**:真实得体的文案(不写Lorem、不把「把照片放到assets/img」这种开发说明露给访客)、每个按钮都真能用、空/加载/出错态齐全、手机优先、不依赖国内打不开的外链、没有素材就用CSS/SVG画而不是留破图。写完用 **web_inspect** 在手机与桌面视口真跑一遍(关键交互用actions走一遍),**必修清零再交付**,并把截图路径告诉用户。(本机没有Chrome/Edge时没有这件工具——那就如实告诉用户页面未经浏览器验证,不要假装查过。)',"- 交付即可用:新建项目要能照着说明直接跑起来(依赖写进清单,启动/构建/测试命令你亲自跑通);用户一定会碰到的基本面——出错了怎么办、没有数据时显示什么、手机上能不能用、输入非法值会怎样——要做全,而不是只做字面要求的最小版本;超出需求的新功能先问再做。没验证过的不要说「完成」,说清验证了什么、还有什么没验证。",'- 需要出图(系统总体架构图/技术架构图/业务架构图、流程图与数据流、接口时序图、数据模型图、功能结构图、演进路线图)时一律用render_diagram:你只负责给出结构化spec,版式/对齐/配色/连线走向由引擎负责,产出矢量SVG(放大不失真)+ 高清PNG。**不要手写SVG/HTML/mermaid,也不要用run_python/matplotlib画架构图**——那些方式必然重叠错位。字段拿不准先调diagram_schema(type)。图要画到能直接进评审材料的程度:先读透源码/设计文档再画,节点名精确(用系统里真实的模块与服务名),每个节点补一句desc与技术栈tags,层/域的职责写进desc,必要时加legend与notes;不要只画几个空方块。画**专利附图**时置theme:"patent" 并给figure:{no,title}(黑白线条、直角、无阴影,符合附图形式要求)。',"- 写要过评审的报告(可研/项目建议书/立项/结题/技术方案/商业计划书)先载入report-craft,按「取证→测算→配图→成文→自审」走:project_facts从代码库数出规模事实(条条带来源);财务数字一律用fin_model算(NPV/IRR/回收期/敏感性,不心算);数据图用render_chart(甘特/投资构成/累计现金流/龙卷风/风险矩阵),不用run_python手写;插进文档的结构图加header:false,图表用 {#fig:x} 标注、正文写 @fig:x交叉引用;成稿后docx_inspect必修清零。**时态**:编制日期之后才是计划,已完成的写进现有工作基础;拿不到的人名/资质/合同写「[待补:…]」,不编造。","- 需要在线资料(文档/API说明)可用fetch_url;带明确目的使用,不要漫游。","- 环境与应用自动化:install_packages装Node.js包、setup_environment一键配置运行环境、install_plugin装VS Code/JetBrains插件、open_browser/open_path/open_app打开浏览器/文件管理器/任意应用。这些操作会先征询用户「是否继续」;被拒绝时不要重试,改为说明手动做法。","- 破坏性或不可逆操作(删除、强推、重置等)前先说明意图;写/执行类工具会请用户授权,被拒绝时据反馈调整方案。","- 需要用户选择方案或范围时,调用 ask_ui 的 single_choice / multi_choice,提供带取舍说明的 options,让用户在选择视图中确认;不要要求在聊天输入框中回复编号或方案名称。只有开放式补充信息才用 text。","- 不要把密钥、令牌等敏感值回显到输出或写进代码。","- 简洁直接,用中文回答;完成后用一两句话总结改了什么、如何验证的。","- 深度思考/推理过程(reasoning、思维链)默认也用简体中文书写,除非用户明确要求其他语言。"];export const VOLATILE_HEADING="# 当前状态(每轮可能变化)";export function stablePrefixOf(e){const n=String(e).indexOf("# 当前状态(每轮可能变化)");return n<0?String(e):String(e).slice(0,n)}export function buildSystemPrompt(s,a={}){const c=function(e){const n=n=>r(n,{cwd:e,stdio:["ignore","pipe","ignore"]}).toString().trim();try{const e=n("git rev-parse --abbrev-ref HEAD");let t="";try{t=n("git status --short")}catch{}return{branch:e,status:t}}catch{return null}}(s),l=function(t){for(const r of["HUICAI.md","AGENTS.md","CLAUDE.md"])try{const i=e.readFileSync(n.join(t,r),"utf8");if(i.trim())return{name:r,content:i.slice(0,8e3)}}catch{}return null}(s),p=function(){try{const r=e.readFileSync(n.join(t.homedir(),".huicai","HUICAI.md"),"utf8");return r.trim()?r.slice(0,4e3):null}catch{return null}}();let d="";if(a.persona&&a.persona.trim()?(d+="# 你的角色设定\n"+a.persona.trim()+"\n\n",d+="# 运行方式\n",d+="你现在运行在用户终端里,可通过工具在用户本机的项目里读写文件、搜索、执行shell命令来真正完成任务。请在保持上述角色设定的前提下遵循以下准则:\n"):(d+="你是huicai,一个运行在用户终端里的专业自治编码与自动化智能体。\n",d+="你能通过工具在用户本机的项目里读写文件、搜索、执行shell命令,从而真正完成任务,而不仅是给建议。\n\n",d+="# 工作准则\n"),d+=m.join("\n")+"\n\n",d+="# 运行环境\n",d+=`- 工作目录: ${s}\n`,d+=`- 操作系统: ${t.platform()} ${t.release()} (${t.arch()})\n`,p&&(d+=`\n# 全局用户说明(~/.huicai/HUICAI.md)\n${p}\n`),l&&(d+=`\n# 项目说明(${l.name})\n${l.content}\n`),d+=capabilityCatalog(s,a),!1!==a.memory){const e=i(s);e&&(d+=`\n# 长期记忆(此前会话经memory_write记录,跨会话生效)\n${e}\n`,d+="(发现新的、值得跨会话记住的稳定事实/偏好/结论时,用memory_write记录。)\n")}d+=`\n# 当前状态(每轮可能变化)\n- 日期: ${(new Date).toISOString().slice(0,10)}\n`;const _=function(n){try{return e.readdirSync(n,{withFileTypes:!0}).filter(e=>".git"!==e.name&&"node_modules"!==e.name).slice(0,60).map(e=>e.isDirectory()?e.name+"/":e.name).join(" ")}catch{return""}}(s);if(_&&(d+=`- 顶层条目: ${_}\n`),c&&(d+=`- Git分支: ${c.branch}\n`,c.status)){d+=`- 未提交改动:\n${c.status.split("\n").slice(0,30).map(e=>" "+e).join("\n")}\n`}if(!1!==a.repoMap)try{const e=o(s);e&&(d+=`\n## 项目地图(关键文件与主要符号;用search_code按概念检索相关实现)\n${e}\n`)}catch{}const g=function(){const e=process.env.HUICAI_IDE_FILE,n=process.env.HUICAI_IDE_SELECTION,t=process.env.HUICAI_IDE_DIAGNOSTICS;if(!e&&!n&&!t)return"";let r="";e&&(r+=`- 用户当前正在编辑的文件: ${e}\n`);if(n){r+="- 用户当前选中的代码片段:\n```\n"+(n.length>4e3?n.slice(0,4e3)+"\n…(选区已截断)":n)+"\n```\n"}if(t){r+="- 编辑器诊断(错误/警告,来自Problems面板):\n```\n"+(t.length>4e3?t.slice(0,4e3)+"\n…(诊断已截断)":t)+"\n```\n"}return r}();return g&&(d+="\n# 编辑器上下文(来自IDE集成)\n"+g),a.append&&String(a.append).trim()&&(d+="\n# 附加指示\n"+String(a.append).trim()+"\n"),d}export function capabilityCatalog(e,n={}){let t="",r=[];try{r=s(e)}catch{}r.length&&(t+="\n# 可召唤的专家团(本地,dispatch_member派发成员)\n",t+=r.slice(0,20).map(e=>{const n=String(e.when||e.description||"").replace(/\s+/g," ").slice(0,100),t=(e.members||[]).map(e=>e.callName||e.id).filter(Boolean).slice(0,6).join("、");return`- ${e.name}${n?":"+n:""}${t?`(成员:${t})`:""}`}).join("\n")+"\n",t+="跨角色、要分工协作的复杂任务,直接用dispatch_member把子任务派给对应成员,不必等用户点名。\n");let i=[];try{i=a(e)}catch{}return i.length&&(t+="\n# 可调用的子智能体人格(本地,dispatch_agent指定agent参数)\n",t+=i.slice(0,20).map(e=>`- ${e.name}${e.description?":"+String(e.description).replace(/\s+/g," ").slice(0,100):""}`).join("\n")+"\n"),Array.isArray(n.experts)&&n.experts.length&&(t+="\n# 可咨询的平台专家(consult_expert / consult_experts)\n",t+=n.experts.slice(0,20).map(e=>`- [${e.type||""}]${e.name}${e.description?":"+String(e.description).slice(0,80):""}`).join("\n")+"\n"),n.mcpCatalog&&String(n.mcpCatalog).trim()&&(t+="\n# 外部能力连接器(MCP)\n"+String(n.mcpCatalog).trim()+"\n需要当前工具覆盖不到的能力时,先list_mcp_servers看有什么,再connect_mcp连上——连接后新工具下一轮即可调用,无需重开会话。\n"),!1!==n.computerUse&&l(n.config||{})&&(t+=c+"\n"),!1!==n.automation&&(t+="\n# 自动化与提醒(主动使用,不必等用户明说)\n- 用户表达出「以后每天/每周/每隔多久…」这类周期性意图时,直接用create_automation把它落成定时任务,再向用户复述频率与内容确认;要改/停/删已有任务先list_automations定位。\n- 当前任务需要「等一会儿再做」(等构建完成、等对方回复、明早再跑)时,用schedule_followup给自己排后续,不要让用户干等,也不要假装已经完成。\n- 长任务跑完、或遇到必须由人拍板的岔路时,用send_notification把用户叫回来(用户可能已经离开电脑)。\n- 预计要跑很久的任务,开始前set_wake_lock(on=true) 防止电脑休眠导致半途中断,结束后释放。\n"),t}
package/src/crew.js ADDED
@@ -0,0 +1 @@
1
+ export const DEFAULT_DISPATCHES_PER_SUBTASK=2;export const DEFAULT_ADVISORIES=5;export const DEFAULT_SUBTASKS=4;export const MAX_SUBTASKS=30;export const MAX_DEGRADED_ROLES=1;export const ADVISOR_WHEN=["方案评审:动手前,方案的结构性选择需要独立第二意见","成稿评审:交付前,对最终产物做一次独立复核","两个工人的结论互相矛盾,且矛盾点超出你已掌握的上下文(自己核不动)",'同一个子任务连续两次校验失败(不是"有点难",是同一处栽了两回)',"你发现判断落在了成功标准之外(做着做着发现要解决的不是原来那个问题)","方案需要结构性调整(不是改细节,是推翻重来)"];export const CREW_GUIDELINE="- 你是**编排者**:负责规划、派发、校验、综合。三条纪律:① 可拆的活派给子智能体(工人),不要自己埋头串行啃——但简单任务直接做,别为拆而拆;② **绝不把执行丢给顾问**(ask_advisor要的是判断,不是让它替你干活,那是拿最贵的模型当工人使);③ 顾问只在承诺边界问(方案评审 / 成稿评审 / 工人结论矛盾且你核不动 / 同一子任务连栽两次 / 发现跑偏出成功标准 / 方案要推翻重来),不是遇到难题就问——问滥了它就退化成第二个工人。";export const VERIFY_DISCIPLINE="## 校验必须动真格\n声称做完之前,去**实际检验交付物本身**:跑真正的命令、读真正的输出、检查真正的产物内容。\n下面这些都**什么也没证明**,不要拿它们当验证:\n- grep一下README或文档里提到了这个功能\n- 测了一个相邻的东西(隔壁能跑不代表这个能跑)\n- 打印一句True、或者命令退出码是 0(退出码 0 只说明进程没崩)\n- 再确认一遍文件存在(文件在不等于内容对)\n- 把代码读了一遍觉得逻辑没问题(读得懂不等于跑得通)\n验证不了就**如实说验证不了**,并说清楚缺什么。一个诚实的「未验证」远比一个假的「已验证」有用——后者会让主智能体把它当既成事实写进产物。";export function createCrewBudget(e={}){const t={declared:!1,subtasks:s(e.subtasks,1,30,4),perSubtask:s(e.dispatchesPerSubtask,1,10,2),advisories:s(e.advisories,0,20,5),successCriteria:"",spent:{dispatch:0,advisor:0},warned:{dispatch:!1,advisor:!1},hitCap:!1,degraded:[]},r=s=>"advisor"===s?t.advisories:t.subtasks*t.perSubtask;function a(){const s=r("dispatch");return`子任务 ${t.subtasks} 个 × 每个最多 ${t.perSubtask} 次派发 = ${s} 次派发额度;顾问咨询 ${t.advisories} 次`+(t.successCriteria?`\n成功标准:${t.successCriteria}`:"")}function n(s,e,t){return`【预算已用尽】${"advisor"===s?"顾问咨询":"子智能体派发"}额度 ${e} 次已全部用完,本次调用未执行。\n现在**停下来向用户报告**,不要改用别的工具绕过去,也不要自己埋头把剩下的活干完(那只是把超支藏起来)。\n报告里说清三件事:已经完成了什么、还剩什么没做、再给多少额度能做完。由用户决定是否继续。`+(t?"":"\n(这条已经提示过一次了——请立即向用户报告,不要再试。)")}function i(){const s=r("dispatch"),e=[`派发:${t.spent.dispatch} / ${s}`,`顾问:${t.spent.advisor} / ${t.advisories}`];t.declared||e.push("(未立框,按默认额度运行;调用plan_budget可显式声明)"),t.successCriteria&&e.push(`成功标准:${t.successCriteria}`);for(const s of t.degraded)e.push(`⚠ 降级:${s.role} 由编排者兼任${s.reason?`(${s.reason})`:""}`);return e.join("\n")}return{declare:function({subtasks:e,successCriteria:r,advisories:n}={}){const i=s(e,1,30,t.subtasks),o=void 0===n?t.advisories:s(n,0,20,t.advisories);return t.hitCap&&(i>t.subtasks||o>t.advisories)?{refused:!0,text:"【不能自行加额度】本次已经撞到过额度上限。要继续,请**向用户说明还需要多少**,由他用 /crew放额度。\n当前:"+a()}:(t.subtasks=i,t.advisories=o,t.successCriteria=String(r||"").trim(),t.declared=!0,{refused:!1,text:a()})},grant:function({subtasks:s=0,advisories:e=0}={}){const r=Math.max(0,Math.floor(Number(s)||0)),a=Math.max(0,Math.floor(Number(e)||0));return r||a?(t.subtasks=Math.min(30,t.subtasks+r),t.advisories=Math.min(20,t.advisories+a),t.hitCap=!1,t.warned.dispatch=!1,t.warned.advisor=!1,i()):i()},plan:a,spend:function(s,e=1){const a=r(s),i=Math.max(1,Math.floor(Number(e)||1)),o=t.spent[s]||0;if(o>=a){const e=!t.warned[s];return t.warned[s]=!0,t.hitCap=!0,{ok:!1,message:n(s,a,e)}}const c=Math.min(i,a-o);return t.spent[s]=o+c,{ok:!0,granted:c,note:(c<i?`\n【预算】本次请求 ${i} 路,额度只剩 ${c} 路,已按 ${c} 路派发。不够用就停下来告诉用户还需要多少,不要偷偷少做还说做完了。`:"")||void 0}},degrade:function(s,e=""){const r=String(s||"").trim();return!r||t.degraded.some(s=>s.role===r)?{ok:!0,message:null}:t.degraded.length>=1?{ok:!1,message:`【降级被拒】已经有一个角色(${t.degraded[0].role})由你兼任了,不能再兼任 ${r}。两个角色都自己兼,三层班子就名存实亡了。停下来告诉用户缺什么、需要配置什么才能补上。`}:(t.degraded.push({role:r,reason:String(e||"").trim()}),{ok:!0,message:`【已降级】${r}层不可用${e?`(${e})`:""},由你临时兼任。三条约束:① 预算不变,兼任不额外给额度;② 最终产物里受影响的部分要**打上降级标记**,说明这部分没有经过独立的`+r+"层;③ 注明**上下文隔离性已丢失**——你既是执行者又是评审者,看不出自己的盲区,这一点必须让用户知道,由他决定要不要另找人复核。"})},status:i,snapshot:function(){return{declared:t.declared,subtasks:t.subtasks,dispatch:{used:t.spent.dispatch,cap:r("dispatch")},advisor:{used:t.spent.advisor,cap:t.advisories},degraded:t.degraded.map(s=>({...s})),successCriteria:t.successCriteria}}}}function s(s,e,t,r){const a=Math.floor(Number(s));return Number.isFinite(a)?Math.min(t,Math.max(e,a)):r}export function withBudget(s,e,t,r=()=>1){if(!s||!e)return s;return{...s,run:async(a,...n)=>{let i=1;try{i=Math.max(1,Math.floor(Number(r(a))||1))}catch{i=1}const o=e.spend(t,i);if(!o.ok)return o.message;const c=await s.run(a,...n);return o.note?String(c??"")+"\n"+o.note:c}}}export function makePlanBudgetTool(s,e=null){return{readOnly:!0,spec:{type:"function",function:{name:"plan_budget",description:"开工前立框:声明这个任务大概拆成几个子任务、成功标准是什么,据此确定本次的派发与顾问额度。**大任务动手前先调它**——说不出自己要派几次,多半也没想清楚要干什么。额度用尽时工具会拒绝执行并要求你向用户报告,不会静默超支。小任务不必调。",parameters:{type:"object",properties:{subtasks:{type:"integer",description:"打算拆成几个子任务(1~30)"},success_criteria:{type:"string",description:'怎样算做完了——具体、可判定,不要写"高质量完成"这类空话'},advisories:{type:"integer",description:"顾问咨询次数上限,默认 5;顾问贵在稀少"}},required:["subtasks","success_criteria"]}}},run:async t=>{const r=s.declare({subtasks:t?.subtasks,successCriteria:t?.success_criteria,advisories:t?.advisories});if(r.refused)return r.text;if("function"==typeof e)try{e(r.text)}catch{}return"【本次预算已立框】\n"+r.text+"\n\n额度用尽时派发/咨询工具会拒绝执行并要求你向用户报告。中途发现预算不够,就停下来说明还需要多少,由用户决定——不要压缩范围硬塞进额度里还说做完了。"}}}export function makeBudgetStatusTool(s){return{readOnly:!0,spec:{type:"function",function:{name:"budget_status",description:"查看本次任务的预算进度(已派发/已咨询/剩余额度、成功标准、降级情况)。派发密集时可自查,避免一头撞上额度用尽。",parameters:{type:"object",properties:{}}}},run:async()=>s.status()}}
package/src/crush.js ADDED
@@ -0,0 +1 @@
1
+ const t={log:{head:5,tail:8},match:{head:8,tail:4},list:{head:10,tail:4}},e=/(?:^|[\s\[\|:])(FATAL|CRITICAL|ERROR|SEVERE|WARN(?:ING)?)\b/i,n=new RegExp(["Traceback \\(most recent call last\\)","Exception in thread","\\w*(?:Error|Exception)\\s*:","panic:","Segmentation fault","core dumped","npm ERR!","fatal:","command not found","No such file or directory","Permission denied","exit(?:ed with)? (?:code |status )?[1-9]\\d*"].join("|"),"i"),r=/\b(?:0 errors?|no errors?|error[_-]?free|errors?:\s*0|失败\s*0|无错误)\b/i;export function queryOf(t,e){if(!e||"object"!=typeof e)return"";const n=(...t)=>{for(const n of t){const t=e[n];if("string"==typeof t&&t.trim())return t.trim()}return""};switch(t){case"grep":return n("pattern","query");case"search_code":case"search_history":default:return n("query","pattern");case"run_bash":return n("command");case"run_python":return""}}function o(t){return!r.test(t)&&(e.test(t)||n.test(t))}function s(t){const e=/^([^\s:]+):\d+[:-]/.exec(t);return e?e[1]:null}export function crush(e,r={}){const a=String(e??"");if(a.length<2500)return null;const i=function(t){switch(t){case"run_bash":case"run_python":return"log";case"grep":case"search_code":case"search_history":return"match";case"list_files":case"glob":return"list";default:return null}}(r.toolName);if(!i)return null;const c=a.split("\n");if(c.length<40)return null;const l=new Set,h=t[i];for(let t=0;t<Math.min(h.head,c.length);t++)l.add(t);for(let t=Math.max(0,c.length-h.tail);t<c.length;t++)l.add(t);let u=0;if("log"===i)for(let t=0;t<c.length&&u<40;t++)if(!l.has(t)&&o(c[t])){if(l.add(t),n.test(c[t])){t>0&&l.add(t-1);for(let e=t+1;e<Math.min(t+4,c.length);e++)l.add(e)}u++}const d=(f=r.query)?String(f).replace(/[\\^$.*+?()[\]{}|/]/g," ").split(/[\s,;:'"`]+/).map(t=>t.trim().toLowerCase()).filter(t=>t.length>=3&&!/^\d+$/.test(t)).slice(0,6):[];var f;let g=0;if(d.length)for(let t=0;t<c.length&&g<25;t++){if(l.has(t))continue;const e=c[t].toLowerCase();d.some(t=>e.includes(t))&&(l.add(t),g++)}if("match"===i){const t=new Set;for(const e of l){const n=s(c[e]);n&&t.add(n)}for(let e=0;e<c.length;e++){if(l.has(e))continue;const n=s(c[e]);n&&!t.has(n)&&(t.add(n),l.add(e))}}const m=c.length-l.size;if(m<10)return null;const p=[];let $=0,y=0,_=0;const x=()=>{y&&(p.push(`…【已省略 ${y} 行同质内容(${_} 字符)${r.spillPath?",全文见下方路径":""}】…`),$+=_,y=0,_=0)};for(let t=0;t<c.length;t++)l.has(t)?(x(),p.push(c[t])):(y++,_+=c[t].length+1);x();const w="log"===i?`保留首尾、${u} 处异常及其上下文${g?`、${g} 行查询命中`:""}`:"match"===i?"保留首尾、各来源代表行"+(g?`、${g} 行查询命中`:""):"保留首尾";return{text:p.join("\n"),kept:l.size,omitted:m,omittedChars:$,reason:w}}export function crushNote(t,e){const n=`\n…【统计式压缩:${t.reason};共省略 ${t.omitted} 行 / ${t.omittedChars} 字符】…`;return e?n+`\n完整输出已保存到:\n${e}\n需要被省略的部分时,用read_file读该绝对路径(可带offset/limit分段),或用grep以path指向该文件精确定位,不要因为这里被省略就重跑一遍工具。`:n+"\n(本次未能落盘,被省略的内容无法取回;如确需完整输出,请缩小查询范围重跑。)"}
package/src/daemon.js CHANGED
@@ -1 +1 @@
1
- import{spawn as o}from"node:child_process";import e from"node:fs";import r from"node:path";import n from"node:os";import{listTasks as t}from"./schedule.js";import{cronMatches as i}from"./cron.js";import{huicaiBin as c}from"./forks.js";import{acquireWakeLock as s,releaseWakeLock as p,presence as a,describePresence as d}from"./presence.js";export const DAEMON_LOG_DIR=r.join(n.homedir(),".huicai","daemon");export async function runDaemon({intervalMs:n=6e4,log:m=o=>process.stdout.write(o+"\n"),once:f=!1,spawnImpl:u}={}){const l=c();if(!l&&!u)throw new Error("无法定位 huicai 可执行文件(daemon 需在 CLI 环境运行)");e.mkdirSync(DAEMON_LOG_DIR,{recursive:!0});const h=u||(n=>{const t=e.openSync(r.join(DAEMON_LOG_DIR,n.id+".log"),"a"),i=o(process.execPath,[l,"-p",n.prompt,"--yolo","--plain"],{cwd:n.cwd,detached:!0,stdio:["ignore",t,t],env:process.env});return i.unref(),i.pid}),w=new Map,I=()=>{const o=new Date,e=o.toISOString().slice(0,16);let r=0;for(const n of t()){if(!n.cron||!n.prompt)continue;if(w.get(n.id)===e)continue;let t=!1;try{t=i(n.cron,o)}catch{t=!1}if(!t)continue;w.set(n.id,e);const c=h(n);r++,m(`[${e}] 触发 #${n.id}(pid ${c}):${String(n.prompt).replace(/\s+/g," ").slice(0,60)}`)}return r},g=I();if(f)return{fired:g};const D=s({reason:"huicai 调度守护运行中"});m(`huicai 调度守护已启动:每 ${Math.round(n/1e3)}s 检查 ~/.huicai/schedule.json(${t().length} 个任务)· Ctrl-C 退出`),m(D.ok?" · 已持防休眠锁:合盖/离开时系统不会睡眠,锁屏期间任务照常触发(屏幕点击类操作除外)。":` · 未能持防休眠锁(${D.reason}):系统若进入休眠,期间的触发点会被跳过。`);try{const o=await a();m(" · 当前在场状态:"+d(o))}catch{}const j=setInterval(I,n);return await new Promise(o=>{process.on("SIGINT",()=>{clearInterval(j),D.ok&&p({force:!0}),m("\n调度守护已停止。"),o()})}),{fired:g}}
1
+ import{spawn as o}from"node:child_process";import n from"node:fs";import e from"node:path";import t from"node:os";import{listTasks as r,setEnabled as i}from"./schedule.js";import{cronMatches as c}from"./cron.js";import{planVerdict as s,automationSuspendedMessage as a}from"./plan-limits.js";import{huicaiBin as p}from"./forks.js";import{acquireWakeLock as d,releaseWakeLock as m,presence as l,describePresence as f}from"./presence.js";export const DAEMON_LOG_DIR=e.join(t.homedir(),".huicai","daemon");export async function runDaemon({intervalMs:t=6e4,log:u=o=>process.stdout.write(o+"\n"),once:h=!1,spawnImpl:w,verdictFn:I=()=>s("agent_tasks"),setEnabledFn:$=i}={}){const g=p();if(!g&&!w)throw new Error("无法定位huicai可执行文件(daemon需在CLI环境运行)");n.mkdirSync(DAEMON_LOG_DIR,{recursive:!0});const y=w||(t=>{const r=n.openSync(e.join(DAEMON_LOG_DIR,t.id+".log"),"a"),i=o(process.execPath,[g,"-p",t.prompt,"--yolo","--plain"],{cwd:t.cwd,detached:!0,stdio:["ignore",r,r],env:process.env});return i.unref(),i.pid}),j=new Map,D=async()=>{const o=new Date,n=o.toISOString().slice(0,16);let e=0,t=null;for(const i of r()){if(!i.cron||!i.prompt)continue;if(!1===i.enabled)continue;if(j.get(i.id)===n)continue;let r=!1;try{r=c(i.cron,o)}catch{r=!1}if(!r)continue;if(j.set(i.id,n),null===t)try{t=await I()}catch{t="unknown"}if("denied"===t){try{$(i.id,!1)}catch{}u(`[${n}] 未执行 #${i.id}:${a()}`);continue}const s=y(i);e++,u(`[${n}] 触发 #${i.id}(pid ${s}):${String(i.prompt).replace(/\s+/g," ").slice(0,60)}`)}return e},_=await D();if(h)return{fired:_};const v=d({reason:"huicai调度守护运行中"});u(`huicai调度守护已启动:每 ${Math.round(t/1e3)}s检查 ~/.huicai/schedule.json(${r().length} 个任务)· Ctrl-C退出`),u(v.ok?" · 已持防休眠锁:合盖/离开时系统不会睡眠,锁屏期间任务照常触发(屏幕点击类操作除外)。":` · 未能持防休眠锁(${v.reason}):系统若进入休眠,期间的触发点会被跳过。`);try{const o=await l();u(" · 当前在场状态:"+f(o))}catch{}const O=setInterval(()=>{D().catch(()=>{})},t);return await new Promise(o=>{process.on("SIGINT",()=>{clearInterval(O),v.ok&&m({force:!0}),u("\n调度守护已停止。"),o()})}),{fired:_}}
package/src/decisions.js CHANGED
@@ -1 +1 @@
1
- import e from"node:fs";import t from"node:path";import r from"node:crypto";import{execFileSync as n}from"node:child_process";import{CONFIG_DIR as s}from"./config.js";export const DECISIONS_DIR=t.join(s,"decisions");const o=2e3;function i(e,t){return n("git",t,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}export function projectKey(e){let n="";try{n=i(e,["config","--get","remote.origin.url"])}catch{}if(!n)try{n=i(e,["rev-parse","--show-toplevel"])}catch{}return n||(n=t.resolve(e||".")),r.createHash("sha1").update(n).digest("hex").slice(0,16)}export function resolveAnchor(e){try{const t=i(e,["rev-parse","HEAD"]);if(!t)return{};let r=!1;try{r=i(e,["status","--porcelain"]).length>0}catch{}return{git:t,dirty:r}}catch{return{}}}function a(e){return t.join(DECISIONS_DIR,e,"graph.json")}function c(e){return{version:1,projectKey:e,nodes:[]}}export function loadGraph(t){try{const r=JSON.parse(e.readFileSync(a(t),"utf8"));return r&&Array.isArray(r.nodes)?(r.version=r.version||1,r.projectKey=t,r):c(t)}catch{return c(t)}}export function saveGraph(r){if(r&&r.projectKey&&Array.isArray(r.nodes))try{e.mkdirSync(t.join(DECISIONS_DIR,r.projectKey),{recursive:!0}),e.writeFileSync(a(r.projectKey),JSON.stringify(r),{mode:384})}catch(e){process.stderr.write(`(决策图保存失败,不影响主流程:${e.message})\n`)}}function d(e,t){return String(null==e?"":e).slice(0,t)}function l(e){return Array.isArray(e)?e.filter(e=>e&&e.option).slice(0,20).map(e=>({option:d(e.option,500),rejectedBecause:d(e.rejectedBecause,1e3)})):[]}export function recordDecision(e,t={},n={}){const s=projectKey(e),i=loadGraph(s),a=l(t.alternatives),c=a.length>0,p={id:"dec_"+Date.now().toString(36)+r.randomBytes(4).toString("hex"),projectKey:s,sessionId:d(t.sessionId,64),turn:Number.isFinite(t.turn)?t.turn:null,title:d(t.title||t.what,200),what:d(t.what,2e3),why:d(t.why,4e3),alternatives:a,affects:Array.isArray(t.affects)?t.affects.slice(0,50).map(e=>d(e,400)):[],anchor:t.anchor&&"object"==typeof t.anchor?t.anchor:resolveAnchor(e),edges:{causedBy:Array.isArray(t.causedBy)?t.causedBy.map(e=>d(e,64)):[],supersedes:t.supersedes?d(t.supersedes,64):null,branchOf:t.branchOf?d(t.branchOf,64):null},status:t.status||(c?"active":"draft"),origin:n.origin||t.origin||"user",confidence:"number"==typeof t.confidence?t.confidence:c?1:.4,tags:Array.isArray(t.tags)?t.tags.slice(0,12).map(e=>d(e,40)):[],memoryRefs:Array.isArray(t.memoryRefs)?t.memoryRefs.map(e=>d(e,120)):[],createdAt:Date.now(),author:d(t.author,200),synced:!1};return i.nodes.push(p),i.nodes.length>o&&i.nodes.splice(0,i.nodes.length-o),saveGraph(i),p}export function listDecisions(e){return loadGraph(projectKey(e)).nodes.slice().sort((e,t)=>(t.createdAt||0)-(e.createdAt||0))}export function getDecision(e,t){return loadGraph(projectKey(e)).nodes.find(e=>e.id===t)||null}export function unsyncedDecisions(e){return loadGraph(projectKey(e)).nodes.filter(e=>!e.synced)}export function markSynced(e,t){const r=new Set(t||[]);if(!r.size)return;const n=loadGraph(projectKey(e));let s=!1;for(const e of n.nodes)r.has(e.id)&&!e.synced&&(e.synced=!0,s=!0);s&&saveGraph(n)}export function getLastSync(e){return loadGraph(projectKey(e)).lastSyncAt||0}export function setLastSync(e,t){const r=loadGraph(projectKey(e));r.lastSyncAt=t,saveGraph(r)}export function mergeRemote(e,t){if(!Array.isArray(t)||!t.length)return 0;const r=projectKey(e),n=loadGraph(r),s=new Set(n.nodes.map(e=>e.id));let i=0;for(const e of t){if(!e||!e.decisionId||s.has(e.decisionId))continue;let t=null;try{t=e.payload?JSON.parse(e.payload):null}catch{t=null}t&&t.id===e.decisionId||(t={id:e.decisionId,projectKey:r,sessionId:"",turn:null,title:d(e.title,200),what:d(e.title,200),why:"",alternatives:[],affects:[],anchor:{},edges:{causedBy:[],supersedes:null,branchOf:null},status:e.status||"active",origin:"agent",confidence:1,tags:[],memoryRefs:[],createdAt:e.clientCreatedAt||Date.now(),author:""}),t.projectKey=r,t.synced=!0,n.nodes.push(t),s.add(e.decisionId),i++}return i&&(n.nodes.sort((e,t)=>(e.createdAt||0)-(t.createdAt||0)),n.nodes.length>o&&n.nodes.splice(0,n.nodes.length-o),saveGraph(n)),i}export const DECISION_TOOL_NAME="record_decision";export function makeDecisionTool({cwd:e,sessionId:t,turn:r,emit:n}){return{readOnly:!0,spec:{type:"function",function:{name:"record_decision",description:"当你在多个可行方案之间做出取舍(技术选型、架构/接口方案、安全策略等)时,记录这次「决策」以便日后追溯「为什么这样做」。仅在存在真实岔路(考虑过其它方案)时调用;读文件、修小问题、单纯信息汇总不要记。务必给出至少一个 alternatives(考虑过但未选的方案及否决理由)——这是决策区别于普通动作的关键;缺少 alternatives 会被存为「待确认草稿」而非正式决策。",parameters:{type:"object",properties:{what:{type:"string",description:"选择了什么(结论,一句话可复述)"},why:{type:"string",description:"为什么这样选(前提 / 约束 / 权衡)"},alternatives:{type:"array",description:"考虑过但未选的方案及否决理由",items:{type:"object",properties:{option:{type:"string",description:"被否决的方案"},rejectedBecause:{type:"string",description:"为什么没选它"}},required:["option"]}},affects:{type:"array",items:{type:"string"},description:"受影响的文件 / 模块(相对路径)"},tags:{type:"array",items:{type:"string"},description:"标签,如 架构 / 性能 / 安全"}},required:["what","why"]}}},run:async s=>{const o=String(s?.what||"").trim();if(!o)return"record_decision 需要非空 what";const i=recordDecision(e,{what:o,title:o,why:s?.why,alternatives:s?.alternatives,affects:s?.affects,tags:s?.tags,sessionId:t,turn:r},{origin:"agent"});try{n?.(i)}catch{}const a="draft"===i.status?"(未给 alternatives,已存为待确认草稿)":"";return`已记录决策:${i.title}${a}`}}}export function parseExtraction(e){if(!e)return null;let t=String(e).trim();const r=t.match(/```(?:json)?\s*([\s\S]*?)```/i);r&&(t=r[1].trim());const n=t.indexOf("{"),s=t.lastIndexOf("}");if(n<0||s<=n)return null;let o;try{o=JSON.parse(t.slice(n,s+1))}catch{return null}if(!o||!0!==o.isDecision)return null;const i=d(o.what,2e3).trim(),a=l(o.alternatives);if(!i||!a.length)return null;const c="number"==typeof o.confidence?Math.max(0,Math.min(1,o.confidence)):.5;return{what:i,why:d(o.why,4e3).trim(),alternatives:a,tags:Array.isArray(o.tags)?o.tags.slice(0,12).map(e=>d(e,40)):[],confidence:c}}export async function extractDecisionFromTurn(e,{userText:t,assistantText:r,toolNames:n}={}){if(!e||!e.baseUrl||!e.apiKey)return null;try{const{streamChat:s}=await import("./api.js"),o=[t?"用户指令:\n"+String(t).slice(0,2e3):"",n&&n.length?"本回合用到的工具:"+n.join(", "):"",r?"助手结论:\n"+String(r).slice(0,4e3):""].filter(Boolean).join("\n\n");if(!o)return null;const i=await s({baseUrl:e.baseUrl,apiKey:e.apiKey,model:e.decisionExtractModel||e.model,client:e.client,aux:!0,messages:[{role:"system",content:'你是「决策抽取器」。判断这一回合里是否发生了一次真实的技术「决策」——在多个可行方案之间做了取舍(存在被认真考虑却否决的其它方案)。\n\n算决策(举例):技术选型(用 A 库而非 B 库)、架构/接口方案取舍、数据/安全策略选择、算法或数据结构的权衡。\n不算决策(举例):读文件/检索、修拼写或明显 bug、跑测试/构建、单纯总结或解释、按用户明确指令直接执行且并无其它可选方案。\n\n判据(须全部满足才算):①确实在多个方案间做了选择;②能说清"为什么选它、为什么不选别的";③对后续有影响、值得日后追溯。\n\n严格只输出一个 JSON 对象,不要任何多余文字/解释/markdown 围栏:\n{"isDecision":true|false,"what":"选择了什么","why":"为什么","alternatives":[{"option":"被否决方案","rejectedBecause":"否决理由"}],"tags":["架构"],"confidence":0.0到1.0}\n不是决策时只输出 {"isDecision":false}。confidence 表示你对"这确实是一次值得记录的决策"的把握(越不确定越低)。\n\n示例(正):选用 pgvector 下推而非应用层余弦 → {"isDecision":true,"what":"向量检索用 pgvector 下推","why":"大库应用层每次全量拉取会超时,HNSW O(logN)","alternatives":[{"option":"应用层余弦","rejectedBecause":"首查装载慢、内存随库线性增长"}],"tags":["架构","性能"],"confidence":0.9}\n示例(负):只是读了几个文件并总结 → {"isDecision":false}'},{role:"user",content:o}],maxTokens:600},{});return parseExtraction(i&&i.content)}catch{return null}}
1
+ import e from"node:fs";import t from"node:path";import r from"node:crypto";import{execFileSync as n}from"node:child_process";import{CONFIG_DIR as s}from"./config.js";export const DECISIONS_DIR=t.join(s,"decisions");const o=2e3;function i(e,t){return n("git",t,{cwd:e,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim()}export function projectKey(e){let n="";try{n=i(e,["config","--get","remote.origin.url"])}catch{}if(!n)try{n=i(e,["rev-parse","--show-toplevel"])}catch{}return n||(n=t.resolve(e||".")),r.createHash("sha1").update(n).digest("hex").slice(0,16)}export function resolveAnchor(e){try{const t=i(e,["rev-parse","HEAD"]);if(!t)return{};let r=!1;try{r=i(e,["status","--porcelain"]).length>0}catch{}return{git:t,dirty:r}}catch{return{}}}function a(e){return t.join(DECISIONS_DIR,e,"graph.json")}function c(e){return{version:1,projectKey:e,nodes:[]}}export function loadGraph(t){try{const r=JSON.parse(e.readFileSync(a(t),"utf8"));return r&&Array.isArray(r.nodes)?(r.version=r.version||1,r.projectKey=t,r):c(t)}catch{return c(t)}}export function saveGraph(r){if(r&&r.projectKey&&Array.isArray(r.nodes))try{e.mkdirSync(t.join(DECISIONS_DIR,r.projectKey),{recursive:!0}),e.writeFileSync(a(r.projectKey),JSON.stringify(r),{mode:384})}catch(e){process.stderr.write(`(决策图保存失败,不影响主流程:${e.message})\n`)}}function d(e,t){return String(null==e?"":e).slice(0,t)}function l(e){return Array.isArray(e)?e.filter(e=>e&&e.option).slice(0,20).map(e=>({option:d(e.option,500),rejectedBecause:d(e.rejectedBecause,1e3)})):[]}export function recordDecision(e,t={},n={}){const s=projectKey(e),i=loadGraph(s),a=l(t.alternatives),c=a.length>0,p={id:"dec_"+Date.now().toString(36)+r.randomBytes(4).toString("hex"),projectKey:s,sessionId:d(t.sessionId,64),turn:Number.isFinite(t.turn)?t.turn:null,title:d(t.title||t.what,200),what:d(t.what,2e3),why:d(t.why,4e3),alternatives:a,affects:Array.isArray(t.affects)?t.affects.slice(0,50).map(e=>d(e,400)):[],anchor:t.anchor&&"object"==typeof t.anchor?t.anchor:resolveAnchor(e),edges:{causedBy:Array.isArray(t.causedBy)?t.causedBy.map(e=>d(e,64)):[],supersedes:t.supersedes?d(t.supersedes,64):null,branchOf:t.branchOf?d(t.branchOf,64):null},status:t.status||(c?"active":"draft"),origin:n.origin||t.origin||"user",confidence:"number"==typeof t.confidence?t.confidence:c?1:.4,tags:Array.isArray(t.tags)?t.tags.slice(0,12).map(e=>d(e,40)):[],memoryRefs:Array.isArray(t.memoryRefs)?t.memoryRefs.map(e=>d(e,120)):[],createdAt:Date.now(),author:d(t.author,200),synced:!1};return i.nodes.push(p),i.nodes.length>o&&i.nodes.splice(0,i.nodes.length-o),saveGraph(i),p}export function listDecisions(e){return loadGraph(projectKey(e)).nodes.slice().sort((e,t)=>(t.createdAt||0)-(e.createdAt||0))}export function getDecision(e,t){return loadGraph(projectKey(e)).nodes.find(e=>e.id===t)||null}export function unsyncedDecisions(e){return loadGraph(projectKey(e)).nodes.filter(e=>!e.synced)}export function markSynced(e,t){const r=new Set(t||[]);if(!r.size)return;const n=loadGraph(projectKey(e));let s=!1;for(const e of n.nodes)r.has(e.id)&&!e.synced&&(e.synced=!0,s=!0);s&&saveGraph(n)}export function getLastSync(e){return loadGraph(projectKey(e)).lastSyncAt||0}export function setLastSync(e,t){const r=loadGraph(projectKey(e));r.lastSyncAt=t,saveGraph(r)}export function mergeRemote(e,t){if(!Array.isArray(t)||!t.length)return 0;const r=projectKey(e),n=loadGraph(r),s=new Set(n.nodes.map(e=>e.id));let i=0;for(const e of t){if(!e||!e.decisionId||s.has(e.decisionId))continue;let t=null;try{t=e.payload?JSON.parse(e.payload):null}catch{t=null}t&&t.id===e.decisionId||(t={id:e.decisionId,projectKey:r,sessionId:"",turn:null,title:d(e.title,200),what:d(e.title,200),why:"",alternatives:[],affects:[],anchor:{},edges:{causedBy:[],supersedes:null,branchOf:null},status:e.status||"active",origin:"agent",confidence:1,tags:[],memoryRefs:[],createdAt:e.clientCreatedAt||Date.now(),author:""}),t.projectKey=r,t.synced=!0,n.nodes.push(t),s.add(e.decisionId),i++}return i&&(n.nodes.sort((e,t)=>(e.createdAt||0)-(t.createdAt||0)),n.nodes.length>o&&n.nodes.splice(0,n.nodes.length-o),saveGraph(n)),i}export const DECISION_TOOL_NAME="record_decision";export function makeDecisionTool({cwd:e,sessionId:t,turn:r,emit:n}){return{readOnly:!0,spec:{type:"function",function:{name:"record_decision",description:"当你在多个可行方案之间做出取舍(技术选型、架构/接口方案、安全策略等)时,记录这次「决策」以便日后追溯「为什么这样做」。仅在存在真实岔路(考虑过其它方案)时调用;读文件、修小问题、单纯信息汇总不要记。务必给出至少一个alternatives(考虑过但未选的方案及否决理由)——这是决策区别于普通动作的关键;缺少alternatives会被存为「待确认草稿」而非正式决策。",parameters:{type:"object",properties:{what:{type:"string",description:"选择了什么(结论,一句话可复述)"},why:{type:"string",description:"为什么这样选(前提 / 约束 / 权衡)"},alternatives:{type:"array",description:"考虑过但未选的方案及否决理由",items:{type:"object",properties:{option:{type:"string",description:"被否决的方案"},rejectedBecause:{type:"string",description:"为什么没选它"}},required:["option"]}},affects:{type:"array",items:{type:"string"},description:"受影响的文件 / 模块(相对路径)"},tags:{type:"array",items:{type:"string"},description:"标签,如 架构 / 性能 / 安全"}},required:["what","why"]}}},run:async s=>{const o=String(s?.what||"").trim();if(!o)return"record_decision需要非空what";const i=recordDecision(e,{what:o,title:o,why:s?.why,alternatives:s?.alternatives,affects:s?.affects,tags:s?.tags,sessionId:t,turn:r},{origin:"agent"});try{n?.(i)}catch{}const a="draft"===i.status?"(未给alternatives,已存为待确认草稿)":"";return`已记录决策:${i.title}${a}`}}}export function parseExtraction(e){if(!e)return null;let t=String(e).trim();const r=t.match(/```(?:json)?\s*([\s\S]*?)```/i);r&&(t=r[1].trim());const n=t.indexOf("{"),s=t.lastIndexOf("}");if(n<0||s<=n)return null;let o;try{o=JSON.parse(t.slice(n,s+1))}catch{return null}if(!o||!0!==o.isDecision)return null;const i=d(o.what,2e3).trim(),a=l(o.alternatives);if(!i||!a.length)return null;const c="number"==typeof o.confidence?Math.max(0,Math.min(1,o.confidence)):.5;return{what:i,why:d(o.why,4e3).trim(),alternatives:a,tags:Array.isArray(o.tags)?o.tags.slice(0,12).map(e=>d(e,40)):[],confidence:c}}export async function extractDecisionFromTurn(e,{userText:t,assistantText:r,toolNames:n}={}){if(!e||!e.baseUrl||!e.apiKey)return null;try{const{streamChat:s}=await import("./api.js"),o=[t?"用户指令:\n"+String(t).slice(0,2e3):"",n&&n.length?"本回合用到的工具:"+n.join(", "):"",r?"助手结论:\n"+String(r).slice(0,4e3):""].filter(Boolean).join("\n\n");if(!o)return null;const i=await s({baseUrl:e.baseUrl,apiKey:e.apiKey,model:e.decisionExtractModel||e.model,client:e.client,aux:!0,messages:[{role:"system",content:'你是「决策抽取器」。判断这一回合里是否发生了一次真实的技术「决策」——在多个可行方案之间做了取舍(存在被认真考虑却否决的其它方案)。\n\n算决策(举例):技术选型(用A库而非B库)、架构/接口方案取舍、数据/安全策略选择、算法或数据结构的权衡。\n不算决策(举例):读文件/检索、修拼写或明显bug、跑测试/构建、单纯总结或解释、按用户明确指令直接执行且并无其它可选方案。\n\n判据(须全部满足才算):①确实在多个方案间做了选择;②能说清"为什么选它、为什么不选别的";③对后续有影响、值得日后追溯。\n\n严格只输出一个JSON对象,不要任何多余文字/解释/markdown围栏:\n{"isDecision":true|false,"what":"选择了什么","why":"为什么","alternatives":[{"option":"被否决方案","rejectedBecause":"否决理由"}],"tags":["架构"],"confidence":0.0到1.0}\n不是决策时只输出 {"isDecision":false}。confidence表示你对"这确实是一次值得记录的决策"的把握(越不确定越低)。\n\n示例(正):选用pgvector下推而非应用层余弦 → {"isDecision":true,"what":"向量检索用pgvector下推","why":"大库应用层每次全量拉取会超时,HNSW O(logN)","alternatives":[{"option":"应用层余弦","rejectedBecause":"首查装载慢、内存随库线性增长"}],"tags":["架构","性能"],"confidence":0.9}\n示例(负):只是读了几个文件并总结 → {"isDecision":false}'},{role:"user",content:o}],maxTokens:600},{});return parseExtraction(i&&i.content)}catch{return null}}
@@ -0,0 +1 @@
1
+ import e from"node:fs";import t from"node:path";const n=new Set(["abc","argparse","array","ast","asyncio","base64","bisect","builtins","bz2","calendar","cmath","cmd","code","codecs","collections","colorsys","configparser","contextlib","copy","csv","ctypes","dataclasses","datetime","decimal","difflib","dis","email","enum","errno","filecmp","fileinput","fnmatch","fractions","ftplib","functools","gc","getpass","gettext","glob","graphlib","gzip","hashlib","heapq","hmac","html","http","imaplib","inspect","io","ipaddress","itertools","json","keyword","linecache","locale","logging","lzma","mailbox","math","mimetypes","multiprocessing","netrc","numbers","operator","os","pathlib","pickle","pipes","pkgutil","platform","plistlib","poplib","pprint","profile","pty","queue","random","re","sched","secrets","select","selectors","shelve","shlex","shutil","signal","site","smtplib","socket","socketserver","sqlite3","ssl","stat","statistics","string","struct","subprocess","symtable","sys","tarfile","telnetlib","tempfile","textwrap","threading","time","timeit","token","tokenize","tomllib","trace","traceback","tracemalloc","types","typing","unicodedata","unittest","urllib","uuid","venv","warnings","wave","weakref","webbrowser","xml","zipfile","zlib"]),s={enum34:"Python 3.4 起enum已进标准库",futures:"Python 3.2 起concurrent.futures已进标准库",functools32:"现代Python的functools已包含相关能力",singledispatch:"Python 3.4 起functools.singledispatch已进标准库",subprocess32:"现代Python的subprocess已是维护版本",pathlib2:"Python 3.4 起pathlib已进标准库","typing-extensions":null},i=/^\s*(\*|latest|x|x\.x|x\.x\.x|)\s*$/i,o=e=>String(e||"").trim().toLowerCase().replace(/_/g,"-");function r(e,t,n,s,i,o){return{level:e,kind:t,file:n,line:s,what:i,fix:o}}export function parseRequirements(e,t="requirements.txt"){const n=[];return String(e||"").split(/\r?\n/).forEach((e,s)=>{const i=e.replace(/\s+#.*$/,"").trim();if(!i||i.startsWith("#")||i.startsWith("-"))return;const o=/^([A-Za-z0-9][A-Za-z0-9._-]*)(?:\[[^\]]*\])?\s*([^;]*)(?:;(.*))?$/.exec(i);o&&n.push({ecosystem:"python",name:o[1],spec:(o[2]||"").trim(),marker:(o[3]||"").trim(),file:t,line:s+1})}),n}export function parsePackageJson(e,t="package.json"){let n;try{n=JSON.parse(String(e||""))}catch{return[]}const s=String(e).split(/\r?\n/),i=[];for(const e of["dependencies","devDependencies","peerDependencies","optionalDependencies"]){const o=n?.[e];if(o&&"object"==typeof o)for(const[n,r]of Object.entries(o)){const o=s.findIndex(e=>e.includes(`"${n}"`)&&e.includes(":"));i.push({ecosystem:"npm",name:n,spec:String(r??""),block:e,file:t,line:o>=0?o+1:0})}}return i}export function parsePom(e,t="pom.xml"){const n=String(e||""),s=[],i=/<dependency>([\s\S]*?)<\/dependency>/g;let o;for(;null!==(o=i.exec(n));){const e=o[1],i=/<groupId>\s*([^<]+?)\s*<\/groupId>/.exec(e),r=/<artifactId>\s*([^<]+?)\s*<\/artifactId>/.exec(e),c=/<version>\s*([^<]+?)\s*<\/version>/.exec(e),l=/<scope>\s*([^<]+?)\s*<\/scope>/.exec(e);r&&s.push({ecosystem:"maven",name:`${i?i[1]:""}:${r[1]}`,spec:c?c[1].trim():"",scope:l?l[1].trim():"",file:t,line:n.slice(0,o.index).split(/\r?\n/).length})}return s}function c(e){return"npm"===e.ecosystem?!i.test(e.spec):"maven"===e.ecosystem||/[<>=~!^]/.test(e.spec)}function l(e){if("python"===e.ecosystem){const t=/(?:^|,)\s*==\s*([^,;\s]+)/.exec(e.spec);return t?t[1]:null}if("npm"===e.ecosystem)return/^\d+\.\d+/.test(e.spec.trim())?e.spec.trim():null;if("maven"===e.ecosystem){const t=e.spec.trim();return t&&!t.startsWith("${")?t:null}return null}export function auditEntries(e){const t=Array.isArray(e)?e:[],i=[];for(const e of t){if("python"===e.ecosystem){const t=o(e.name),c=t.replace(/-/g,"_");Object.prototype.hasOwnProperty.call(s,t)&&s[t]?i.push(r("high","abandoned-backport",e.file,e.line,`${e.name} 是已被标准库吸收的老backport(${s[t]})`,`删掉它。若确实要支持很老的运行时,用环境标记限定,如 ${e.name}; python_version < "3.4"`)):(n.has(c)||n.has(t))&&i.push(r("high","stdlib-shadowing",e.file,e.line,`${e.name} 与Python标准库同名。装上它会**遮蔽**标准库模块——import到的是PyPI上那个同名包,而它多半是很久以前某人占的坑`,`从清单里删掉 ${e.name},直接import标准库模块。(这类故障的现场很迷惑:代码没改、Python没换,只是某次安装之后某个标准库突然不对了)`))}c(e)||i.push(r("medium","unpinned",e.file,e.line,`${e.name} 没有可用的版本约束(当前写法:${e.spec||"(空)"}),下次安装可能解析到完全不同的代码`,"先看清当前环境里装的是哪一版,再钉一个评审过的版本。"))}const p=new Map;for(const e of t){const t=`${e.ecosystem}|${o(e.name)}`;p.has(t)||p.set(t,[]),p.get(t).push(e)}for(const[,e]of p){if(e.length<2)continue;if("npm"===e[0].ecosystem&&new Set(e.map(e=>e.block)).size===e.length)continue;const t=[...new Set(e.map(l).filter(Boolean))],n=e.map(e=>`${e.file}:${e.line}`).join("、");t.length>1?i.push(r("high","conflicting-constraints",e[0].file,e[0].line,`${e[0].name} 被钉成了不相容的版本:${t.join(" / ")}(出现在 ${n})`,"选定一个版本,只保留一条约束。留着两条的结果取决于解析顺序——今天能跑不代表明天能跑。")):i.push(r("medium","duplicate-constraint",e[0].file,e[0].line,`${e[0].name} 重复出现在 ${n}`,"合并成一条。两条约束最容易的失败方式是「只改了其中一条」,而这不会有任何报错。"))}const a={high:0,medium:1,low:2};return i.sort((e,t)=>a[e.level]-a[t.level]||String(e.file).localeCompare(String(t.file))||e.line-t.line),i.slice(0,40)}export const MANIFESTS=["requirements.txt","package.json","pom.xml"];export function auditProject(n,s={}){const i=s.readFile||(t=>{try{return e.readFileSync(t,"utf8")}catch{return null}}),o=[],r=[];for(const e of s.files||MANIFESTS){const s=i(t.join(n||".",e));null!=s&&(r.push(e),e.endsWith("package.json")?o.push(...parsePackageJson(s,e)):e.endsWith("pom.xml")?o.push(...parsePom(s,e)):o.push(...parseRequirements(s,e)))}return{scanned:r,entries:o,findings:auditEntries(o)}}export function formatReport(e){const{scanned:t,entries:n,findings:s}=e;if(!t.length)return"没有找到可体检的依赖清单(支持requirements.txt / package.json / pom.xml)。";const i=`已检查 ${t.join("、")},共 ${n.length} 条依赖。`;if(!s.length)return`${i}\n未发现清单层面的问题。\n(注意:本体检只看「这份清单自己有没有病」,不查上游有没有新版本、也不查已知漏洞——前者用依赖发布雷达,后者用安全扫描。)`;const o={high:"严重",medium:"中等",low:"提示"},r=[i,`发现 ${s.length} 处问题:`];for(const e of s)r.push(""),r.push(`[${o[e.level]}] ${e.file}:${e.line||"?"} · ${e.kind}`),r.push(` 问题:${e.what}`),r.push(` 处置:${e.fix}`);return r.join("\n")}export function makeDepAuditTool({cwd:e}){return[{readOnly:!0,spec:{type:"function",function:{name:"dependency_audit",description:"静态体检依赖清单本身有没有病:标准库被同名三方包遮蔽、已被标准库吸收的老backport、未钉版本、同一个包被钉成两个不同版本、重复条目。支持requirements.txt / package.json / pom.xml。\n**纯离线只读**,不联网、不改文件。\n注意它与另外两件事的分工:查「上游有没有新版本」用依赖发布雷达;查「怎么安全升级」用dependency-upgrade技能;本工具只查这份清单自己的毛病。",parameters:{type:"object",properties:{path:{type:"string",description:"(可选)项目目录,默认当前工作目录"}},required:[]}}},run:async n=>formatReport(auditProject(n?.path?t.resolve(e||".",n.path):e))}]}
package/src/device.js CHANGED
@@ -1 +1 @@
1
- import{spawn as e}from"node:child_process";import{c as t}from"./colors.js";export function openBrowser(t){if(!t||process.env.HUICAI_NO_BROWSER)return;const r=process.platform,o="darwin"===r?"open":"win32"===r?"cmd":"xdg-open",i="win32"===r?["/c","start","",t]:[t];try{const t=e(o,i,{stdio:"ignore",detached:!0});t.on("error",()=>{}),t.unref()}catch{}}async function r(e,t,r){const o=await fetch(`${e}${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r||{})}),i=await o.text();let n;try{n=JSON.parse(i)}catch{throw new Error(`服务返回异常(${o.status}):${i.slice(0,200)}`)}if(!o.ok)throw new Error(n?.message||`HTTP ${o.status}`);if(n&&"object"==typeof n&&"data"in n&&"code"in n){if(0!==n.code&&200!==n.code)throw new Error(n.message||"请求失败");return n.data}return n}const o=e=>new Promise(t=>setTimeout(t,e));export async function startDeviceAuth(e,t){const o=await r(e,"/v1/device/code",{client_name:t||`huicai-cli (${process.platform})`}),{deviceCode:i,userCode:n,verificationUri:s,verificationUriComplete:c}=o,a=Math.max(2,o.interval||5),d=o.expiresIn||600,p=s||e+"/device",u=c||p+(p.includes("?")?"&":"?")+"code="+encodeURIComponent(n);return{deviceCode:i,userCode:n,verificationUri:p,verificationUriComplete:u,interval:a,expiresIn:d}}export function pollDeviceAuth(e,t){return r(e,"/v1/device/token",{device_code:t})}export async function deviceLogin(e){const{deviceCode:r,userCode:i,verificationUri:n,verificationUriComplete:s,interval:c,expiresIn:a}=await startDeviceAuth(e);process.stdout.write("\n"+t.bold("请在浏览器中完成授权:")+"\n"),process.stdout.write(" 1) 打开 "+t.cyan(n)+"\n"),process.stdout.write(" 2) 确认授权码 "+t.bold(t.yellow(i))+"\n"),process.stdout.write(t.gray(" (已尝试自动打开浏览器;未弹出请手动访问上面的地址)")+"\n"),openBrowser(s),process.stdout.write(t.gray(`\n等待授权中…(有效期约 ${Math.round(a/60)} 分钟,Ctrl+C 取消)`)+"\n");const d=Date.now()+1e3*a;for(;Date.now()<d;){let i;await o(1e3*c);try{i=await pollDeviceAuth(e,r)}catch(e){process.stdout.write(t.gray(` 轮询出错(将重试):${e.message}`)+"\n");continue}if("approved"===i.status&&i.apiKey)return process.stdout.write(t.green("\n✓ 授权成功")+"\n"),i.apiKey;if("denied"===i.status)throw new Error("用户在浏览器中拒绝了授权");if("expired"===i.status)throw new Error("授权码已过期,请重新登录");process.stdout.write(t.gray(" · 仍在等待浏览器确认…")+"\n")}throw new Error("授权超时,请重新运行 huicai login")}
1
+ import{spawn as e}from"node:child_process";import{c as t}from"./colors.js";export function openBrowser(t){if(!t||process.env.HUICAI_NO_BROWSER)return;const r=process.platform,o="darwin"===r?"open":"win32"===r?"cmd":"xdg-open",i="win32"===r?["/c","start","",t]:[t];try{const t=e(o,i,{stdio:"ignore",detached:!0});t.on("error",()=>{}),t.unref()}catch{}}async function r(e,t,r){const o=await fetch(`${e}${t}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r||{})}),i=await o.text();let n;try{n=JSON.parse(i)}catch{throw new Error(`服务返回异常(${o.status}):${i.slice(0,200)}`)}if(!o.ok)throw new Error(n?.message||`HTTP ${o.status}`);if(n&&"object"==typeof n&&"data"in n&&"code"in n){if(0!==n.code&&200!==n.code)throw new Error(n.message||"请求失败");return n.data}return n}const o=e=>new Promise(t=>setTimeout(t,e));export async function startDeviceAuth(e,t){const o=await r(e,"/v1/device/code",{client_name:t||`huicai-cli (${process.platform})`}),{deviceCode:i,userCode:n,verificationUri:s,verificationUriComplete:c}=o,a=Math.max(2,o.interval||5),d=o.expiresIn||600,p=s||e+"/device",u=c||p+(p.includes("?")?"&":"?")+"code="+encodeURIComponent(n);return{deviceCode:i,userCode:n,verificationUri:p,verificationUriComplete:u,interval:a,expiresIn:d}}export function pollDeviceAuth(e,t){return r(e,"/v1/device/token",{device_code:t})}export async function deviceLogin(e){const{deviceCode:r,userCode:i,verificationUri:n,verificationUriComplete:s,interval:c,expiresIn:a}=await startDeviceAuth(e);process.stdout.write("\n"+t.bold("请在浏览器中完成授权:")+"\n"),process.stdout.write(" 1) 打开 "+t.cyan(n)+"\n"),process.stdout.write(" 2) 确认授权码 "+t.bold(t.yellow(i))+"\n"),process.stdout.write(t.gray(" (已尝试自动打开浏览器;未弹出请手动访问上面的地址)")+"\n"),openBrowser(s),process.stdout.write(t.gray(`\n等待授权中…(有效期约 ${Math.round(a/60)} 分钟,Ctrl+C取消)`)+"\n");const d=Date.now()+1e3*a;for(;Date.now()<d;){let i;await o(1e3*c);try{i=await pollDeviceAuth(e,r)}catch(e){process.stdout.write(t.gray(` 轮询出错(将重试):${e.message}`)+"\n");continue}if("approved"===i.status&&i.apiKey)return process.stdout.write(t.green("\n✓ 授权成功")+"\n"),i.apiKey;if("denied"===i.status)throw new Error("用户在浏览器中拒绝了授权");if("expired"===i.status)throw new Error("授权码已过期,请重新登录");process.stdout.write(t.gray(" · 仍在等待浏览器确认…")+"\n")}throw new Error("授权超时,请重新运行huicai login")}
@@ -0,0 +1 @@
1
+ export const FONT_STACK='"PingFang SC","Helvetica Neue","Hiragino Sans GB","Microsoft YaHei","Source Han Sans SC","Noto Sans CJK SC","WenQuanYi Micro Hei",-apple-system,"Segoe UI",Roboto,Arial,sans-serif';export const MONO_STACK='"SF Mono","JetBrains Mono","Cascadia Mono",Menlo,Consolas,"DejaVu Sans Mono",monospace';const e=new Set([..."iljtIf.,:;'|!`[](){}-·"]),t=new Set([..."mwMW@%—…"]);export function isFullWidth(e){return e>=4352&&e<=4447||e>=11904&&e<=12350||e>=12353&&e<=13311||e>=13312&&e<=19903||e>=19968&&e<=40959||e>=40960&&e<=42191||e>=44032&&e<=55203||e>=63744&&e<=64255||e>=65072&&e<=65103||e>=65280&&e<=65376||e>=65504&&e<=65510||e>=131072&&e<=262141}function r(r){const n=r.codePointAt(0);return 32===n?.28:9===n?1.12:isFullWidth(n)?1:n>65535?1.15:e.has(r)?.3:t.has(r)?.88:r>="0"&&r<="9"?.56:r>="A"&&r<="Z"?.66:r>="a"&&r<="z"||n<11904?.53:1}export function measureText(e,t,n={}){const s=String(e??"");if(!s)return 0;if(n.mono){let e=0;for(const t of s)e+=isFullWidth(t.codePointAt(0))?1:.6;return e*t}let i=0;for(const e of s)i+=r(e);return i*t*(n.bold?1.035:1)}const n=new Set([...",。、;:?!)」』】》”’%,.;:?!)]}>"]),s=new Set([..."(「『【《“‘([{<"]);export function wrapText(e,t,r,n={}){const s=String(e??"").replace(/\r/g,"");if(!s)return[];const o=n.maxLines||99,a=[];for(const e of s.split("\n")){if(!e.trim()&&a.length){a.push("");continue}let s=i(e,t,r,n);if(s.length>=2&&measureText(s[s.length-1],t,n)<.25*r){for(let o=Math.max(measureText(e,t,n)/s.length,.5*r);o<r;o+=.03*r){const r=i(e,t,o,n);if(r.length===s.length){s=r;break}}}a.push(...s)}if(a.length<=o)return a;const l=a.slice(0,o),h=l[o-1];let f=h;for(;f&&measureText(f+"…",t,n)>r;)f=f.slice(0,-1);return l[o-1]=(f||h.slice(0,2))+"…",l}function i(e,t,r,i){const o=[];let a="",l=0;for(const h of function(e){const t=[];let r="";for(const n of e){const e=n.codePointAt(0);isFullWidth(e)||e>65535?(r&&(t.push(r),r=""),t.push(n)):" "===n?(r+=n,t.push(r),r=""):"/\\-_,;".includes(n)&&r?(t.push(r+n),r=""):r+=n}r&&t.push(r);return t}(e)){const e=measureText(h,t,i);if(l>0&&l+e>r){let e="";s.has(a.slice(-1))&&(e=a.slice(-1),a=a.slice(0,-1)),o.push(a),a=e+h,l=measureText(a,t,i)}else if(0===l&&e>r){let e="";for(const n of h)measureText(e+n,t,i)>r&&e&&(o.push(e),e=""),e+=n;a=e,l=measureText(e,t,i)}else a+=h,l+=e;o.length&&a&&n.has(a[0])&&(o[o.length-1]+=a[0],a=a.slice(1),l=measureText(a,t,i))}return a&&o.push(a),o}function o(e){const t=String(e).replace("#",""),r=3===t.length?t.split("").map(e=>e+e).join(""):t;return[parseInt(r.slice(0,2),16),parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16)]}const a=(e,t,r)=>"#"+[e,t,r].map(e=>(e=>Math.max(0,Math.min(255,Math.round(e))))(e).toString(16).padStart(2,"0")).join("");export function mix(e,t,r){const[n,s,i]=o(e),[l,h,f]=o(t);return a(n+(l-n)*r,s+(h-s)*r,i+(f-i)*r)}export const ACCENT_NAMES=["blue","indigo","violet","cyan","teal","green","amber","orange","red","pink","slate"];const l={blue:"#2563eb",indigo:"#4f46e5",violet:"#7c3aed",cyan:"#0891b2",teal:"#0d9488",green:"#16a34a",amber:"#d97706",orange:"#ea580c",red:"#dc2626",pink:"#db2777",slate:"#475569"},h={light:{bg:"#ffffff",panel:"#f8fafc",panel2:"#f1f5f9",ink:"#0f172a",ink2:"#475569",ink3:"#8a97ab",border:"#e2e8f0",border2:"#cbd5e1",shadow:!0,satur:1},dark:{bg:"#0b1220",panel:"#131e33",panel2:"#1a2740",ink:"#e8eefc",ink2:"#a9b8d4",ink3:"#6f819f",border:"#26324c",border2:"#3a4867",shadow:!0,satur:1,lighten:.34},print:{bg:"#ffffff",panel:"#f7f7f8",panel2:"#eeeff1",ink:"#111111",ink2:"#3f3f46",ink3:"#71717a",border:"#d4d4d8",border2:"#a1a1aa",shadow:!1,satur:.55},patent:{bg:"#ffffff",panel:"#ffffff",panel2:"#ffffff",ink:"#000000",ink2:"#000000",ink3:"#000000",border:"#000000",border2:"#000000",shadow:!1,satur:1,mono:!0,lw:1.7,rScale:0}};export function resolveTheme(e){const t=h[e]?e:"light",r={...h[t],name:t};r.lw=r.lw||1,r.rScale=r.rScale??1;const n=new Map;if(r.mono){const e={base:"#000000",deep:"#000000",soft:"#ffffff",soft2:"#ffffff",line:"#000000",on:"#ffffff"};return r.accent=()=>e,r}return r.accent=e=>{const t=l[e]||ACCENT_NAMES.includes(String(e))?e:null,s=t?l[t]:/^#[0-9a-fA-F]{3,6}$/.test(String(e))?String(e):l.blue;if(n.has(s))return n.get(s);let i=s;r.lighten&&(i=mix(i,"#ffffff",r.lighten)),r.satur<1&&(i=mix(i,"#5b6472",1-r.satur));const o={base:i,deep:mix(i,"#000000","dark"===r.name?.05:.22),soft:mix(i,r.panel,"dark"===r.name?.84:.9),soft2:mix(i,r.panel,"dark"===r.name?.72:.8),line:mix(i,r.bg,"dark"===r.name?.42:.52),on:"dark"===r.name?"#0b1220":"#ffffff"};return n.set(s,o),o},r}export function esc(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g,"")}const f=e=>Math.round(100*Number(e))/100||0;function c(e){return Object.entries(e).filter(([,e])=>null!=e&&!1!==e).map(([e,t])=>`${e}="${"number"==typeof t?f(t):esc(t)}"`).join(" ")}export class Canvas{constructor({theme:e="light",title:t="",desc:r=""}={}){this.t="string"==typeof e?resolveTheme(e):e,this.title=t,this.desc=r,this.defs=[],this.z={back:[],mid:[],front:[],over:[]},this._markers=new Map,this._w=0,this._h=0}extend(e,t){e>this._w&&(this._w=e),t>this._h&&(this._h=t)}add(e,t){return this.z[e].push(t),this}sw(e){return null==e?null:e*this.t.lw}r(e){return(e||0)*this.t.rScale}rect(e,t,r,n,s,i={}){this.extend(t+n,r+s);const o=this.r(i.r??0);return this.add(e,`<rect ${c({x:t,y:r,width:Math.max(0,n),height:Math.max(0,s),rx:o,ry:o,fill:i.fill||"none",stroke:i.stroke||null,"stroke-width":this.sw(i.sw??(i.stroke?1:null)),"stroke-dasharray":i.dash||null,filter:i.shadow&&this.t.shadow?"url(#dgShadow)":null,opacity:i.opacity??null,class:i.class||null})}/>`)}line(e,t,r,n,s,i={}){return this.extend(Math.max(t,n),Math.max(r,s)),this.add(e,`<line ${c({x1:t,y1:r,x2:n,y2:s,stroke:i.stroke||this.t.border,"stroke-width":this.sw(i.sw??1),"stroke-dasharray":i.dash||null,"stroke-linecap":i.cap||null,opacity:i.opacity??null,"marker-end":i.markerEnd||null,"marker-start":i.markerStart||null})}/>`)}path(e,t,r={}){return this.add(e,`<path ${c({d:t,fill:r.fill||"none",stroke:r.stroke||null,"stroke-width":this.sw(r.sw??(r.stroke?1:null)),"stroke-dasharray":r.dash||null,"stroke-linecap":r.cap||"round","stroke-linejoin":"round","marker-end":r.markerEnd||null,"marker-start":r.markerStart||null,opacity:r.opacity??null})}/>`)}circle(e,t,r,n,s={}){return this.extend(t+n,r+n),this.add(e,`<circle ${c({cx:t,cy:r,r:n,fill:s.fill||"none",stroke:s.stroke||null,"stroke-width":this.sw(s.sw??(s.stroke?1:null)),opacity:s.opacity??null})}/>`)}polygon(e,t,r={}){return this.add(e,`<polygon ${c({points:t.map(([e,t])=>`${f(e)},${f(t)}`).join(" "),fill:r.fill||"none",stroke:r.stroke||null,"stroke-width":this.sw(r.sw??(r.stroke?1:null)),"stroke-linejoin":"round","stroke-dasharray":r.dash||null})}/>`)}text(e,t,r,n,s={}){const i=String(n??"");if(!i)return this;const o=s.size||13;return this.extend(t+measureText(i,o,s)/2,r),this.add(e,`<text ${c({x:t,y:r,"font-size":o,fill:s.fill||this.t.ink,"font-weight":!0===s.bold?600:s.bold||null,"text-anchor":s.anchor&&"start"!==s.anchor?s.anchor:null,"font-family":s.mono?MONO_STACK:null,"letter-spacing":s.ls||null,opacity:s.opacity??null})}>${esc(i)}</text>`)}lines(e,t,r,n,s={}){const i=s.size||13,o=s.lh||Math.round(1.5*i);return n.forEach((n,i)=>this.text(e,t,r+i*o,n,s)),n.length*o}marker(e,t="arrow"){const r=t+":"+e;if(this._markers.has(r))return this._markers.get(r);const n="m"+this._markers.size+t[0];"arrow"===t?this.defs.push(`<marker id="${n}" viewBox="0 0 10 10" refX="9.2" refY="5" markerWidth="7" markerHeight="7" markerUnits="strokeWidth" orient="auto-start-reverse"><path d="M0.6,1.2 L9.4,5 L0.6,8.8 L2.6,5 Z" fill="${esc(e)}"/></marker>`):"open"===t?this.defs.push(`<marker id="${n}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" markerUnits="strokeWidth" orient="auto-start-reverse"><path d="M1,1.4 L9,5 L1,8.6" fill="none" stroke="${esc(e)}" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></marker>`):"dot"===t?this.defs.push(`<marker id="${n}" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5" markerUnits="strokeWidth" orient="auto"><circle cx="5" cy="5" r="3.4" fill="${esc(e)}"/></marker>`):this.defs.push(`<marker id="${n}" viewBox="0 0 10 10" refX="9.5" refY="5" markerWidth="9" markerHeight="9" markerUnits="strokeWidth" orient="auto-start-reverse"><path d="M9.5,5 L1,1 M9.5,5 L1,5 M9.5,5 L1,9" fill="none" stroke="${esc(e)}" stroke-width="1.2" stroke-linecap="round"/></marker>`);const s=`url(#${n})`;return this._markers.set(r,s),s}render(e,t,r={}){const n=Math.max(1,Math.ceil(e)),s=Math.max(1,Math.ceil(t)),i=this.t,o=[];i.shadow&&o.push(`<filter id="dgShadow" x="-12%" y="-12%" width="124%" height="130%"><feDropShadow dx="0" dy="1.2" stdDeviation="1.8" flood-color="${"dark"===i.name?"#000000":"#0f172a"}" flood-opacity="${"dark"===i.name?.5:.1}"/></filter>`),o.push(...this.defs);const a=[`<rect x="0" y="0" width="${n}" height="${s}" fill="${i.bg}"/>`,...this.z.back,...this.z.mid,...this.z.front,...this.z.over].join("\n");return`<?xml version="1.0" encoding="UTF-8"?>\n<svg xmlns="http://www.w3.org/2000/svg" width="${n}" height="${s}" viewBox="0 0 ${n} ${s}" font-family='${FONT_STACK}' shape-rendering="geometricPrecision" text-rendering="geometricPrecision">\n`+(this.title?`<title>${esc(this.title)}</title>\n`:"")+(this.desc?`<desc>${esc(this.desc)}</desc>\n`:"")+(r.meta?`<metadata>${esc(r.meta)}</metadata>\n`:"")+`<defs>\n${o.join("\n")}\n</defs>\n`+a+"\n</svg>\n"}}export function rrPath(e,t,r,n,s){const[i,o,a,l]=Array.isArray(s)?s:[s,s,s,s];return`M${f(e+i)},${f(t)} H${f(e+r-o)} A${f(o)},${f(o)} 0 0 1 ${f(e+r)},${f(t+o)} V${f(t+n-a)} A${f(a)},${f(a)} 0 0 1 ${f(e+r-a)},${f(t+n)} H${f(e+l)} A${f(l)},${f(l)} 0 0 1 ${f(e)},${f(t+n-l)} V${f(t+i)} A${f(i)},${f(i)} 0 0 1 ${f(e+i)},${f(t)} Z`}export function elbowPath(e,t=8){const r=e.filter((t,r)=>0===r||Math.abs(t[0]-e[r-1][0])>.5||Math.abs(t[1]-e[r-1][1])>.5);if(r.length<2)return"";let n=`M${f(r[0][0])},${f(r[0][1])}`;for(let e=1;e<r.length-1;e++){const[s,i]=r[e-1],[o,a]=r[e],[l,h]=r[e+1],c=Math.hypot(o-s,a-i),d=Math.hypot(l-o,h-a),u=Math.max(0,Math.min(t,c/2,d/2)),p=[o+(s-o)/c*u,a+(i-a)/c*u],m=[o+(l-o)/d*u,a+(h-a)/d*u];n+=` L${f(p[0])},${f(p[1])}`,u>.5&&(n+=` Q${f(o)},${f(a)} ${f(m[0])},${f(m[1])}`)}const s=r[r.length-1];return n+=` L${f(s[0])},${f(s[1])}`,n}export function edgeLabel(e,t,r,n,s={}){const i=s.size||11,o=measureText(n,i)+12,a=i+8,l=e.t.mono;return e.rect("over",t-o/2,r-a/2,o,a,{r:l?0:a/2,fill:s.bg||e.t.bg,stroke:l?null:s.stroke||e.t.border,sw:1}),e.text("over",t,r+.36*i,n,{size:i,fill:s.fill||e.t.ink2,anchor:"middle"}),{w:o,h:a}}export function chip(e,t,r,n,s,i=10){const o=measureText(n,i)+11,a=i+7;return e.rect("front",t,r,o,a,{r:3,fill:s.soft2,stroke:s.line,sw:.8}),e.text("front",t+o/2,r+a-(a-i)/2-1.5,n,{size:i,fill:s.deep,anchor:"middle"}),o}export const crisp=e=>Math.round(e)+.5;
@@ -0,0 +1 @@
1
+ import{Canvas as e,measureText as t,wrapText as n,elbowPath as o,rrPath as r,edgeLabel as l,chip as s,ACCENT_NAMES as a}from"./diagram-draw.js";let i=36;export function applyThemeMetrics(e){i=e&&e.mono?16:36}const c=25,h=13,m=118;export const pickColor=(e,t)=>e||a[t%a.length];const d=e=>Array.isArray(e)?e:null==e?[]:[e],f=e=>null==e?"":String(e),p=15.5;function g(e){const t=e.figure;if(!t||"object"!=typeof t)return null;const n="top"===t.caption?"top":"bottom"===t.caption?"bottom":"none";if("none"===n)return{text:"",pos:"none"};const o=t.no??t.number??t.n,r=null!=o&&""!==String(o),l=f(t.title||e.title||""),s=(r?"图 "+o:"")+(l?(r?" ":"")+l:"");return{text:s,pos:s?n:"none"}}function u(e,t,o,r){if(!t||!t.text)return 0;const l=n(t.text,p,o-2*i,{bold:!0});return e.lines("over",o/2,r+p,l,{size:p,bold:700,fill:e.t.ink,anchor:"middle",lh:22.5}),22.5*l.length+12}function b(e,t,o){const r=e.t,l=g(t);if(l)return i+("top"===l.pos?u(e,l,o,i):0);if(!t.title&&!t.subtitle)return i;const s=r.accent(pickColor(t.accent,0));let a=i;const m=a;if(t.title&&(e.text("over",i+16,a+19.5,t.title,{size:c,bold:700,fill:r.ink}),a+=35),t.subtitle){const l=n(t.subtitle,h,o-2*i-18);e.lines("over",i+16,a+6.5,l,{size:h,lh:19,fill:r.ink2}),a+=19*l.length+2}e.rect("over",i,m+3,4.5,Math.max(18,a-m-8),{r:2.5,fill:s.base});return d(t.meta).map(f).filter(Boolean).forEach((t,n)=>e.text("over",o-i,m+14+17*n,t,{size:11.5,fill:r.ink3,anchor:"end"})),a+=14,e.line("back",i,a,o-i,a,{stroke:r.border,sw:1}),a+22}function x(e,o,r,l){const s=e.t,a=g(o),c=d(o.legend),h=d(o.notes).map(f).filter(Boolean);let m=l;if(a&&"bottom"===a.pos&&(m+=16+u(e,a,r,m+16)),!c.length&&!h.length)return m-l+i;if(a?m+=14:(m+=8,e.line("back",i,m,r-i,m,{stroke:s.border,sw:1}),m+=20),c.length){let n=i;c.forEach((o,r)=>{const l=f(o.label??o.name??o),a=s.accent(pickColor(o.color,r));e.rect("over",n,m-8,11,11,{r:3,fill:a.soft2,stroke:a.base,sw:1.2}),e.text("over",n+16,m+1.5,l,{size:11.5,fill:s.ink2}),n+=16+t(l,11.5)+20}),m+=20}for(const t of h){const o=n(t,11.5,r-2*i-14);e.text("over",i,m+4,"·",{size:11.5,fill:s.ink3}),e.lines("over",i+10,m+4,o,{size:11.5,lh:17,fill:s.ink2}),m+=17*o.length}return m-l+i-8}const k=e=>e&&void 0!==e.numeral&&null!==e.numeral&&""!==String(e.numeral);function w(e,o,r={}){const l=r.nameSize||13,s=r.descSize||11,a=k(e)&&!0!==e.numeralOutside?t(String(e.numeral),11,{bold:!0})+8:0,i=o-22-a,c=e.badge?t(f(e.badge),10)+18:0,h=!e.desc&&!d(e.tags).length,m=Math.max(i-c*(h?2:1),36),p=n(e.name,l,m,{bold:!0,maxLines:r.maxNameLines||3}),g=e.desc?n(e.desc,s,i,{maxLines:r.maxDescLines||3}):[],u=d(e.tags).map(f).filter(Boolean);let b=18+p.length*(l+5);return g.length&&(b+=3+g.length*(s+4)),u.length&&(b+=5+(s+7)),a&&(b+=9),{h:Math.max(r.minH||40,b),nameLines:p,descLines:g,tags:u,nameSize:l,descSize:s}}function M(e,n,o,l,a,i,c,h={}){const m=e.t,d=h.m||w(n,a,h),p=!m.mono&&(!0===n.emphasis||"solid"===n.style);e.rect("front",o,l,a,i,{r:7,fill:p?c.base:h.fill||m.bg,stroke:p?c.deep:c.line,sw:p?1:m.mono?1.15:1.1,shadow:!h.flat,dash:"dashed"===n.style?"5 4":null}),p||m.mono||e.path("front",r(o,l,3.5,i,[7,0,0,7]),{fill:c.base});const g=m.mono||!d.descLines.length&&!d.tags.length,u=p?c.on:m.ink,b=p?c.on:m.ink2,x=g?o+a/2+(m.mono?0:1.75):o+11+2,M=d.nameLines.length*(d.nameSize+5),_=M+(d.descLines.length?1+d.descLines.length*(d.descSize+4):0)+(d.tags.length?d.descSize+8:0);let z=l+(g?(i-_)/2+.92*d.nameSize:9+.92*d.nameSize);if(d.nameLines.forEach((t,n)=>e.text("front",x,z+n*(d.nameSize+5),t,{size:d.nameSize,bold:600,fill:u,anchor:g?"middle":"start"})),z+=M,d.descLines.length&&(z+=1,d.descLines.forEach((t,n)=>e.text("front",x,z+n*(d.descSize+4),t,{size:d.descSize,fill:p?c.on:m.ink3,opacity:p?.85:null,anchor:g?"middle":"start"})),z+=d.descLines.length*(d.descSize+4)),d.tags.length){let n=o+11+2;for(const r of d.tags){if(n+(t(r,10)+11)>o+a-6)break;n+=s(e,n,z+1,r,c)+5}}if(n.badge){const r=t(f(n.badge),10)+10;m.mono?e.text("front",o+a-8,l+15,f(n.badge),{size:10,fill:m.ink,anchor:"end"}):(e.rect("front",o+a-r-6,l+6,r,16,{r:8,fill:c.base}),e.text("front",o+a-r/2-6,l+17.5,f(n.badge),{size:10,fill:c.on,anchor:"middle",bold:600}))}return k(n)&&y(e,String(n.numeral),o,l,a,!0===n.numeralOutside),{ink:u,ink2:b}}function y(e,t,n,o,r,l){const s=e.t;if(l)return e.line("over",n+r-4,o+5,n+r+12,o-7,{stroke:s.ink,sw:.9}),void e.text("over",n+r+14,o-8,t,{size:11,bold:700,fill:s.ink});e.text("over",n+r-11+1,o+11+4,t,{size:11,bold:700,fill:s.ink,anchor:"end"})}const _=16,z=12;function $(e){return"string"==typeof e?{name:e}:{...e,name:f(e?.name??e?.label??""),desc:f(e?.desc??e?.description??"")}}function E(e){const t=e.groups.map(e=>Math.max(1,Math.min(e.nodes.length||1,e.bare?8:5)));let n=t.reduce((e,t)=>e+t,0);for(;n>8;){const e=t.indexOf(Math.max(...t));if(t[e]<=1)break;t[e]-=1,n-=1}return t}export function renderLayered(e,o){const l=o.t,s=function(e){return d(e.layers).map((e,t)=>{const n=pickColor(e.color,t),o=d(e.groups).length?d(e.groups).map((e,t)=>({name:f(e.name),desc:f(e.desc),color:e.color||n,nodes:d(e.nodes).map($),bare:!1,idx:t})):[{name:"",desc:"",color:n,nodes:d(e.nodes).map($),bare:!0,idx:0}];return{name:f(e.name),desc:f(e.desc),color:n,groups:o.filter(e=>e.nodes.length||e.name),idx:t}}).filter(e=>e.groups.length||e.name)}(e),a=d(e.sidebars).map((e,t)=>({name:f(e.name),color:pickColor(e.color,s.length+t),side:"left"===e.side?"left":"right",nodes:d(e.nodes).map($),desc:f(e.desc)})),h=Math.max(1,...s.map(e=>E(e).reduce((e,t)=>e+t,0))),m=s.some(e=>e.name)?Math.round(Math.max(104,Math.min(176,Math.max(...s.map(e=>t(e.name,14.5,{bold:!0})))+26))):0,p=132,g=148*a.length,u=Math.max(560,Math.min(Number(e.width)?Number(e.width)-2*i-m-g:1e9,178*h)),k=Math.round(Math.max(2*i+m+u+g,e.title?2*i+20+t(e.title,c,{bold:!0}):0)),y=b(o,e,k),L=[];for(const e of s){const t=E(e),n=t.reduce((e,t)=>e+t,0),o=14*(e.groups.length-1),r=e.groups.some(e=>e.name),l=e.groups.map((e,r)=>Math.floor((u-o)*t[r]/n)),s=(u-(e.groups[0].bare?24:16))/l.reduce((e,t)=>e+t+14,-14),a=e.groups.map((e,n)=>{const o=Math.floor(l[n]*s),r=t[n],a=Math.max(98,Math.floor(((e.bare?o:o-24)-10*(r-1))/r)),i=Math.max(1,Math.ceil(e.nodes.length/r)),c=e.nodes.map(e=>w(e,a)),h=Math.max(40,...c.map(e=>e.h)),m=e.name?25:0,d=(e.bare?0:24)+m+i*h+10*(i-1);return{g:e,gw:o,cols:r,cw:a,nh:h,rows:i,ms:c,head:m,h:d}}),i=Math.max(...a.map(e=>e.h),44);L.push({L:e,gs:a,h:i+(r?8:10),named:r})}const v=y,C=L.reduce((e,t)=>e+t.h,0)+(L.length-1)*_;let S=i,H=k-i;for(const e of a){const t="left"===e.side?S:H-p,r=l.accent(e.color);o.rect("back",t,v,p,C,{r:10,fill:r.soft,stroke:r.line,sw:1});const s=n(e.name,13,114,{bold:!0,maxLines:2});o.lines("mid",t+66,v+22,s,{size:13,bold:600,fill:r.deep,anchor:"middle",lh:17});let a=v+16+17*s.length+6;const i=112;for(const n of e.nodes){const e=w(n,i,{nameSize:12,descSize:10,minH:34});if(a+e.h>v+C-10)break;M(o,n,t+10,a,i,e.h,r,{m:e,nameSize:12,descSize:10}),a+=e.h+8}"left"===e.side?S+=148:H-=148}let N=v;const A=S;for(let t=0;t<L.length;t++){const{L:s,gs:a,h:i}=L[t],c=l.accent(s.color),h=m+u;if(o.rect("back",A,N,h,i,{r:10,fill:c.soft,stroke:c.line,sw:1}),m){o.path("back",r(A,N,m,i,[10,0,0,10]),{fill:c.soft2}),o.line("back",A+m,N+6,A+m,N+i-6,{stroke:c.line,sw:1});const e=n(s.name,14.5,m-20,{bold:!0,maxLines:3}),t=s.desc?n(s.desc,10.5,m-18,{maxLines:3}):[];let a=N+(i-(20*e.length+(t.length?4+14*t.length:0)))/2+14.5;o.lines("mid",A+m/2,a,e,{size:14.5,bold:600,fill:c.deep,anchor:"middle",lh:20}),a+=20*e.length+4,t.length&&o.lines("mid",A+m/2,a,t,{size:10.5,fill:l.ink3,anchor:"middle",lh:14})}let d=A+m+(a[0].g.bare?12:8);const f=Math.max(...a.map(e=>e.h));for(const e of a){const t=e.gw,n=l.accent(e.g.color),r=N+(i-f)/2;e.g.bare||o.rect("mid",d,r,t,f,{r:8,fill:l.bg,stroke:n.line,sw:1,opacity:"dark"===l.name?.45:.7}),e.g.name&&o.text("mid",d+(e.g.bare?2:z),r+16,e.g.name,{size:12,bold:600,fill:n.deep});const s=e.cw,a=d+(e.g.bare?0:z),c=e.rows*e.nh+10*(e.rows-1),h=r+(e.g.bare?0:z)+e.head,m=f-(e.g.bare?0:24)-e.head,p=h+Math.max(0,(m-c)/2);e.g.nodes.forEach((t,n)=>{const r=Math.floor(n/e.cols),i=n%e.cols,c=a+i*(s+10),h=p+r*(e.nh+10);M(o,t,c,h,s,e.nh,l.accent(t.color||e.g.color),{m:e.ms[n]})}),d+=t+14}if(e.flow&&t<L.length-1){const t=A+m+u/2,n=N+i,r=l.border2;"up"!==e.flow&&o.line("over",t-26,n+3,t-26,n+_-3,{stroke:r,sw:1.6,markerEnd:o.marker(r)}),"up"!==e.flow&&"both"!==e.flow||o.line("over",t+26,n+_-3,t+26,n+3,{stroke:r,sw:1.6,markerEnd:o.marker(r)})}N+=i+_}const W=v+C;return{W:k,H:W+x(o,e,k,W)}}function L(e,t,n,o,l){switch(e){case"round":case"stadium":return r(t,n,o,l,Math.min(l/2,18));case"circle":return r(t,n,o,l,Math.min(o,l)/2);case"diamond":return`M${t+o/2},${n} L${t+o},${n+l/2} L${t+o/2},${n+l} L${t},${n+l/2} Z`;case"hex":return`M${t+14},${n} H${t+o-14} L${t+o},${n+l/2} L${t+o-14},${n+l} H${t+14} L${t},${n+l/2} Z`;case"parallelogram":return`M${t+16},${n} H${t+o} L${t+o-16},${n+l} H${t} Z`;case"cylinder":{const e=Math.min(9,l/5);return`M${t},${n+e} A${o/2},${e} 0 0 1 ${t+o},${n+e} V${n+l-e} A${o/2},${e} 0 0 1 ${t},${n+l-e} Z M${t},${n+e} A${o/2},${e} 0 0 0 ${t+o},${n+e}`}default:return r(t,n,o,l,7)}}const v={diamond:[1.45,1.5],circle:[1.35,1.35],hex:[1.2,1],cylinder:[1,1.28],parallelogram:[1.18,1]};export function renderFlow(e,r){const s=r.t,a="LR"!==f(e.direction||"TB").toUpperCase(),{nodes:h,byId:p,edges:g}=function(e){const t=f(e.stepPrefix||"");let n=Number(e.stepStart)>0?Number(e.stepStart):1;const o=d(e.nodes).map((e,o)=>{const r="string"==typeof e?{id:e,name:e}:{...e};let l=f(r.name??r.label??r.id??"");if(t&&!1!==r.step){const e=void 0!==r.step&&!0!==r.step?f(r.step):t+n++;e&&(l=e+" "+l)}return{id:f(r.id||r.name||"n"+o),name:l,desc:f(r.desc??r.description??""),shape:f(r.shape||"box"),color:r.color,lane:f(r.lane||""),rank:Number.isFinite(r.rank)?Number(r.rank):null,tags:d(r.tags),emphasis:!0===r.emphasis,style:r.style,numeral:r.numeral,numeralOutside:r.numeralOutside}}),r=new Map(o.map(e=>[e.id,e])),l=d(e.edges).map(e=>({from:f(e.from??e.source),to:f(e.to??e.target),label:f(e.label??e.text??""),style:f(e.style||"solid"),color:e.color})).filter(e=>r.has(e.from)&&r.has(e.to));return{nodes:o,byId:r,edges:l}}(e);if(!h.length)return A(e,r,"流程图缺少nodes");const u=d(e.lanes).map((e,t)=>"string"==typeof e?{id:e,name:e,color:pickColor(null,t)}:{id:f(e.id||e.name),name:f(e.name||e.id),color:pickColor(e.color,t)}),k=new Map(u.map((e,t)=>[e.id,t]));for(const e of h){const n=230,o=Math.min(n,Math.max(m,t(e.name,13,{bold:!0})+26)),r=e.desc?Math.min(n,Math.max(o,.62*t(e.desc,11)+26)):o,l=w(e,r,{minH:42}),[s,a]=v[e.shape]||[1,1];e.w=Math.round(r*s),e.h=Math.round(l.h*a),e.m=w(e,e.w,{minH:42})}const{rank:y,back:_}=function(e,t){const n=new Map(e.map(e=>[e.id,[]])),o=new Map(e.map(e=>[e.id,0])),r=new Set,l=new Map(e.map(e=>[e.id,0])),s=new Map(e.map(e=>[e.id,[]]));t.forEach((e,t)=>s.get(e.from).push([e.to,t]));const a=e=>{l.set(e,1);for(const[t,n]of s.get(e))1===l.get(t)?r.add(n):0===l.get(t)&&a(t);l.set(e,2)};for(const t of e)0===l.get(t.id)&&a(t.id);t.forEach((e,t)=>{r.has(t)||e.from===e.to||(n.get(e.from).push(e.to),o.set(e.to,o.get(e.to)+1))});const i=new Map(e.map(e=>[e.id,0])),c=e.filter(e=>0===o.get(e.id)).map(e=>e.id),h=new Set(c);for(;c.length;){const e=c.shift();for(const t of n.get(e))i.set(t,Math.max(i.get(t),i.get(e)+1)),o.set(t,o.get(t)-1),0!==o.get(t)||h.has(t)||(h.add(t),c.push(t))}for(const t of e)null!=t.rank&&i.set(t.id,t.rank);return{rank:i,back:r}}(h,g);for(const e of h)e.r=y.get(e.id)||0;const z=Math.max(...h.map(e=>e.r)),$=Array.from({length:z+1},()=>[]);for(const e of h)$[e.r].push(e);const E=u.length>0&&/per-?lane/i.test(f(e.laneRank||e.laneFlow||""));if(E){const e=new Map;for(const t of h){const n=k.has(t.lane)?k.get(t.lane):0,o=e.get(n)||0;t.r=o,e.set(n,o+1),t.lane_i=n}}else if(u.length){const e=new Set;for(const t of h){let n=t.r;const o=k.has(t.lane)?k.get(t.lane):0;for(;e.has(n+":"+o);)n++;e.add(n+":"+o),t.r=n,t.lane_i=o}}else{const e=new Map(h.map(e=>[e.id,[]])),t=new Map(h.map(e=>[e.id,[]]));g.forEach((n,o)=>{_.has(o)||n.from===n.to||(t.get(n.from).push(n.to),e.get(n.to).push(n.from))});const n=new Map(h.map((e,t)=>[e.id,t]));for(let o=0;o<4;o++){const r=o%2==0,l=r?$:[...$].reverse();for(const o of l){const l=new Map;for(const s of o){const o=(r?e:t).get(s.id).map(e=>n.get(e)).filter(e=>null!=e);l.set(s.id,o.length?o.reduce((e,t)=>e+t,0)/o.length:n.get(s.id))}o.sort((e,t)=>l.get(e.id)-l.get(t.id)),o.forEach((e,t)=>n.set(e.id,t))}}}const C=Math.max(...h.map(e=>e.r)),S=Array.from({length:C+1},()=>[]);for(const e of h)S[e.r].push(e);if(u.length)for(const e of S)e.sort((e,t)=>e.lane_i-t.lane_i);const H=S.map(e=>e.length?Math.max(...e.map(e=>a?e.h:e.w)):0),W=[];let O=0;for(let e=0;e<S.length;e++)W.push(O),O+=H[e]+74;const T=Math.max(0,O-74);let R=0;const Z=[];if(u.length){const e=u.map((e,t)=>Math.max(150,...h.filter(e=>e.lane_i===t).map(e=>a?e.w:e.h),0)+34);let t=0;u.forEach((n,o)=>{Z.push([t,e[o]]),t+=e[o]}),R=t;for(const e of h){const[t,n]=Z[e.lane_i];e.cross=t+(n-(a?e.w:e.h))/2}}else{const e=new Map;for(let t=0;t<3;t++){const n=t%2==0?S:[...S].reverse();for(const t of n){const n=t.map(t=>{const n=g.filter(e=>e.from===t.id||e.to===t.id).map(e=>e.from===t.id?e.to:e.from).map(t=>e.get(t)).filter(e=>null!=e);return n.length?n.reduce((e,t)=>e+t,0)/n.length:null});let o=0;t.forEach((e,t)=>{const r=a?e.w:e.h,l=null==n[t]?o:Math.max(o,n[t]-r/2);e.cross=l,o=l+r+30});const r=o-30;t.forEach(t=>e.set(t.id,t.cross+(a?t.w:t.h)/2)),t._w=r}}const t=Math.max(...S.map(e=>e._w||0));for(const e of S){const n=(t-(e._w||0))/2;for(const t of e)t.cross+=n}const n=Math.min(...h.map(e=>e.cross));for(const e of h)e.cross-=n;R=Math.max(...h.map(e=>e.cross+(a?e.w:e.h)))}const q=g.filter((e,t)=>_.has(t)||Math.abs(p.get(e.to).r-p.get(e.from).r)>1||p.get(e.to).r<=p.get(e.from).r),B=a?Math.max(0,...q.map(e=>t(e.label,11)/2+10)):12,F=q.length?26+14*Math.min(4,q.length)+B:0,I=(a?R:T)+2*F,U=Math.round(Math.max(2*i+I,e.title?2*i+20+t(e.title,c,{bold:!0}):0)),j=b(r,e,U),D=u.length&&a?34:0,P=i+F+Math.max(0,(U-2*i-I)/2),V=j+D+(a?0:F),G=e=>a?[P+e.cross,V+W[e.r]]:[P+W[e.r],V+e.cross];u.length&&u.forEach((e,t)=>{const n=s.accent(e.color),[o,l]=Z[t];a?(r.rect("back",P+o,j,l,D+T+12,{r:8,fill:t%2?s.panel:n.soft,stroke:s.border,sw:1}),r.rect("back",P+o,j,l,D-6,{r:8,fill:n.soft2}),r.text("mid",P+o+l/2,j+19,e.name,{size:12.5,bold:600,fill:n.deep,anchor:"middle"})):(r.rect("back",P-10,V+o,T+20,l,{r:8,fill:t%2?s.panel:n.soft,stroke:s.border,sw:1}),r.text("mid",P-4,V+o+16,e.name,{size:12.5,bold:600,fill:n.deep}))});const J={minC:1/0,maxC:-1/0},K=[];for(const e of h){const[t,n]=G(e);J.minC=Math.min(J.minC,a?t:n),J.maxC=Math.max(J.maxC,a?t+e.w:n+e.h),K.push({nd:e,x:t,y:n,w:e.w,h:e.h})}const Q=(e,t,n,o,r,l)=>K.some(s=>s.nd!==r&&s.nd!==l&&Math.max(e,n)>s.x+2&&Math.min(e,n)<s.x+s.w-2&&Math.max(t,o)>s.y+2&&Math.min(t,o)<s.y+s.h-2);let X=0;g.forEach((e,t)=>{const n=p.get(e.from),i=p.get(e.to),c=s.accent(pickColor(e.color,0)),h=e.color?c.base:s.border2,m="dashed"===e.style?"6 5":null,d="thick"===e.style?2.2:1.5,[f,g]=G(n),[u,b]=G(i);let x;if(n.id===i.id){const t=a?`M${f+n.w},${g+.35*n.h} h18 a12,12 0 0 1 0,${.3*n.h} h-18`:`M${f+.35*n.w},${g+n.h} v18 a12,12 0 0 0 ${.3*n.w},0 v-18`;return r.path("mid",t,{stroke:h,sw:d,dash:m,markerEnd:r.marker(h)}),void(e.label&&l(r,a?f+n.w+34:f+n.w/2,a?g+n.h/2:g+n.h+24,e.label))}if(E&&n.lane_i!==i.lane_i){const t=u>f,s=t?f+n.w:f,a=t?u:u+i.w,c=g+n.h/2,p=b+i.h/2;let x=(s+a)/2;for(const e of N(Math.abs(a-s)/2)){const o=(s+a)/2+e;if((t?o>s&&o<a:o<s&&o>a)&&!(Q(o,c,o,p,n,i)||Q(s,c,o,c,n,i)||Q(o,p,a,p,n,i))){x=o;break}}const k=[[s,c],[x,c],[x,p],[a,p]];return r.path("mid",o(k,r.r(10)),{stroke:h,sw:d,dash:m,markerEnd:r.marker(h)}),void(e.label&&l(r,x,(c+p)/2,e.label))}if(1===Math.abs(i.r-n.r)&&i.r>n.r){const t=a?(g+n.h+b)/2:(f+n.w+u)/2;x=a?[[f+n.w/2,g+n.h],[f+n.w/2,t],[u+i.w/2,t],[u+i.w/2,b]]:[[f+n.w,g+n.h/2],[t,g+n.h/2],[t,b+i.h/2],[u,b+i.h/2]],r.path("mid",o(x,r.r(10)),{stroke:h,sw:d,dash:m,markerEnd:r.marker(h)}),e.label&&l(r,a?(f+n.w/2+u+i.w/2)/2:t,a?t:(g+n.h/2+b+i.h/2)/2,e.label)}else{const s=X++,c=!_.has(t)?J.maxC+22+s%4*14:J.minC-22-s%4*14,p=16;if(a){const e=i.r>n.r?g+n.h:g,t=i.r>n.r?b:b+i.h;x=[[f+n.w/2,e],[f+n.w/2,e+(i.r>n.r?p:-p)],[c,e+(i.r>n.r?p:-p)],[c,t+(i.r>n.r?-p:p)],[u+i.w/2,t+(i.r>n.r?-p:p)],[u+i.w/2,t]]}else{const e=i.r>n.r?f+n.w:f,t=i.r>n.r?u:u+i.w;x=[[e,g+n.h/2],[e+(i.r>n.r?p:-p),g+n.h/2],[e+(i.r>n.r?p:-p),c],[t+(i.r>n.r?-p:p),c],[t+(i.r>n.r?-p:p),b+i.h/2],[t,b+i.h/2]]}r.path("mid",o(x,r.r(10)),{stroke:h,sw:d,dash:m||(_.has(t)?"6 5":null),markerEnd:r.marker(h)}),e.label&&l(r,a?c:(x[2][0]+x[3][0])/2,a?(x[2][1]+x[3][1])/2:c,e.label)}}),h.forEach((e,t)=>{const[o,l]=G(e),a=s.accent(pickColor(e.color,u.length?e.lane_i:t));if(e.shape&&"box"!==e.shape){const t=e.emphasis;r.path("front",L(e.shape,o,l,e.w,e.h),{fill:t?a.base:s.bg,stroke:t?a.deep:a.line,sw:1.2,dash:"dashed"===e.style?"5 4":null});const i=n(e.name,12.5,e.w*("diamond"===e.shape?.6:.82),{bold:!0,maxLines:3}),c=e.desc?n(e.desc,10.5,.82*e.w,{maxLines:2}):[],h=17*i.length+(c.length?14*c.length+2:0);let m=l+(e.h-h)/2+12.5;r.lines("front",o+e.w/2,m,i,{size:12.5,bold:600,fill:t?a.on:s.ink,anchor:"middle",lh:17}),m+=17*i.length+2,c.length&&r.lines("front",o+e.w/2,m,c,{size:10.5,fill:t?a.on:s.ink3,anchor:"middle",lh:14})}else M(r,e,o,l,e.w,e.h,a,{m:e.m})});const Y=V+(a?T:R+F)+8;return{W:U,H:Y+x(r,e,U,Y)}}export function renderSequence(e,o){const l=o.t,s=d(e.actors??e.participants).map((e,t)=>"string"==typeof e?{id:e,name:e,color:pickColor(null,t)}:{id:f(e.id||e.name),name:f(e.name||e.id),desc:f(e.desc||""),color:pickColor(e.color,t),kind:f(e.kind||"")});if(!s.length)return A(e,o,"时序图缺少actors");const a=new Map(s.map((e,t)=>[e.id,t])),h=d(e.messages).map(e=>({from:f(e.from),to:f(e.to),text:f(e.text??e.label??""),kind:f(e.kind||e.type||"call"),note:f(e.note||""),color:e.color})).filter(e=>a.has(e.from)&&a.has(e.to)),m=s.map(e=>Math.max(120,t(e.name,13,{bold:!0})+34,e.desc?t(e.desc,10.5)+26:0));h.forEach(e=>{const n=a.get(e.from),o=a.get(e.to);if(n===o)return;const r=t(e.text,11.5)+40,l=Math.abs(o-n),s=Math.min(n,o);for(let e=0;e<l;e++)m[s+e]=Math.max(m[s+e],r/l)});const p=[];let g=0;s.forEach((e,t)=>{p.push(g+m[t]/2),g+=m[t]+46});const u=g-46,k=Math.round(Math.max(2*i+u,e.title?2*i+20+t(e.title,c,{bold:!0}):0)),w=b(o,e,k),M=i+Math.max(0,(k-2*i-u)/2),y=[];let _=w+52+30;for(const e of h)y.push(_),_+=(e.from===e.to?64:46)+(e.note?34:0);const z=_+8;s.forEach((e,t)=>{const s=l.accent(e.color),a=M+p[t];o.line("back",a,w+52,a,z,{stroke:l.border2,sw:1.2,dash:"5 6"});const i=m[t];o.rect("front",a-i/2,w,i,44,{r:8,fill:s.soft,stroke:s.line,sw:1.2,shadow:!0}),o.path("front",r(a-i/2,w,i,3.5,[8,8,0,0]),{fill:s.base});const c=n(e.name,13,i-18,{bold:!0,maxLines:2});o.lines("front",a,w+(e.desc?22:26.5),c,{size:13,bold:600,fill:s.deep,anchor:"middle",lh:16}),e.desc&&o.text("front",a,w+22+16*c.length,e.desc,{size:10.5,fill:l.ink3,anchor:"middle"}),h.length>10&&(o.rect("front",a-i/2,z+6,i,26,{r:7,fill:s.soft,stroke:s.line,sw:1}),o.text("front",a,z+23,e.name,{size:12,bold:600,fill:s.deep,anchor:"middle"}))});const $=new Map(s.map(e=>[e.id,[]])),E=[];h.forEach((e,t)=>{const n=y[t];if("return"===e.kind){const t=$.get(e.from);t&&t.length&&E.push({id:e.from,y1:t.pop(),y2:n})}else e.from!==e.to&&$.get(e.to).push(n)});for(const[e,t]of $)for(const n of t)E.push({id:e,y1:n,y2:z-6});E.forEach(e=>{const t=a.get(e.id),n=l.accent(s[t].color);o.rect("mid",M+p[t]-5,e.y1-6,10,Math.max(16,e.y2-e.y1+12),{r:3,fill:n.soft2,stroke:n.line,sw:1})}),d(e.frames).forEach((e,n)=>{const s=Math.max(0,Number(e.from??0)),a=Math.min(h.length-1,Number(e.to??h.length-1));if(!(a>=s&&y.length))return;const i=l.accent(pickColor(e.color,n+4)),c=M-14,m=M+u+14,d=y[s]-30,p=(y[a]??y[y.length-1])+20;o.rect("back",c,d,m-c,p-d,{r:8,fill:"none",stroke:i.line,sw:1.2,dash:"4 4"});const g=f(e.kind||"alt").toUpperCase(),b=t(g,10.5,{bold:!0})+16;o.path("back",r(c,d,b,19,[8,0,6,0]),{fill:i.soft2}),o.text("back",c+b/2,d+13.5,g,{size:10.5,bold:700,fill:i.deep,anchor:"middle"}),e.label&&o.text("back",c+b+8,d+13.5,f(e.label),{size:11,fill:l.ink2})}),h.forEach((r,s)=>{const i=y[s],c=a.get(r.from),h=a.get(r.to),m=l.accent(pickColor(r.color,c)),d="return"===r.kind?l.ink3:m.base,f="return"===r.kind||"async"===r.kind?"6 4":null;if(c===h){const e=M+p[c];o.path("mid",`M${e+5},${i} h30 a10,10 0 0 1 0,22 h-30`,{stroke:d,sw:1.4,dash:f,markerEnd:o.marker(d,"open")}),o.text("over",e+44,i+8,r.text,{size:11.5,fill:l.ink2})}else{const n=h>c?1:-1,a=M+p[c]+6*n,m=M+p[h]-6*n;o.line("mid",a,i,m,i,{stroke:d,sw:1.5,dash:f,markerEnd:o.marker(d,"return"===r.kind?"open":"arrow")});const g=(a+m)/2,u=(!1===e.numbered?"":s+1+". ")+r.text;if(u.trim()){const e=t(u,11.5)+12;o.rect("over",g-e/2,i-20,e,17,{r:4,fill:l.bg,opacity:.95}),o.text("over",g,i-7.5,u,{size:11.5,fill:"return"===r.kind?l.ink3:l.ink,anchor:"middle"})}}if(r.note){const e=M+(p[c]+p[h])/2,s=n(r.note,10.5,260,{maxLines:2}),a=Math.max(...s.map(e=>t(e,10.5)))+20;o.path("over",`M${e-a/2},${i+10} h${a-8} l8,8 v16 h${-a} v-24 Z`,{fill:l.accent("amber").soft,stroke:l.accent("amber").line,sw:1}),o.lines("over",e-a/2+8,i+25,s,{size:10.5,fill:l.ink2,lh:13})}});const L=z+(h.length>10?40:10);return{W:k,H:L+x(o,e,k,L)}}export function renderEr(e,n){const s=n.t,a=d(e.entities??e.tables).map((e,t)=>({id:f(e.id||e.name),name:f(e.name||e.id),comment:f(e.comment||e.desc||""),color:pickColor(e.color,t),fields:d(e.fields??e.columns).map(e=>"string"==typeof e?{name:e,type:""}:{name:f(e.name),type:f(e.type||""),pk:!!e.pk,fk:!!e.fk,nullable:!1!==e.nullable&&!e.pk,comment:f(e.comment||"")})}));if(!a.length)return A(e,n,"数据模型图缺少entities");const h=new Map(a.map(e=>[e.id,e])),m=d(e.relations).map(e=>({from:f(e.from),to:f(e.to),kind:f(e.kind||e.type||"1-n"),label:f(e.label||"")})).filter(e=>h.has(e.from)&&h.has(e.to));for(const e of a){const n=t(e.name,13.5,{bold:!0})+30,o=Math.max(0,...e.fields.map(e=>t((e.pk?"◆ ":e.fk?"◇ ":"")+e.name,11.5)+t(e.type,11,{mono:!0})+42));e.w=Math.round(Math.min(330,Math.max(190,n,o))),e.h=34+(e.comment?16:0)+22*Math.max(1,e.fields.length)+8}const p=new Map(a.map(e=>[e.id,0]));for(const e of m)p.set(e.from,p.get(e.from)+1),p.set(e.to,p.get(e.to)+1);const g=[],u=new Set(a.map(e=>e.id)),k=new Map(a.map(e=>[e.id,[]]));for(const e of m)k.get(e.from).push(e.to),k.get(e.to).push(e.from);for(;u.size;){const e=[...u].sort((e,t)=>p.get(t)-p.get(e))[0],t=[e];for(u.delete(e);t.length;){const e=t.shift();g.push(e);for(const n of k.get(e))u.has(n)&&(u.delete(n),t.push(n))}}const w=g.map(e=>h.get(e)),M=Number(e.columns)>0?Number(e.columns):Math.max(1,Math.min(4,Math.round(Math.sqrt(1.4*w.length)))),y=52,_=[];w.forEach((e,t)=>{e.gc=t%M,e.gr=Math.floor(t/M),(_[e.gr]||=[]).push(e)});const z=Array.from({length:M},(e,t)=>Math.max(...w.filter(e=>e.gc===t).map(e=>e.w),0)),$=_.map(e=>Math.max(...e.map(e=>e.h))),E=[];let L=0;z.forEach(e=>{E.push(L),L+=e+74});const v=[];let C=0;$.forEach(e=>{v.push(C),C+=e+y});const S=L-74,H=C-y,N=Math.round(Math.max(2*i+S,e.title?2*i+20+t(e.title,c,{bold:!0}):0)),W=b(n,e,N),O=i+Math.max(0,(N-2*i-S)/2);for(const e of a)e.x=O+E[e.gc],e.y=W+v[e.gr];const T=[];m.forEach(e=>{const r=h.get(e.from),a=h.get(e.to),i=s.border2;let c;if(r.gr===a.gr&&1===Math.abs(r.gc-a.gc)){const[e,t]=r.x<a.x?[r,a]:[a,r],n=(e.x+e.w+t.x)/2;c=[[e.x+e.w,e.y+e.h/2],[n,e.y+e.h/2],[n,t.y+t.h/2],[t.x,t.y+t.h/2]]}else if(r.gr===a.gr){const e=(d=r.gr)<v.length-1?W+v[d]+$[d]+26:W+v[d]-26,t=e>r.y;c=[[r.x+r.w/2,t?r.y+r.h:r.y],[r.x+r.w/2,e],[a.x+a.w/2,e],[a.x+a.w/2,t?a.y+a.h:a.y]]}else if(r.gc===a.gc&&1===Math.abs(r.gr-a.gr)){const[e,t]=r.y<a.y?[r,a]:[a,r],n=(e.y+e.h+t.y)/2;c=[[e.x+e.w/2,e.y+e.h],[e.x+e.w/2,n],[t.x+t.w/2,n],[t.x+t.w/2,t.y]]}else if(r.gc===a.gc){const e=(m=r.gc)<E.length-1?O+E[m]+z[m]+37:O+E[m]-37,t=e>r.x;c=[[t?r.x+r.w:r.x,r.y+r.h/2],[e,r.y+r.h/2],[e,a.y+a.h/2],[t?a.x+a.w:a.x,a.y+a.h/2]]}else{const e=a.gr>r.gr,t=e?W+v[a.gr]-26:W+v[r.gr]-26;c=[[r.x+r.w/2,e?r.y+r.h:r.y],[r.x+r.w/2,t],[a.x+a.w/2,t],[a.x+a.w/2,e?a.y:a.y+a.h]]}var m,d;const f=e.kind.replace(/\s/g,""),p=/^n|^m|^\*/i.test(f),g=/n$|m$|\*$/i.test(f);n.path("mid",o(c,n.r(8)),{stroke:i,sw:1.4,markerStart:p?n.marker(i,"crow"):n.marker(i,"dot"),markerEnd:g?n.marker(i,"crow"):n.marker(i,"dot")});const u=c[Math.floor(c.length/2)-1]||c[0],b=c[Math.floor(c.length/2)],x=e.label||f,k=t(x,10.5)+12;let w=null;for(const e of[.5,.3,.7,.18,.82]){const t=u[0]+(b[0]-u[0])*e,n=u[1]+(b[1]-u[1])*e;if(!T.some(e=>Math.abs(e.x-t)<(e.w+k)/2+6&&Math.abs(e.y-n)<20)){w=[t,n];break}}w||(w=[(u[0]+b[0])/2,(u[1]+b[1])/2-20]),T.push({x:w[0],y:w[1],w:k}),l(n,w[0],w[1],x,{size:10.5})});for(const e of a){const t=s.accent(e.color);n.rect("front",e.x,e.y,e.w,e.h,{r:8,fill:s.bg,stroke:t.line,sw:1.2,shadow:!0}),n.path("front",r(e.x,e.y,e.w,34,[8,8,0,0]),{fill:t.soft2}),n.line("front",e.x,e.y+34,e.x+e.w,e.y+34,{stroke:t.line,sw:1}),n.text("front",e.x+12,e.y+22,e.name,{size:13.5,bold:700,fill:t.deep});let o=e.y+34;e.comment&&(n.text("front",e.x+12,o+12,e.comment,{size:10.5,fill:s.ink3}),o+=16),e.fields.forEach((r,l)=>{const a=o+22*l+15;l%2==1&&n.rect("front",e.x+1,o+22*l+1,e.w-2,22,{fill:s.panel});const i=r.pk?"◆":r.fk?"◇":"";i&&n.text("front",e.x+11,a,i,{size:10,fill:r.pk?s.accent("amber").base:t.base}),n.text("front",e.x+(i?25:12),a,r.name,{size:11.5,bold:r.pk?600:null,fill:s.ink}),r.type&&n.text("front",e.x+e.w-11,a,r.type,{size:10.5,fill:s.ink3,anchor:"end",mono:!0})})}const R=W+H+6;return{W:N,H:R+x(n,e,N,R)}}const C=46;function S(e,t){const n="string"==typeof e?{name:e}:{...e};return{name:f(n.name??n.label??""),desc:f(n.desc??n.description??""),color:n.color,tags:d(n.tags),badge:n.badge,style:n.style,emphasis:!0===n.emphasis,stack:n.stack,depth:t,children:d(n.children).map(e=>S(e,t+1))}}export function renderTree(e,n){const r=n.t,l=function(e){if(e.root)return S(e.root,0);const t=d(e.nodes);if(!t.length)return null;const n=new Map;t.forEach((e,t)=>{const o="string"==typeof e?{id:e,name:e}:{...e};o.id=f(o.id||o.name||"n"+t),o.name=f(o.name||o.id),o.children=[],n.set(o.id,o)});let o=null;for(const e of n.values()){const t=e.parent?n.get(f(e.parent)):null;t?t.children.push(e):o||(o=e)}return o?S(o,0):null}(e);if(!l)return A(e,n,"结构图缺少root或nodes");const s="LR"!==f(e.direction||"TB").toUpperCase(),a=Number(e.stackAt)>0?Number(e.stackAt):4,h=(e,t)=>{t(e),e.children.forEach(e=>h(e,t))};h(l,e=>{const n=Math.min(240,Math.max(m,t(e.name,13,{bold:!0})+28,e.desc?Math.min(240,.7*t(e.desc,11)+26):0));e.m=w(e,n,{minH:42}),e.w=n,e.h=e.m.h,e.stacked=!0===e.stack||!1!==e.stack&&s&&e.children.length>=a&&e.children.every(e=>!e.children.length)});const p=e=>{if(e.children.some(e=>e.stacked))for(const t of e.children)!1!==t.stack&&t.children.length&&t.children.every(e=>!e.children.length)&&(t.stacked=!0);e.children.forEach(p)};p(l);const g=(e,t,n)=>{e._c=e.color||(0===n?pickColor(null,0):t),e.children.forEach((t,o)=>g(t,0===n?pickColor(t.color,o):e._c,n+1))};g(l,null,0);const u=e=>{if(!e.children.length)return e.bw=e.w,void(e.bh=e.h);if(e.children.forEach(u),s&&e.stacked){const t=Math.max(...e.children.map(e=>e.bw));e.bw=Math.max(e.w,26+t),e.bh=e.h+23+e.children.reduce((e,t)=>e+t.bh+10,0)-10}else s?(e.bw=Math.max(e.w,e.children.reduce((e,t)=>e+t.bw+22,-22)),e.bh=e.h+C+Math.max(...e.children.map(e=>e.bh))):(e.bh=Math.max(e.h,e.children.reduce((e,t)=>e+t.bh+14,-14)),e.bw=e.w+C+Math.max(...e.children.map(e=>e.bw)))};u(l);const k=Math.round(Math.max(2*i+l.bw,e.title?2*i+20+t(e.title,c,{bold:!0}):0)),y=b(n,e,k),_=i+Math.max(0,(k-2*i-l.bw)/2),z=(e,t,n)=>{if(s){if(e.x=t+(e.bw-e.w)/2,e.stacked&&(e.x=t),e.y=n,!e.children.length)return;if(e.stacked){let o=n+e.h+23;for(const n of e.children)z(n,t+26,o),o+=n.bh+10}else{let o=t+(e.bw-e.children.reduce((e,t)=>e+t.bw+22,-22))/2;for(const t of e.children)z(t,o,n+e.h+C),o+=t.bw+22}}else{e.x=t,e.y=n+(e.bh-e.h)/2;let o=n;for(const n of e.children)z(n,t+e.w+C,o),o+=n.bh+14}};z(l,_,y);const $=(e,t)=>{const l=r.accent(e._c);for(const t of e.children){const l=r.accent(t._c).line;if(s&&e.stacked){const r=e.x+13;n.path("mid",o([[r,e.y+e.h],[r,t.y+t.h/2],[t.x,t.y+t.h/2]],n.r(6)),{stroke:l,sw:1.4})}else if(s){const r=(e.y+e.h+t.y)/2;n.path("mid",o([[e.x+e.w/2,e.y+e.h],[e.x+e.w/2,r],[t.x+t.w/2,r],[t.x+t.w/2,t.y]],n.r(8)),{stroke:l,sw:1.4})}else{const r=(e.x+e.w+t.x)/2;n.path("mid",o([[e.x+e.w,e.y+e.h/2],[r,e.y+e.h/2],[r,t.y+t.h/2],[t.x,t.y+t.h/2]],n.r(8)),{stroke:l,sw:1.4})}}M(n,e,e.x,e.y,e.w,e.h,l,{m:e.m}),e.children.forEach(e=>$(e,t+1))};$(l,0);const E=y+l.bh+6;return{W:k,H:E+x(n,e,k,E)}}export function renderTimeline(e,o){const l=o.t,s=d(e.phases??e.stages).map((e,t)=>"string"==typeof e?{name:e,items:[],color:pickColor(null,t)}:{name:f(e.name||e.title||""),period:f(e.period||e.time||""),desc:f(e.desc||""),color:pickColor(e.color,t),items:d(e.items).map(e=>"string"==typeof e?{text:e}:{text:f(e.text||e.name),done:!!e.done}),goal:f(e.goal||"")});if(!s.length)return A(e,o,"路线图缺少phases");const a=Math.max(210,...s.map(e=>Math.min(300,t(e.name,14,{bold:!0})+40))),h=s.length*a+18*(s.length-1),m=Math.round(Math.max(2*i+h,e.title?2*i+20+t(e.title,c,{bold:!0}):0)),p=b(o,e,m),g=i+Math.max(0,(m-2*i-h)/2),u=a-28,k=s.map(e=>{const t=e.items.map(e=>n(e.text,11.5,u-18,{maxLines:3})),o=e.goal?n("目标:"+e.goal,11,u-8,{maxLines:3}):[],r=e.desc?n(e.desc,11,u-8,{maxLines:3}):[],l=44+16*r.length+(r.length?6:0)+t.reduce((e,t)=>e+17*t.length+7,0)+(o.length?15*o.length+14:0)+10;return{items:t,goalLines:o,descLines:r,h:Math.max(74,l)}}),w=Math.max(...k.map(e=>e.h)),M=p+46;o.line("back",g,M,g+h,M,{stroke:l.border2,sw:2,cap:"round"}),o.path("back",`M${g+h},${M} l-9,-5 v10 Z`,{fill:l.border2}),s.forEach((e,t)=>{const n=l.accent(e.color),s=g+t*(a+18),i=s+a/2;e.period&&o.text("over",i,p+14,e.period,{size:11,fill:l.ink3,anchor:"middle"}),o.circle("over",i,M,7,{fill:n.base,stroke:l.bg,sw:2.5}),o.line("mid",i,M+7,i,M+22,{stroke:n.line,sw:1.4});const c=M+22,h=k[t];o.rect("front",s,c,a,w,{r:9,fill:l.bg,stroke:n.line,sw:1.2,shadow:!0}),o.path("front",r(s,c,a,30,[9,9,0,0]),{fill:n.soft2}),o.text("front",s+14,c+20,e.name,{size:14,bold:700,fill:n.deep});let m=c+30+14;if(h.descLines.length&&(o.lines("front",s+14,m,h.descLines,{size:11,fill:l.ink3,lh:16}),m+=16*h.descLines.length+6),h.items.forEach((t,r)=>{const a=e.items[r].done;o.circle("front",s+18,m-4,3,{fill:a?n.base:"none",stroke:n.base,sw:1.4}),o.lines("front",s+28,m,t,{size:11.5,fill:l.ink2,lh:17}),m+=17*t.length+7}),h.goalLines.length){const e=15*h.goalLines.length+10,t=Math.max(m-8,c+w-e-12);o.rect("front",s+10,t,a-20,e,{r:5,fill:n.soft}),o.lines("front",s+18,t+15,h.goalLines,{size:11,fill:n.deep,lh:15})}});const y=M+22+w+6;return{W:m,H:y+x(o,e,m,y)}}function H(e,t){const n="string"==typeof e?{name:e}:{...e||{}};return{id:f(n.id||n.numeral||n.name||"m"+t),name:f(n.name??n.label??""),desc:f(n.desc??n.description??""),numeral:n.numeral,numeralOutside:n.numeralOutside,shape:f(n.shape||"box"),tags:d(n.tags),color:n.color,style:n.style,columns:Number(n.columns)||0,children:d(n.children).map(H)}}export function renderModule(e,r){const s=r.t,a=e.root?H(e.root,0):{id:"__root",name:f(e.title||e._title||""),desc:"",numeral:e.numeral,shape:"box",tags:[],columns:Number(e.columns)||0,children:d(e.modules??e.nodes).map(H),bare:!0};if(!a.children.length&&!a.name)return A(e,r,"模块图缺少root或modules");e.columns&&!a.columns&&(a.columns=Number(e.columns)||0);const c=e=>{if(!e.children.length){const n=Math.min(280,Math.max(m,t(e.name,13,{bold:!0})+30+(k(e)?30:0),e.desc?Math.min(280,.66*t(e.desc,11)+28):0)),o=w(e,n,{minH:46});return e.w=n,e.h=o.h,void(e.m=o)}e.children.forEach(c);const n=(o=e.children.length,(r=e.columns)>0?r:o<=3?Math.max(1,o):Math.min(4,Math.ceil(Math.sqrt(o))));var o,r;const l=Math.max(...e.children.map(e=>e.w)),s=Math.ceil(e.children.length/n),a=[];for(let t=0;t<s;t++)a.push(Math.max(...e.children.slice(t*n,(t+1)*n).map(e=>e.h)));e._cols=n,e._cw=l,e._rowH=a,e.w=n*l+18*(n-1)+28,e.h=(e.bare?0:30)+a.reduce((e,t)=>e+t,0)+18*(s-1)+28};c(a);const h=Math.round(Math.max(2*i+a.w,420)),p=b(r,e,h),g=i+Math.max(0,(h-2*i-a.w)/2),u=new Map,_=(e,t,n,o,r)=>{e._x=t,e._y=n,e._w=o,e._h=r;for(const t of[e.id,e.name,k(e)?String(e.numeral):null])t&&!u.has(t)&&u.set(t,e);if(!e.children.length)return;const l=e._cols,s=Math.floor((o-28-18*(l-1))/l);let a=n+(e.bare?0:30)+14;e.children.forEach((n,o)=>{const r=Math.floor(o/l);o%l===0&&o&&(a+=e._rowH[r-1]+18),_(n,t+14+o%l*(s+18),a,s,e._rowH[r])})};_(a,g,p,a.w,a.h);const z=(e,t)=>{const o=s.accent(pickColor(e.color,t));if(e.children.length){if(!e.bare){r.rect("back",e._x,e._y,e._w,e._h,{r:8,fill:s.bg,stroke:o.line,sw:t?1.2:1.5}),r.line("back",e._x,e._y+30,e._x+e._w,e._y+30,{stroke:o.line,sw:1});const n=e.name+(k(e)?" "+e.numeral:"");r.text("mid",e._x+e._w/2,e._y+15+5,n,{size:13.5,bold:700,fill:o.deep,anchor:"middle"})}e.children.forEach(e=>z(e,t+1))}else if(e.shape&&"box"!==e.shape){r.path("front",L(e.shape,e._x,e._y,e._w,e._h),{fill:s.bg,stroke:o.line,sw:1.2});const t=n(e.name,12.5,.78*e._w,{bold:!0,maxLines:3});r.lines("front",e._x+e._w/2,e._y+(e._h-17*t.length)/2+12.5,t,{size:12.5,bold:600,fill:s.ink,anchor:"middle",lh:17}),k(e)&&y(r,String(e.numeral),e._x,e._y,e._w,!0===e.numeralOutside)}else M(r,e,e._x,e._y,e._w,e._h,o,{m:w(e,e._w,{minH:46})})};z(a,0);const $=[],E=(e,t)=>{const n=[...t,e];$.push({nd:e,x:e._x,y:e._y,w:e._w,h:e._h,chain:n}),e.children.forEach(e=>E(e,n))};E(a,[]),d(e.links??e.edges).forEach(e=>{const t=u.get(f(e.from??e.source)),n=u.get(f(e.to??e.target));if(!t||!n||t===n)return;const a=s.mono?s.ink:s.border2,i="dashed"===f(e.style)?"6 5":null,c=function(e,t,n){const o=new Set;for(const r of n)r.nd!==e&&r.nd!==t||r.chain.forEach(e=>o.add(e));const r=(e,t,r,l)=>n.some(n=>{if(o.has(n.nd))return!1;const s=Math.max(e,r)>n.x+2&&Math.min(e,r)<n.x+n.w-2,a=Math.max(t,l)>n.y+2&&Math.min(t,l)<n.y+n.h-2;return s&&a}),l=[e._x+e._w/2,e._y+e._h/2],s=[t._x+t._w/2,t._y+t._h/2],a=()=>{const n=s[0]>l[0],o=n?e._x+e._w:e._x,a=n?t._x:t._x+t._w,i=(o+a)/2;for(const e of N(Math.abs(a-o)/2)){const t=i+e;if((n?t>o&&t<a:t<o&&t>a)&&!(r(o,l[1],t,l[1])||r(t,l[1],t,s[1])||r(t,s[1],a,s[1])))return[[o,l[1]],[t,l[1]],[t,s[1]],[a,s[1]]]}return null},i=()=>{const n=s[1]>l[1],o=n?e._y+e._h:e._y,a=n?t._y:t._y+t._h,i=(o+a)/2;for(const e of N(Math.abs(a-o)/2)){const t=i+e;if((n?t>o&&t<a:t<o&&t>a)&&!(r(l[0],o,l[0],t)||r(l[0],t,s[0],t)||r(s[0],t,s[0],a)))return[[l[0],o],[l[0],t],[s[0],t],[s[0],a]]}return null},c=Math.abs(s[0]-l[0])>=Math.abs(s[1]-l[1])?[a,i]:[i,a];for(const e of c){const t=e();if(t)return t}const h=s[1]>l[1],m=h?e._y+e._h:e._y,d=h?t._y:t._y+t._h,f=(m+d)/2;return[[l[0],m],[l[0],f],[s[0],f],[s[0],d]]}(t,n,$);if(r.path("mid",o(c,r.r(8)),{stroke:a,sw:1.4,dash:i,markerEnd:r.marker(a)}),e.label){const t=c[Math.floor(c.length/2)-1]||c[0],n=c[Math.floor(c.length/2)];l(r,(t[0]+n[0])/2,(t[1]+n[1])/2,f(e.label),{size:10.5})}});const v=p+a.h+6;return{W:h,H:v+x(r,e,h,v)}}function N(e){const t=[0];for(let n=6;n<=Math.max(6,e);n+=6)t.push(n,-n);return t}export function renderCompare(e,o){const l=o.t,s=d(e.columns??e.items??e.schemes).map((e,t)=>"string"==typeof e?{title:e,items:[],color:pickColor(null,t)}:{title:f(e.title||e.name||""),subtitle:f(e.subtitle||e.desc||""),items:d(e.items).map(e=>"string"==typeof e?{text:e}:{text:f(e.text||e.name),sub:f(e.sub||"")}),verdict:f(e.verdict||e.conclusion||""),color:pickColor(e.color,t),emphasis:!0===e.emphasis,numeral:e.numeral});if(!s.length)return A(e,o,"对照图缺少columns");const a=e.arrows?44:18,c=Math.max(200,...s.map(e=>Math.min(330,Math.max(t(e.title,13.5,{bold:!0})+34,...e.items.map(e=>Math.min(330,.6*t(e.text,11.5)+40)))))),h=Math.round(c),m=s.length*h+(s.length-1)*a,p=Math.round(Math.max(2*i+m,420)),g=b(o,e,p),u=i+Math.max(0,(p-2*i-m)/2),w=h-24,M=s.map(e=>{const t=e.subtitle?n(e.subtitle,11,w,{maxLines:2}):[],o=e.items.map(e=>({lines:n(e.text,11.5,w-14,{maxLines:4}),subLines:e.sub?n(e.sub,10.5,w-14,{maxLines:2}):[]})),r=e.verdict?n(e.verdict,11.5,w-12,{maxLines:3}):[],l=34+(t.length?15*t.length+6:0)+10+o.reduce((e,t)=>e+17*t.lines.length+14*t.subLines.length+8,0)+(r.length?16*r.length+20:0)+12;return{sub:t,items:o,verdict:r,h:l}}),_=Math.max(...M.map(e=>e.h),90);s.forEach((t,i)=>{const c=l.accent(t.color),m=u+i*(h+a);o.rect("back",m,g,h,_,{r:8,fill:l.bg,stroke:c.line,sw:t.emphasis&&!l.mono?1.8:1.3}),l.mono?o.line("back",m,g+34,m+h,g+34,{stroke:c.line,sw:1}):o.path("back",r(m,g,h,34,[o.r(8),o.r(8),0,0]),{fill:c.soft2});const d=n(t.title,13.5,h-20,{bold:!0,maxLines:2});o.lines("mid",m+h/2,g+(d.length>1?15:22),d,{size:13.5,bold:700,fill:c.deep,anchor:"middle",lh:16});let f=g+34+14;const p=M[i];if(p.sub.length&&(o.lines("mid",m+12,f,p.sub,{size:11,fill:l.ink3,lh:15}),f+=15*p.sub.length+6),p.items.forEach(e=>{o.circle("mid",m+16,f-4,2.2,{fill:c.base}),o.lines("mid",m+24,f,e.lines,{size:11.5,fill:l.ink,lh:17}),f+=17*e.lines.length,e.subLines.length&&(o.lines("mid",m+24,f+2,e.subLines,{size:10.5,fill:l.ink3,lh:14}),f+=14*e.subLines.length),f+=8}),p.verdict.length){const e=16*p.verdict.length+12,t=Math.max(f-6,g+_-e-10);o.rect("mid",m+10,t,h-20,e,{r:5,fill:l.mono?l.bg:c.soft,stroke:c.line,sw:1,dash:"5 4"}),o.lines("mid",m+18,t+15,p.verdict,{size:11.5,fill:c.deep,lh:16})}if(k(t)&&y(o,String(t.numeral),m,g,h,!1),e.arrows&&i<s.length-1){const e=g+_/2,t=l.mono?l.ink:l.border2;o.line("over",m+h+8,e,m+h+a-8,e,{stroke:t,sw:1.6,markerEnd:o.marker(t)})}});let z=g+_;const $=f(e.footer||e.conclusion||"");if($){const t=n($,12,m-28),r=18*t.length+20,a=l.accent(pickColor(e.footerColor,s.length));o.rect("mid",u,z+14,m,r,{r:8,fill:l.mono?l.bg:a.soft,stroke:a.line,sw:1.2}),o.lines("mid",u+14,z+14+20,t,{size:12,fill:l.ink,lh:18}),z+=14+r}return z+=6,{W:p,H:z+x(o,e,p,z)}}export function renderState(e,o){const r=o.t,s=d(e.states??e.nodes).map((e,t)=>"string"==typeof e?{id:e,name:e,color:pickColor(null,t)}:{id:f(e.id||e.name),name:f(e.name||e.id),desc:f(e.desc||""),color:pickColor(e.color,t),initial:!0===e.initial,terminal:!0===e.terminal,numeral:e.numeral});if(!s.length)return A(e,o,"状态机图缺少states");const a=new Map(s.map((e,t)=>[e.id,t])),c=d(e.transitions??e.edges).map(e=>({from:f(e.from??e.source),to:f(e.to??e.target),label:f(e.label??e.text??""),style:f(e.style||"")})).filter(e=>a.has(e.from)&&a.has(e.to));s.forEach(e=>{e.w=Math.max(132,t(e.name,13.5,{bold:!0})+34,e.desc?Math.min(230,.72*t(e.desc,10.5)+26):0),e.nl=n(e.name,13.5,e.w-20,{bold:!0,maxLines:2}),e.dl=e.desc?n(e.desc,10.5,e.w-18,{maxLines:2}):[],e.h=20+18*e.nl.length+(e.dl.length?14*e.dl.length+4:0)});const h=Math.max(...s.map(e=>e.w)),m=Math.max(...s.map(e=>e.h)),p=c.filter(e=>a.get(e.to)>a.get(e.from)+1),g=c.filter(e=>a.get(e.to)<a.get(e.from)),u=(c.filter(e=>e.from===e.to).length?46:0)+(p.length?36+26*p.length:0),w=g.length?34+26*g.length:0,M=s.some(e=>e.initial)?42:0,_=s.length*h+96*(s.length-1)+M,z=Math.round(Math.max(2*i+_,420)),$=b(o,e,z),E=i+M+Math.max(0,(z-2*i-_)/2),L=$+u,v=e=>E+e*(h+96),C=r.mono?r.ink:r.border2,S=s.findIndex(e=>e.initial);if(S>=0){const e=L+m/2;o.circle("mid",v(S)-34,e,5,{fill:r.mono?r.ink:r.accent(s[S].color).base}),o.line("mid",v(S)-29,e,v(S)-6,e,{stroke:C,sw:1.6,markerEnd:o.marker(C)})}let H=0,N=0;c.forEach(e=>{const t=a.get(e.from),n=a.get(e.to),r="dashed"===e.style?"6 5":null;if(t===n){const n=v(t)+h/2;return o.path("mid",`M${n-26},${L} C${n-26},${L-38} ${n+26},${L-38} ${n+26},${L}`,{stroke:C,sw:1.5,dash:r,markerEnd:o.marker(C)}),void(e.label&&l(o,n,L-32,e.label,{size:10.5}))}if(n===t+1){const s=L+m/2;return o.line("mid",v(t)+h+4,s,v(n)-6,s,{stroke:C,sw:1.6,dash:r,markerEnd:o.marker(C)}),void(e.label&&l(o,(v(t)+h+v(n))/2,s-14,e.label,{size:10.5}))}const s=n>t,i=s?H++:N++,c=s?L-30-26*i:L+m+30+26*i,d=v(t)+h/2,f=v(n)+h/2,p=s?L:L+m,g=s?L:L+m;if(o.path("mid",`M${d},${p} C${d},${c} ${f},${c} ${f},${g}`,{stroke:C,sw:1.5,dash:r,markerEnd:o.marker(C)}),e.label){l(o,(d+f)/2,(p+3*c)/4,e.label,{size:10.5})}}),s.forEach((e,t)=>{const n=r.accent(e.color),l=v(t);o.rect("front",l,L,h,m,{r:r.mono?0:Math.min(14,m/2),fill:r.bg,stroke:n.line,sw:1.4}),e.terminal&&o.rect("front",l+4,L+4,h-8,m-8,{r:r.mono?0:10,fill:"none",stroke:n.line,sw:1});const s=18*e.nl.length+(e.dl.length?14*e.dl.length+4:0);let a=L+(m-s)/2+13.5;o.lines("front",l+h/2,a,e.nl,{size:13.5,bold:700,fill:n.deep,anchor:"middle",lh:18}),a+=18*e.nl.length+4,e.dl.length&&o.lines("front",l+h/2,a,e.dl,{size:10.5,fill:r.ink3,anchor:"middle",lh:14}),k(e)&&y(o,String(e.numeral),l,L,h,!1)});const W=L+m+w+6;return{W:z,H:W+x(o,e,z,W)}}function A(e,t,n){const o=b(t,{...e,title:e.title||"(空图)"},640);return t.rect("front",i,o,640-2*i,70,{r:8,fill:t.t.accent("amber").soft,stroke:t.t.accent("amber").line,sw:1}),t.text("front",i+16,o+30,n,{size:13,fill:t.t.ink}),t.text("front",i+16,o+50,"请补全spec后重新生成。",{size:11.5,fill:t.t.ink3}),{W:640,H:o+70+i}}export const RENDERERS={layered:renderLayered,flow:renderFlow,sequence:renderSequence,er:renderEr,tree:renderTree,timeline:renderTimeline,module:renderModule,compare:renderCompare,state:renderState};export{e as Canvas};
@@ -0,0 +1 @@
1
+ import{execFile as e,execFileSync as r,spawn as t}from"node:child_process";import o from"node:fs";import n from"node:os";import i from"node:path";let s;function a(e){try{return r("win32"===process.platform?"where":"which",[e],{stdio:["ignore","ignore","ignore"],timeout:4e3,windowsHide:!0}),!0}catch{return!1}}export function findChrome(){const e=[process.env.HUICAI_CHROME,process.env.CHROME_PATH,process.env.PUPPETEER_EXECUTABLE_PATH].filter(Boolean);for(const r of e)if(o.existsSync(r))return r;if("darwin"===process.platform){const e=["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Chromium.app/Contents/MacOS/Chromium","/Applications/Brave Browser.app/Contents/MacOS/Brave Browser","/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"];for(const r of e)if(o.existsSync(r))return r}else if("win32"===process.platform){const e=[process.env.PROGRAMFILES,process.env["PROGRAMFILES(X86)"],process.env.LOCALAPPDATA].filter(Boolean),r=["Google\\Chrome\\Application\\chrome.exe","Microsoft\\Edge\\Application\\msedge.exe","Chromium\\Application\\chrome.exe","BraveSoftware\\Brave-Browser\\Application\\brave.exe"];for(const t of e)for(const e of r){const r=i.join(t,e);if(o.existsSync(r))return r}}else for(const e of["google-chrome","google-chrome-stable","chromium","chromium-browser","microsoft-edge","brave-browser"])if(a(e))return e;return null}export function detectRaster(){if(void 0!==s)return s;const e=findChrome();return s=e?{kind:"chrome",bin:e}:a("rsvg-convert")?{kind:"rsvg",bin:"rsvg-convert"}:a("inkscape")?{kind:"inkscape",bin:"inkscape"}:a("magick")?{kind:"magick",bin:"magick"}:a("convert")?{kind:"magick",bin:"convert"}:{kind:null,bin:null},s}export function resetRasterCache(){s=void 0}function c(r,t,o=9e4){return new Promise(n=>{let i;try{i=e(r,t,{timeout:o,windowsHide:!0,maxBuffer:8<<20},(e,r,t)=>n({ok:!e,out:String(r||""),err:e?String(t||"")||e.message:""}))}catch(e){return void n({ok:!1,out:"",err:e.message})}i.on("error",e=>n({ok:!1,out:"",err:e.message}))})}function d(e,r,n,i=9e4){return new Promise(s=>{let a,c=!1,d=-1,u=0,p="";const l=(e,r)=>{if(!c){c=!0,clearInterval(m),clearTimeout(f);try{a?.kill("SIGTERM")}catch{}setTimeout(()=>{try{a?.kill("SIGKILL")}catch{}},800).unref?.(),s({ok:e,err:r||p})}};try{a=t(e,r,{stdio:["ignore","ignore","pipe"],windowsHide:!0,detached:!1})}catch(e){return void s({ok:!1,err:e.message})}a.stderr?.on("data",e=>{p=(p+String(e)).slice(-2e3)}),a.on("error",e=>l(!1,e.message)),a.on("exit",()=>setTimeout(()=>l(o.existsSync(n)),60).unref?.());const m=setInterval(()=>{let e;try{e=o.statSync(n)}catch{return}e.size>0&&e.size===d?(u++,u>=2&&l(!0)):(u=0,d=e.size)},120),f=setTimeout(()=>l(o.existsSync(n),"渲染超时"),i);m.unref?.(),f.unref?.()})}export async function rasterize(e,r){const t=detectRaster(),s="pdf"===r.format?"pdf":"png",a=Math.max(1,Math.min(6,Number(r.scale)||3)),u=Math.max(1,Math.round(r.width)),p=Math.max(1,Math.round(r.height));if(!t.kind)return{ok:!1,error:"未找到可用的栅格化后端(Chrome/Edge/Chromium、rsvg-convert、inkscape或ImageMagick装其一即可)"};if("pdf"===s&&"chrome"!==t.kind)return{ok:!1,error:`PDF导出需要Chrome/Edge(当前可用后端:${t.kind})`};if(u*a*p*a>4e8)return{ok:!1,error:`目标位图过大(${u*a}×${p*a}):请降低scale或拆分图形`};const l=process.pid+"-"+Number(process.hrtime.bigint()%100000n),m=function(){const e=i.join(n.tmpdir(),"huicai-diagram");return o.mkdirSync(e,{recursive:!0}),e}(),f=i.join(m,`d${l}.svg`),h=i.join(m,`d${l}.html`);o.mkdirSync(i.dirname(r.outPath),{recursive:!0});try{let n;if("chrome"===t.kind){o.writeFileSync(h,function(e,r,t,o){return`<!doctype html><html><head><meta charset="utf-8"><style>@page{size:${r}px ${t}px;margin:0}html,body{margin:0;padding:0;background:${o||"transparent"};}svg{display:block;width:${r}px;height:${t}px}</style></head><body>${e.replace(/^<\?xml[^>]*\?>\s*/,"")}</body></html>`}(e,u,p,r.background||"#ffffff"),"utf8");try{o.unlinkSync(r.outPath)}catch{}const c=["--headless=new","--disable-gpu","--no-sandbox","--disable-dev-shm-usage","--hide-scrollbars","--no-first-run","--no-default-browser-check","--no-pings","--disable-extensions","--disable-background-networking","--disable-component-update","--disable-sync","--disable-default-apps","--disable-breakpad","--metrics-recording-only","--disable-client-side-phishing-detection","--safebrowsing-disable-auto-update","--password-store=basic","--use-mock-keychain","--disable-features=Translate,MediaRouter,OptimizationHints,InterestFeedContentSuggestions,CalculateNativeWinOcclusion","--virtual-time-budget=1500",`--user-data-dir=${i.join(m,"profile")}`],l="pdf"===s?[...c,"--no-pdf-header-footer",`--print-to-pdf=${r.outPath}`,h]:[...c,`--force-device-scale-factor=${a}`,`--window-size=${u},${p}`,`--screenshot=${r.outPath}`,h];n=await d(t.bin,l,r.outPath),o.existsSync(r.outPath)||(n=await d(t.bin,l.map(e=>"--headless=new"===e?"--headless":e),r.outPath))}else o.writeFileSync(f,e,"utf8"),n="rsvg"===t.kind?await c(t.bin,["-w",String(u*a),"-h",String(p*a),"-b",r.background||"white","-o",r.outPath,f]):"inkscape"===t.kind?await c(t.bin,[f,"--export-type=png",`--export-filename=${r.outPath}`,"--export-width="+u*a,"--export-height="+p*a,`--export-background=${r.background||"#ffffff"}`]):await c(t.bin,["-density",String(96*a),"-background",r.background||"white","-resize",`${u*a}x${p*a}`,f,r.outPath]);if(!o.existsSync(r.outPath))return{ok:!1,backend:t.kind,error:(n&&n.err?n.err.split("\n").slice(-3).join(" ").slice(0,300):"")||"栅格化未产出文件"};const l=o.statSync(r.outPath).size;return{ok:!0,backend:t.kind,px:"pdf"===s?null:[u*a,p*a],bytes:l}}catch(e){return{ok:!1,backend:t.kind,error:e.message}}finally{for(const e of[f,h])try{o.unlinkSync(e)}catch{}}}export function rasterHint(){const e=detectRaster();return e.kind?{chrome:"Chrome/Edge无头渲染",rsvg:"librsvg",inkscape:"Inkscape",magick:"ImageMagick"}[e.kind]:"本机未安装位图后端,仅产出矢量SVG(放大不失真);需要PNG/PDF可安装Chrome/Edge或rsvg-convert。"}
package/src/diagram.js ADDED
@@ -0,0 +1 @@
1
+ import e from"node:fs";import t from"node:path";import{Canvas as n}from"./diagram-draw.js";import{RENDERERS as r,applyThemeMetrics as o}from"./diagram-layouts.js";import{rasterize as a,detectRaster as i}from"./diagram-raster.js";const s={layered:"layered",layer:"layered",architecture:"layered",arch:"layered","架构图":"layered","分层架构":"layered","总体架构":"layered","技术架构":"layered","业务架构":"layered","能力地图":"layered",capability:"layered",block:"layered",component:"layered","部署分层":"layered",flow:"flow",flowchart:"flow",graph:"flow",dag:"flow",dataflow:"flow",topology:"flow","流程图":"flow","数据流":"flow","调用链":"flow","部署拓扑":"flow","泳道图":"flow",swimlane:"flow",sequence:"sequence",seq:"sequence","时序图":"sequence","顺序图":"sequence",interaction:"sequence",er:"er",erd:"er",entity:"er","数据模型":"er","实体关系":"er","表结构":"er",schema:"er",tree:"tree",mindmap:"tree",wbs:"tree","功能结构":"tree","模块分解":"tree","组织结构":"tree","分解图":"tree",timeline:"timeline",roadmap:"timeline","路线图":"timeline","演进路线":"timeline","实施计划":"timeline","里程碑":"timeline",module:"module",modules:"module","模块图":"module","结构图":"module","形状图":"module","组成图":"module","装置图":"module","系统组成":"module","模块组成":"module",block:"module","框图":"module",compare:"compare",comparison:"compare",contrast:"compare","对照图":"compare","对比图":"compare","现有技术":"compare","方案对比":"compare","多重防线":"compare",state:"state",statemachine:"state",fsm:"state","状态机":"state","状态图":"state","状态转移":"state"};export const DIAGRAM_TYPES=["layered","flow","sequence","er","tree","timeline","module","compare","state"];export function normType(e){const t=String(e||"").trim().toLowerCase();return s[t]||s[String(e||"").trim()]||null}export function renderDiagram(e){if(!e||"object"!=typeof e)throw new Error("spec必须是对象");const t=normType(e.type);if(!t)throw new Error(`未知图型type=${JSON.stringify(e.type)};可选:${DIAGRAM_TYPES.join(" / ")}`);const a=new n({theme:e.theme,title:e.title,desc:e.subtitle});o(a.t);const i=!1===e.header?{...e,title:"",subtitle:"",meta:[],_title:e.title}:e,s=r[t](i,a),m=Math.max(1,Math.ceil(s.W)),c=Math.max(1,Math.ceil(s.H));if(m>3e4||c>3e4)throw new Error(`图形尺寸异常(${m}×${c}):内容过多,请拆成多张图`);return{svg:a.render(m,c),width:m,height:c,type:t,stats:l(e,t),warns:d(e,t)}}function l(e,t){const n=e=>Array.isArray(e)?e:[];if("layered"===t){const t=n(e.layers),r=t.reduce((e,t)=>e+n(t.nodes).length+n(t.groups).reduce((e,t)=>e+n(t.nodes).length,0),0);return{"层":t.length,"分组":t.reduce((e,t)=>e+n(t.groups).length,0),"节点":r,"侧栏":n(e.sidebars).length}}if("flow"===t)return{"节点":n(e.nodes).length,"连线":n(e.edges).length,"泳道":n(e.lanes).length};if("sequence"===t)return{"参与者":n(e.actors??e.participants).length,"消息":n(e.messages).length,"分组框":n(e.frames).length};if("er"===t){const t=n(e.entities??e.tables);return{"实体":t.length,"字段":t.reduce((e,t)=>e+n(t.fields??t.columns).length,0),"关系":n(e.relations).length}}if("tree"===t){let t=0;const r=e=>{e&&(t++,n(e.children).forEach(r))};return e.root?r(e.root):t=n(e.nodes).length,{"节点":t}}if("module"===t){let t=0,r=0;const o=e=>{e&&(t++,void 0!==e.numeral&&null!==e.numeral&&""!==String(e.numeral)&&r++,n(e.children).forEach(o))};return e.root?o(e.root):n(e.modules??e.nodes).forEach(o),{"模块":t,"附图标记":r,"连线":n(e.links??e.edges).length}}if("compare"===t){const t=n(e.columns??e.items??e.schemes);return{"对照栏":t.length,"条目":t.reduce((e,t)=>e+n(t&&t.items).length,0)}}return"state"===t?{"状态":n(e.states??e.nodes).length,"迁移":n(e.transitions??e.edges).length}:{"阶段":n(e.phases??e.stages).length}}function d(e,t){const n=[],r=e=>Array.isArray(e)?e:[],o=[],a=e=>{e&&("string"!=typeof e?(o.push([String(e.name??e.label??""),String(e.desc??e.description??"")]),r(e.children).forEach(a),r(e.nodes).forEach(a),r(e.groups).forEach(a)):o.push([e,""]))};r(e.layers).forEach(a),r(e.nodes).forEach(a),r(e.sidebars).forEach(a),r(e.entities??e.tables).forEach(a),r(e.modules).forEach(a),r(e.states).forEach(a),r(e.columns).forEach(e=>{e&&"object"==typeof e&&o.push([String(e.title||""),String(e.subtitle||"")])}),e.root&&a(e.root);const i=o.filter(([e])=>e.length>30).length,s=o.filter(([,e])=>e.length>90).length;if(i&&n.push(`${i} 处节点名过长(>30 字)会被截断,建议把细节挪到desc`),s&&n.push(`${s} 处desc过长(>90 字)会被截断,建议精简到一句话`),e.title||e.figure||n.push("缺title:正式交付的图应有图名"),"flow"===t){const t=new Set(r(e.nodes).map(e=>String("string"==typeof e?e:e.id||e.name))),o=r(e.edges).filter(e=>!t.has(String(e.from??e.source))||!t.has(String(e.to??e.target)));o.length&&n.push(`${o.length} 条连线的from/to找不到对应节点id,已忽略`)}if(e.figure||"patent"===e.theme){"patent"!==e.theme&&n.push('专利附图请置theme:"patent":附图须为黑白线条图,不得使用色彩、灰度与阴影');const t=e.figure&&(e.figure.no??e.figure.number??e.figure.n);null!=t&&""!==String(t)||n.push("缺figure.no:附图须按「图1、图2…」顺序编号");const o=[],a=e=>{e&&"object"==typeof e&&(void 0!==e.numeral&&null!==e.numeral&&""!==String(e.numeral)&&o.push(String(e.numeral)),r(e.children).forEach(a))};e.root&&a(e.root),r(e.modules).forEach(a),r(e.nodes).forEach(a),r(e.states).forEach(a);const i=o.filter((e,t)=>o.indexOf(e)!==t);i.length&&n.push(`附图标记重复:${[...new Set(i)].join("、")}(同一标记只能指代同一部件)`)}return n}export function defaultDiagramPath(e){let n=e;if("string"==typeof n)try{n=JSON.parse(n)}catch{n=null}return t.join("docs","diagrams",function(e){return String(e||"").trim().replace(/[\\/:*?"<>|\s]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||"diagram"}(n?.title||n?.type||"diagram")+".svg")}const m=e=>e>1048576?(e/1048576).toFixed(2)+" MB":Math.max(1,Math.round(e/1024))+" KB";export async function generateDiagram(n,r){let o=r?.spec;if("string"==typeof o)try{o=JSON.parse(o)}catch(e){throw new Error("spec不是合法JSON:"+e.message)}if(!o&&r?.spec_path){const a=t.resolve(n,String(r.spec_path));o=JSON.parse(e.readFileSync(a,"utf8"))}if(!o||"object"!=typeof o)throw new Error("缺少spec(图形结构)或spec_path");r?.type&&!o.type&&(o.type=r.type),r?.theme&&(o.theme=r.theme),r?.title&&!o.title&&(o.title=r.title);const s=renderDiagram(o),l=function(e,n,r){if(!e)throw new Error("工作目录未设置:请在项目目录中运行");let o=String(n||"").trim();o||(o=defaultDiagramPath(r)),/\.(svg|png|pdf)$/i.test(o)||(o+=".svg");const a=t.resolve(e,o),i=t.relative(e,a);if(""===i||i.startsWith("..")||t.isAbsolute(i))throw new Error(`路径超出项目目录(写操作仅限当前项目内):${o}`);return a}(n,r?.path,o),d=l.replace(/\.(svg|png|pdf)$/i,""),c=String(r?.format||"").toLowerCase(),p=/\.png$/i.test(l)?"png":/\.pdf$/i.test(l)?"pdf":"",u=c||p||(i().kind?"both":"svg"),h=Math.max(1,Math.min(6,Number(r?.scale)||3)),f=[],y=[],b=d+".svg";e.mkdirSync(t.dirname(b),{recursive:!0}),e.writeFileSync(b,s.svg,"utf8"),f.push(b),y.push(`✓ 矢量图 ${t.relative(n,b)}(${s.width}×${s.height} pt,${m(Buffer.byteLength(s.svg))},放大任意倍率不失真)`);const w=async e=>{try{return await a(s.svg,e)}catch(e){return{ok:!1,error:e.message}}};if("png"===u||"both"===u){const e=d+".png",r=await w({width:s.width,height:s.height,outPath:e,scale:h,background:"dark"===o.theme?"#0b1220":"#ffffff"});r.ok?(f.push(e),y.push(`✓ 高清位图 ${t.relative(n,e)}(${r.px[0]}×${r.px[1]} px,${h}× 设备像素比,${m(r.bytes)},${r.backend} 渲染)`)):y.push(`· 位图未生成:${r.error}(SVG已可用;Word/WPS/PPT均可直接插入SVG)`)}if("pdf"===u){const e=d+".pdf",r=await w({width:s.width,height:s.height,outPath:e,format:"pdf"});r.ok?(f.push(e),y.push(`✓ PDF ${t.relative(n,e)}(矢量,${m(r.bytes)})`)):y.push(`· PDF未生成:${r.error}`)}const S=Object.entries(s.stats).map(([e,t])=>`${e} ${t}`).join("、");y.push(`图型 ${s.type}|${S}`),s.warns.length&&y.push("提醒:"+s.warns.join(";")),o.title&&!1!==o.header&&!o.figure&&y.push("(要插进Word/PPT、图名由题注给出时,spec加header:false重画,图里就不会再重复一遍标题)");const $=printLegibility(s.svg,s.width,s.height);if($&&$.min<PRINT_MIN_PT){const e=e=>e.toFixed(1);y.push(`⚠ 这张图插进A4 纵向版心(14.66cm)后,最小的字约 ${e($.min)} 磅、多数文字约 ${e($.body)} 磅——打印出来看不清(文档插图要 ≥${PRINT_MIN_PT} 磅)。要进Word的话:${g[s.type]||g.default}`+($.landscapeMin>$.min+.5?`;实在要整张放,插入时横排(Markdown写 ![图题](图){#fig:x .landscape}),横排后最小约 ${e($.landscapeMin)} 磅。`:"(这张图是竖长的,横排也放不大,只能精简或拆图)。")+"只放PPT或屏幕上看可以忽略这条。")}return{text:y.join("\n"),files:f,width:s.width,height:s.height,legibility:$}}export const PRINT_MIN_PT=5.5;const c=415.6,p=544,u=662.2,h=370,g={layered:"每层节点压到 4~5 个(同类合并,明细放正文或另画一张)、tags只留关键的一两个",tree:'分支多就改direction:"LR"(自左向右,图变窄变高),或拆成几张子树',flow:"改direction让图顺着窄边走,步骤多就拆成主流程 + 子流程",sequence:"参与者超过 5 个就拆图,只保留这一段交互涉及的角色",er:"一张图 ≤8 张表,按业务域拆开",default:"减少同一行并排的元素、精简文字,或拆成两张图"};export function printLegibility(e,t,n=0){const r=[...String(e||"").matchAll(/font-size="([\d.]+)"/g)].map(e=>Number(e[1])).filter(e=>e>0);if(!(r.length&&t>0))return null;r.sort((e,t)=>e-t);const o=(e,r)=>Math.min(1,e/t,n>0?r/n:1/0),a=o(c,p);return{min:r[0]*a,body:r[Math.floor(r.length/2)]*a,landscapeMin:r[0]*o(u,h)}}const f={layered:'【layered分层架构图】系统总体架构 / 技术架构 / 业务架构(业务能力地图)/ 部署分层。\n{\n "type":"layered", "title":"XX平台总体架构图", "subtitle":"一句话说明(可选)",\n "meta":["V1.0","2026-08"], // 右上角元信息,可选\n "theme":"light|dark|print", // 可选,默认light;print供黑白打印\n "flow":"down|up|both", // 可选:层与层之间画箭头\n "layers":[ // 自上而下排列,通常 3~6 层\n { "name":"接入层", "desc":"层职责一句话", "color":"blue",\n "groups":[ // 可选:层内再分组(业务架构里就是「业务域」)\n { "name":"客户端", "color":"cyan", "nodes":[\n { "name":"桌面客户端", "desc":"Electron + Vue3", "tags":["Windows","macOS"], "badge":"自研" },\n "移动端" // 只有名字时可直接写字符串\n ]}\n ],\n "nodes":[ ... ] // 不分组时直接给nodes(与groups二选一)\n }\n ],\n "sidebars":[ // 可选:纵向贯穿的支撑体系(安全体系/标准规范/运维保障)\n { "name":"安全保障体系", "side":"right", "color":"red", "nodes":["身份认证","数据加密","审计留痕"] }\n ],\n "legend":[{"label":"自研","color":"blue"},{"label":"外采","color":"slate"}], // 可选\n "notes":["数据流向:自下而上……"] // 可选,图下方注记\n}\n节点字段:name(必填) / desc / tags[] / badge / color / emphasis:true(实底强调) / style:"dashed"(规划中)。\n取色:blue indigo violet cyan teal green amber orange red pink slate,或直接 "#2563eb"。\n经验:一层放 3~6 个节点最好看;超过 8 个引擎会自动换行;层职责写进layer.desc,别塞进节点名。',flow:'【flow流程图】业务流程 / 数据流 / 调用链 / 部署拓扑 / 跨职能泳道图。\n{\n "type":"flow", "title":"订单履约流程", "direction":"TB|LR", // TB自上而下(默认),LR自左向右\n "lanes":[{"id":"user","name":"客户"},{"id":"ops","name":"运营"}], // 可选:有lanes即泳道图\n "nodes":[\n {"id":"start","name":"提交订单","shape":"round","lane":"user"},\n {"id":"chk","name":"风控校验","shape":"diamond","lane":"ops","desc":"命中规则则转人工"},\n {"id":"db","name":"订单库","shape":"cylinder"}\n ],\n "edges":[\n {"from":"start","to":"chk"},\n {"from":"chk","to":"db","label":"通过","color":"green"},\n {"from":"chk","to":"start","label":"驳回","style":"dashed"}\n ]\n}\nshape:box(默认) round/stadium circle diamond(判断) hex cylinder(存储) parallelogram(输入输出)。\nedge:label / style:"solid|dashed|thick" / color。\n引擎自动分层、自动减少连线交叉、跳层与回边自动走外侧通道绕行,不会压到卡片上。\nid必须唯一且edges里引用一致;有环也能画(回边自动虚线绕行)。',sequence:'【sequence时序图】接口交互 / 关键用例 / 详细设计里的调用时序。\n{\n "type":"sequence", "title":"登录鉴权时序",\n "numbered": true, // 消息前自动编号,默认true\n "actors":[\n {"id":"u","name":"用户"},\n {"id":"web","name":"前端","desc":"Vue3"},\n {"id":"gw","name":"网关"},\n {"id":"auth","name":"鉴权服务"}\n ],\n "messages":[\n {"from":"u","to":"web","text":"输入账号密码"},\n {"from":"web","to":"gw","text":"POST /login","kind":"call"},\n {"from":"gw","to":"auth","text":"校验凭据","note":"失败 5 次锁定 10 分钟"},\n {"from":"auth","to":"gw","text":"签发JWT","kind":"return"}\n ],\n "frames":[{"kind":"alt","label":"密码错误","from":2,"to":3}] // 可选:alt/loop/opt/par框,from/to是消息下标\n}\nkind:call(默认实线实心箭头) / return(虚线开口箭头) / async(虚线) ;from===to表示自调用。\n激活条(谁在处理)由引擎按call/return自动推导,不用手写。',er:'【er数据模型图】表结构与关系(详细设计 / 数据库设计说明书)。\n{\n "type":"er", "title":"订单域数据模型", "columns":3, // columns可选:每行摆几张表\n "entities":[\n {"name":"t_order","comment":"订单主表","color":"blue","fields":[\n {"name":"id","type":"bigint","pk":true},\n {"name":"user_id","type":"bigint","fk":true,"comment":"下单人"},\n {"name":"amount","type":"decimal(18,2)"},\n {"name":"status","type":"tinyint"}\n ]},\n {"name":"t_order_item","comment":"订单明细","fields":[...]}\n ],\n "relations":[{"from":"t_order","to":"t_order_item","kind":"1-n","label":"包含"}]\n}\nkind:1-1 / 1-n / n-1 / n-m(「多」端自动画鸦爪)。pk菱形实心、fk菱形空心,引擎自动标注。\n建议一张图 ≤ 12 张表;超了按业务域拆图(订单域/用户域/结算域各一张)。',tree:'【tree结构分解图】功能结构 / 模块分解 / WBS / 组织结构 / 思维导图。\n{\n "type":"tree", "title":"系统功能结构图", "direction":"TB|LR", // TB自上而下(默认,功能结构图常用)\n "stackAt": 4, // 叶子 ≥ 该数量时自动竖排成列(默认 4),避免横向撑爆\n "root":{ "name":"XX管理系统", "children":[\n {"name":"用户中心","desc":"账号与权限","children":[\n {"name":"账号管理"},{"name":"角色权限"},{"name":"登录审计"}\n ]},\n {"name":"业务办理","children":[...]}\n ]}\n}\n也支持扁平写法:{"nodes":[{"id":"a","name":"根"},{"id":"b","name":"子","parent":"a"}]}。\n节点字段同layered(name/desc/tags/badge/color/emphasis/style),另有stack:true|false手动控制竖排。',module:'【module模块图 / 结构图 / 形状图】专利装置权利要求的标配附图,也可画系统组成、模块框图。\n形态是「大框套小框」:外框是装置或系统,内部是各功能模块,模块还可再嵌子模块。\n{\n "type":"module", "theme":"patent",\n "figure":{"no":3,"title":"本发明:XX装置的模块组成"},\n "root":{ "name":"规则热更新装置", "numeral":"100", "columns":3, "children":[\n {"name":"表达式校验模块","numeral":"101","desc":"保存前语法解析"},\n {"name":"热加载模块","numeral":"102","children":[\n {"name":"筛选子模块","numeral":"1021"},\n {"name":"整体替换子模块","numeral":"1022"}\n ]},\n {"name":"沙箱求值模块","numeral":"103","shape":"box"}\n ]},\n "links":[{"from":"101","to":"102","label":"校验通过"}] // 可选,按numeral/name/id引用\n}\n不需要外框时改用 "modules":[ ... ](同children结构)。\nnumeral即附图标记,默认标在框内右上角;置numeralOutside:true则改为框外数字 + 引出线(形状图常用)。\nshape同flow(box/round/circle/cylinder/hex/parallelogram),画硬件形状示意图时用。\n经验:一层模块 3~6 个;模块名统一用「XX模块」;标记编号 100/101/102…,子模块 1021/1022,全篇不得重复。',compare:'【compare对照图】现有技术的N类做法、N重防线、本发明与现有技术对比——交底书「图1」最常用的形态。\n{\n "type":"compare", "theme":"patent",\n "figure":{"no":1,"title":"现有技术:三类风控规则实现方案"},\n "arrows": false, // true则在相邻栏之间画箭头(表达递进,如「三重防线」)\n "columns":[\n {"title":"方案一:规则硬编码","subtitle":"规则写在业务代码中",\n "items":["规则以条件分支写入源码",{"text":"变更等同于源码变更","sub":"改码→编译→测试→发布"}],\n "verdict":"缺点:变更周期以天计"},\n {"title":"方案二:编译型规则引擎","items":["规则编译为可执行形式","以模式匹配网络批量匹配"],\n "verdict":"缺点:冷启动慢、内存翻倍、切换抖动"},\n {"title":"方案三:通用脚本引擎","items":["规则以脚本书写并存库","运行时动态解释执行"],\n "verdict":"缺点:规则内容即可执行代码"}\n ],\n "footer":"三类方案的共同问题:单条规则异常将中断整批评估" // 可选,底部通栏结论\n}\n每栏items 3~5 条最好读;verdict写该方案的结论(现有技术写缺点、本发明写效果)。',state:'【state状态机图】状态与迁移条件型的权利要求(熔断三态、会话状态、任务状态机)。\n{\n "type":"state", "theme":"patent",\n "figure":{"no":6,"title":"单条规则的三态熔断状态机"},\n "states":[\n {"id":"closed","name":"闭合态","desc":"正常参与评估","initial":true,"numeral":"201"},\n {"id":"open","name":"断开态","desc":"直接跳过不求值","numeral":"202"},\n {"id":"half","name":"半开态","desc":"放行一次试探","numeral":"203"}\n ],\n "transitions":[\n {"from":"closed","to":"closed","label":"求值成功→计数清零"},\n {"from":"closed","to":"open","label":"连续异常达阈值"},\n {"from":"open","to":"half","label":"冷却时长届满"},\n {"from":"half","to":"closed","label":"试探成功"},\n {"from":"half","to":"open","label":"仍异常,冷却加倍"}\n ]\n}\nstates按迁移主路径从左到右排(排乱了弧线会来回穿);initial:true的状态左侧自动带起始箭头;\nfrom===to为自环(画在框顶);相邻正向迁移画直线,跨状态与回退迁移自动走上/下弧线分层,互不重叠。',timeline:'【timeline演进路线图】技术演进 / 实施计划 / 里程碑(可研报告、项目建议书标配)。\n{\n "type":"timeline", "title":"三期建设实施路线",\n "phases":[\n {"name":"一期 · 夯基","period":"2026 Q3","color":"blue","desc":"打通主流程",\n "items":[{"text":"完成需求与架构评审","done":true},"核心链路上线","首批 3 家试点"],\n "goal":"日均处理 1 万单,可用性 99.5%"},\n {"name":"二期 · 提效","period":"2026 Q4","items":["智能审核上线","移动端发布"]}\n ]\n}\nitems里done:true会画成实心圆点(已完成);goal单独高亮显示为该阶段验收目标。'},y='可用图型(type):\n· layered分层架构图 —— 系统总体架构 / 技术架构 / 业务架构(能力地图)/ 部署分层\n· flow流程图 —— 业务流程 / 数据流 / 调用链 / 部署拓扑(支持泳道、S1..Sn步骤编号)\n· sequence时序图 —— 接口交互 / 关键用例\n· er数据模型图 —— 表结构与关系\n· tree结构分解图 —— 功能结构 / 模块分解 / WBS / 组织结构\n· timeline演进路线图 —— 技术演进 / 实施计划 / 里程碑\n· module模块图 —— 装置模块组成 / 系统结构图 / 形状示意图(大框套小框 + 附图标记)\n· compare对照图 —— 现有技术N类做法 / N重防线 / 本发明与现有技术对比\n· state状态机图 —— 状态与迁移条件(熔断三态、会话状态机)\n画**专利附图**时置theme:"patent"(纯黑白线条、直角、无阴影,符合附图形式要求)\n并给figure:{no:1,title:"..."}(图号图名,默认标在图的正下方居中)。\n用diagram_schema(type="module") 取某一类的完整字段与范例。';export function diagramSchema(e){if(!e)return y;const t=normType(e);return t?f[t]+'\n\n通用字段(所有图型都支持):title / subtitle / meta[] / legend[] / notes[] / width;header:false = 图里不画标题块(插进Word/PPT、图名交给文档题注时用,免得同一个图名出现两次);theme = light(默认)| dark | print(黑白打印)| **patent(专利附图:纯黑白线条、直角、无阴影、线宽加粗)**;figure = {no:1, title:"图名", caption:"bottom|top|none"} —— 置了figure即进入专利附图体例:不画标题色条与分隔线,只在图的正下方(默认)居中标注「图1 图名」。\n附图标记:任意节点可加numeral:"101"(默认标在框内右上角;numeralOutside:true则框外数字 + 引出线)。\n方法流程图给步骤编号:flow图置stepPrefix:"S",即按nodes书写顺序自动编为S1、S2…(单个节点可用step覆盖或step:false跳过)。':`未知图型:${e}\n\n${y}`}export function makeDiagramTools({cwd:e}){return[{readOnly:!0,spec:{type:"function",function:{name:"diagram_schema",description:"查看某一类工程图的完整字段说明与范例(画图前不确定字段就先查,别猜)。不传type则返回图型菜单。",parameters:{type:"object",properties:{type:{type:"string",description:"图型:layered/flow/sequence/er/tree/timeline"}},required:[]}}},run:async e=>diagramSchema(e?.type)},{readOnly:!1,spec:{type:"function",function:{name:"render_diagram",description:`把结构化spec画成工程级矢量图并落盘:系统总体架构图/技术架构图/业务架构图、流程图与数据流、接口时序图、数据模型图、功能结构图、演进路线图,以及**专利附图**(模块图/结构图/形状图、现有技术对照图、状态机图;置theme:"patent" 即输出符合形式要求的黑白线条图)。**你只给内容结构,版式与美观由引擎负责**——不要自己手写SVG/HTML/mermaid,也不要用run_python画架构图。输出SVG(矢量,放大任意倍率不模糊)+ 高清PNG(默认 3 倍像素密度,贴PPT/Word不糊)。\n${y}\n字段拿不准先调diagram_schema(type)。一次一张图,多张图可在同一轮并行多次调用。`,parameters:{type:"object",properties:{path:{type:"string",description:"输出路径(相对项目根,如docs/diagrams/总体架构.svg)。省略则写到docs/diagrams/<图名>.svg"},spec:{type:"object",description:"图形结构:必含type与该图型对应的内容字段(layers/nodes+edges/actors+messages/entities/root/phases),建议同时给title。完整字段见diagram_schema。",additionalProperties:!0,properties:{type:{type:"string",description:"layered/flow/sequence/er/tree/timeline/module/compare/state"},title:{type:"string",description:"图名(正式交付必填)"},header:{type:"boolean",description:"false = 图里不画标题块(插进Word/PPT、图名由文档题注负责时用)"},subtitle:{type:"string"},theme:{type:"string",description:"light(默认)/dark/print/patent(专利附图黑白线条)"},figure:{type:"object",description:'专利附图体例:{no:1, title:"图名", caption:"bottom|top|none"}',additionalProperties:!0}}},spec_path:{type:"string",description:"可选:从JSON文件读取spec(超大图或复用已保存的spec时用)"},format:{type:"string",description:"svg / png / both(默认,SVG+PNG都出)/ pdf"},scale:{type:"integer",description:"PNG像素倍率,默认 3(3 倍设备像素比);印刷可用 4~6"},theme:{type:"string",description:"light / dark / print(黑白打印友好)/ patent(专利附图:黑白线条)"}},required:["spec"]}}},run:async t=>{try{return(await generateDiagram(e,t)).text+"\n(SVG可直接插入Word/WPS/PPT并无损缩放;需要改动时改spec重新生成,不要手改SVG)"}catch(e){return`绘图失败:${e.message}\n用diagram_schema(type=...) 核对字段后重试。`}}}]}
@@ -0,0 +1 @@
1
+ import{transcribe as t}from"./voice.js";import{extractOnce as e,emptyForm as n,validateSchema as r,missingRequired as o,formSummary as s}from"./voice-form.js";export const MAX_SEGMENTS=600;export const CONTEXT_TAIL_CHARS=180;export const SEGMENT_WINDOW_CHARS=600;export const GAP_MARK="〔此处一段未能转写〕";const i=/[\u3000-\u303f\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff00-\uffef]/;export function joinSegments(t){const e=(t||[]).map(t=>String(t||"").trim()).filter(Boolean);if(!e.length)return"";let n=e[0];for(let t=1;t<e.length;t++){const r=n[n.length-1],o=e[t][0];n+=(i.test(r)&&i.test(o)||/\s$/.test(n)?"":" ")+e[t]}return n}export function createDictation(i={}){const{config:a,ask:f,onUpdate:c,sttModel:l,language:u,io:m={}}=i,g=i.schema?r(i.schema):{ok:!0,schema:null,errors:[]};if(i.schema&&!g.ok)throw new Error("表单定义有问题:"+g.errors.join(";"));const h=g.schema,d=[];let p=n(),x=!1,S=Promise.resolve();const y=new Set;function A(){return joinSegments(d.map(t=>"ok"===t.status?t.text:"failed"===t.status?GAP_MARK:"").filter(Boolean)).trim()}function T(){for(let t=d.length-1;t>=0;t--)if("ok"===d[t].status&&d[t].text)return d[t].text.slice(-180);return""}function _(){return{text:A(),form:p,schema:h,total:d.length,done:d.filter(t=>"ok"===t.status).length,pending:d.filter(t=>"pending"===t.status).length,failed:d.filter(t=>"failed"===t.status).length,errors:d.filter(t=>"failed"===t.status).map(t=>({index:t.index,error:t.error})),missing:h?o(h,p):[],closed:x}}function k(){if("function"==typeof c)try{c(_())}catch{}}return{pushSegment:function(n,r={}){if(x)return-1;if(d.length>=600){const t={index:d.length,status:"failed",text:"",error:"超过单次口述上限(600 段)"};return d.push(t),k(),t.index}const o={index:d.length,status:"pending",text:"",error:""};d.push(o);const s=[r.prompt,T()].filter(Boolean).join("\n"),i=(async()=>{try{const{text:c}=await t(a,{bytes:n,filename:r.filename,mime:r.mime,model:l,language:u,prompt:s||void 0},m);o.status="ok",o.text=c,k(),i=c,h&&"function"==typeof f&&(S=S.then(async()=>{const t=A();if(!t)return;const n=await e(f,{schema:h,transcript:t,form:p,segment:String(i||"").slice(-600)});n?.form&&(p=n.form),k()}).catch(()=>{}))}catch(t){o.status="failed",o.error=String(t?.message||t),k()}var i})();return y.add(i),i.finally(()=>y.delete(i)),k(),o.index},finish:async function(){for(x=!0;y.size;)await Promise.allSettled([...y]);return await S,k(),_()},state:_,summary:()=>h?s(h,p):A()}}export const DICTATION_NOTE="分段口述:每说一段转写一段,表格随之填充。延迟约等于一段的时长加一次转写往返,不是实时语音流(实时流需渠道侧上架双向流模型)。";
package/src/diffview.js CHANGED
@@ -1 +1 @@
1
- import{execSync as t}from"node:child_process";import{c as i}from"./colors.js";function n(i,n){return t(`git ${n}`,{cwd:i,stdio:["ignore","pipe","ignore"],maxBuffer:8388608}).toString()}export function sessionEditedFiles(t){const i=new Set,n=[];for(const r of t||[])if("assistant"===r.role&&Array.isArray(r.tool_calls))for(const t of r.tool_calls){const r=t.function?.name;if("write_file"!==r&&"edit_file"!==r)continue;let s;try{s=JSON.parse(t.function?.arguments||"{}").path}catch{continue}s&&!i.has(s)&&(i.add(s),n.push(s))}return n}export function colorizeDiff(t){return String(t).split("\n").map(t=>t.startsWith("diff --git")||t.startsWith("index ")||t.startsWith("--- ")||t.startsWith("+++ ")?i.bold(t):t.startsWith("@@")?i.cyan(t):t.startsWith("+")?i.green(t):t.startsWith("-")?i.red(t):t).join("\n")}export function renderDiff(t,r){const s=sessionEditedFiles(r),o=[];if(s.length){o.push(i.bold(`本会话智能体改过的文件(${s.length})`));for(const t of s)o.push(" · "+t);o.push("")}if(!function(t){try{return"true"===n(t,"rev-parse --is-inside-work-tree").trim()}catch{return!1}}(t))return s.length?(o.push(i.gray("(非 git 仓库,无法给出 diff;可用 /rewind 回滚本会话改动。)")),o.join("\n")):"当前目录无 git 仓库,且本会话尚无文件改动。";let e="";try{e=n(t,"status --short").trim()}catch{}if(!e)return o.push("工作区干净,无未提交改动。"),o.join("\n");o.push(i.bold("git 状态")),o.push(e.split("\n").map(t=>" "+t).join("\n"));let f="";try{f=n(t,"diff --stat").trim()}catch{}f&&(o.push(""),o.push(i.bold("改动统计")),o.push(f.split("\n").map(t=>" "+t).join("\n")));let u="";try{u=n(t,"diff").trim()}catch{}return u&&(o.push(""),o.push(i.bold("改动明细(未暂存)")),o.push(colorizeDiff(function(t){const i=String(t).split("\n");return i.length<=500?t:i.slice(0,500).join("\n")+"\n…(diff 过长,仅显示前 500 行,用 git diff 查看完整)"}(u)))),o.push(""),o.push(i.gray("(未跟踪文件见上方 ?? 标记;已暂存改动用 git diff --staged 查看。)")),o.join("\n")}
1
+ import{execSync as t}from"node:child_process";import{c as i}from"./colors.js";function n(i,n){return t(`git ${n}`,{cwd:i,stdio:["ignore","pipe","ignore"],maxBuffer:8388608}).toString()}export function sessionEditedFiles(t){const i=new Set,n=[];for(const r of t||[])if("assistant"===r.role&&Array.isArray(r.tool_calls))for(const t of r.tool_calls){const r=t.function?.name;if("write_file"!==r&&"edit_file"!==r)continue;let s;try{s=JSON.parse(t.function?.arguments||"{}").path}catch{continue}s&&!i.has(s)&&(i.add(s),n.push(s))}return n}export function colorizeDiff(t){return String(t).split("\n").map(t=>t.startsWith("diff --git")||t.startsWith("index ")||t.startsWith("--- ")||t.startsWith("+++ ")?i.bold(t):t.startsWith("@@")?i.cyan(t):t.startsWith("+")?i.green(t):t.startsWith("-")?i.red(t):t).join("\n")}export function renderDiff(t,r){const s=sessionEditedFiles(r),o=[];if(s.length){o.push(i.bold(`本会话智能体改过的文件(${s.length})`));for(const t of s)o.push(" · "+t);o.push("")}if(!function(t){try{return"true"===n(t,"rev-parse --is-inside-work-tree").trim()}catch{return!1}}(t))return s.length?(o.push(i.gray("(非git仓库,无法给出diff;可用 /rewind回滚本会话改动。)")),o.join("\n")):"当前目录无git仓库,且本会话尚无文件改动。";let e="";try{e=n(t,"status --short").trim()}catch{}if(!e)return o.push("工作区干净,无未提交改动。"),o.join("\n");o.push(i.bold("git状态")),o.push(e.split("\n").map(t=>" "+t).join("\n"));let f="";try{f=n(t,"diff --stat").trim()}catch{}f&&(o.push(""),o.push(i.bold("改动统计")),o.push(f.split("\n").map(t=>" "+t).join("\n")));let u="";try{u=n(t,"diff").trim()}catch{}return u&&(o.push(""),o.push(i.bold("改动明细(未暂存)")),o.push(colorizeDiff(function(t){const i=String(t).split("\n");return i.length<=500?t:i.slice(0,500).join("\n")+"\n…(diff过长,仅显示前 500 行,用git diff查看完整)"}(u)))),o.push(""),o.push(i.gray("(未跟踪文件见上方 ?? 标记;已暂存改动用git diff --staged查看。)")),o.join("\n")}
package/src/doctor.js CHANGED
@@ -1 +1 @@
1
- import o from"node:fs";import r from"node:path";import{execSync as s}from"node:child_process";import{c as t}from"./colors.js";import{CONFIG_PATH as n}from"./config.js";import{listModels as e}from"./api.js";import{loadHooks as i}from"./hooks.js";import{runtimeStatus as c}from"./pyruntime.js";const p=()=>t.green("✓"),h=()=>t.red("✗"),u=()=>t.yellow("•");export async function runDoctor(l){const{config:$,cwd:a}=l,y=[t.bold("huicai 体检")],m=Number(String(process.versions.node).split(".")[0]);y.push(` ${m>=18?p():h()} Node ${process.version}${m>=18?"":t.yellow("(需 ≥18)")}`),y.push(` ${u()} 配置文件 ${n}`),y.push(` ${u()} 工作目录 ${a}${function(r){try{return o.accessSync(r,o.constants.W_OK),!0}catch{return!1}}(a)?"":t.yellow("(不可写)")}`);const d=!(!$.apiKey||!$.baseUrl);if(y.push(` ${d?p():h()} 登录 ${d?"已登录 · "+$.baseUrl:"未登录(/login 或 huicai login)"}`),d)try{const o=await e($.baseUrl,$.apiKey);y.push(` ${p()} 平台连通 · ${o.length} 个模型/智能体可用`);const r=$.model,s="auto"===r||o.some(o=>o.id===r);y.push(` ${s?p():h()} 当前模型 ${r}${s?"":t.yellow("(不在账号可用列表,建议 /model auto)")}`)}catch(o){y.push(` ${h()} 平台连通失败:${o.message}`)}const g=function(o){try{return"true"===s("git rev-parse --is-inside-work-tree",{cwd:o,stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return!1}}(a);y.push(` ${g?p():u()} Git ${g?"仓库":t.yellow("当前目录非 git 仓库(/diff 与 /rewind code 在 git 下体验更佳)")}`);try{const o=i(a);o.all.length&&y.push(` ${u()} 钩子 已配置 ${o.all.length} 个(/hooks 查看)`),o.projectIgnored&&y.push(` ${u()} 钩子 ${o.projectIgnored} 个项目钩子未信任(HUICAI_PROJECT_HOOKS=1 启用)`)}catch{}const f=r.join(process.env.HOME||process.env.USERPROFILE||"",".huicai","mcp.json");y.push(` ${u()} MCP ${o.existsSync(f)?"已配置 ~/.huicai/mcp.json":"未配置(可选)"}`);try{const o=c($);"1"===process.env.HUICAI_PYTHON_DISABLED?y.push(` ${u()} Python 运行时 已禁用(HUICAI_PYTHON_DISABLED=1)`):o.ready?y.push(` ${p()} Python 运行时 已就绪 · CPython ${o.pythonVersion} · ${o.packages.length} 个包 · ${o.dir}`):y.push(` ${u()} Python 运行时 未安装${t.yellow("(首次数据分析/办公任务时自动安装;可 huicai python setup 预装)")}`);const r=[o.mirrors.uv&&"uv",o.mirrors.python&&"python",o.mirrors.pypi&&"PyPI"].filter(Boolean);r.length&&y.push(` ${u()} Python 镜像覆盖:${r.join(" / ")}`)}catch{}return y.push(t.gray(" (体检只读,不改动任何东西)")),y.join("\n")}
1
+ import s from"node:fs";import o from"node:path";import{execSync as e}from"node:child_process";import{c as n}from"./colors.js";import{CONFIG_PATH as t,isWebSearchOn as r}from"./config.js";import{listModels as i}from"./api.js";import{refreshPlatformWebSearch as c,platformStatusText as h}from"./websearch.js";import{loadHooks as l}from"./hooks.js";import{runtimeStatus as p}from"./pyruntime.js";import{lintSkillLibrary as a}from"./skill-lint.js";import{tokenize as $}from"./codeindex.js";import{skillDirs as u}from"./skills.js";import{auditProject as g}from"./depaudit.js";const d=()=>n.green("✓"),f=()=>n.red("✗"),m=()=>n.yellow("•");export async function runDoctor(y){const{config:j,cwd:w}=y,I=[n.bold("huicai体检")],P=Number(String(process.versions.node).split(".")[0]);I.push(` ${P>=18?d():f()} Node ${process.version}${P>=18?"":n.yellow("(需 ≥18)")}`),I.push(` ${m()} 配置文件 ${t}`),I.push(` ${m()} 工作目录 ${w}${function(o){try{return s.accessSync(o,s.constants.W_OK),!0}catch{return!1}}(w)?"":n.yellow("(不可写)")}`);const v=!(!j.apiKey||!j.baseUrl);if(I.push(` ${v?d():f()} 登录 ${v?"已登录 · "+j.baseUrl:"未登录(/login或huicai login)"}`),v){try{const s=await i(j.baseUrl,j.apiKey);I.push(` ${d()} 平台连通 · ${s.length} 个模型/智能体可用`);const o=j.model,e="auto"===o||s.some(s=>s.id===o);I.push(` ${e?d():f()} 当前模型 ${o}${e?"":n.yellow("(不在账号可用列表,建议 /model auto)")}`)}catch(s){I.push(` ${f()} 平台连通失败:${s.message}`)}if(r()){const s=await c(j,{force:!0});I.push(` ${"available"===s.state?d():"unavailable"===s.state?f():m()} 联网搜索 ${h(s)}`)}else I.push(` ${m()} 联网搜索 本端已关闭(/websearch on打开)`)}const b=function(s){try{return"true"===e("git rev-parse --is-inside-work-tree",{cwd:s,stdio:["ignore","pipe","ignore"]}).toString().trim()}catch{return!1}}(w);I.push(` ${b?d():m()} Git ${b?"仓库":n.yellow("当前目录非git仓库(/diff与 /rewind code在git下体验更佳)")}`);try{const s=l(w);s.all.length&&I.push(` ${m()} 钩子 已配置 ${s.all.length} 个(/hooks查看)`),s.projectIgnored&&I.push(` ${m()} 钩子 ${s.projectIgnored} 个项目钩子未信任(HUICAI_PROJECT_HOOKS=1 启用)`)}catch{}const _=o.join(process.env.HOME||process.env.USERPROFILE||"",".huicai","mcp.json");I.push(` ${m()} MCP ${s.existsSync(_)?"已配置 ~/.huicai/mcp.json":"未配置(可选)"}`);try{const s=p(j);"1"===process.env.HUICAI_PYTHON_DISABLED?I.push(` ${m()} Python运行时 已禁用(HUICAI_PYTHON_DISABLED=1)`):s.ready?I.push(` ${d()} Python运行时 已就绪 · CPython ${s.pythonVersion} · ${s.packages.length} 个包 · ${s.dir}`):I.push(` ${m()} Python运行时 未安装${n.yellow("(首次数据分析/办公任务时自动安装;可huicai python setup预装)")}`);const o=[s.mirrors.uv&&"uv",s.mirrors.python&&"python",s.mirrors.pypi&&"PyPI"].filter(Boolean);o.length&&I.push(` ${m()} Python镜像覆盖:${o.join(" / ")}`)}catch{}try{const o=u(w).filter(o=>s.existsSync(o));if(o.length){const s=a(o,$,{route:!0}),{skills:e,errors:t,warnings:r}=s.counts;if(e)if(t){I.push(` ${f()} 自建技能 ${e} 个 · ${t} 个错误${r?` / ${r} 个警告`:""}`);const o=s.results.find(s=>s.errors.length);o&&I.push(n.gray(` ${o.name}:${o.errors[0]}`));for(const o of s.route.errors.slice(0,1))I.push(n.gray(` 触发路由:${o}`));I.push(n.gray(" 逐条查看:huicai skills lint"))}else if(r){I.push(` ${m()} 自建技能 ${e} 个 · ${r} 个警告(不影响使用,但可能触发不稳)`);const o=s.results.find(s=>s.warnings.length)||null;o&&I.push(n.gray(` ${o.name}:${o.warnings[0]}`))}else I.push(` ${d()} 自建技能 ${e} 个 · 结构与触发路由均正常`);else I.push(` ${m()} 自建技能 无(内置技能不受影响)`)}}catch{}I.push(n.gray(" (体检只读,不改动任何东西)"));try{const s=g(w);if(s.scanned.length){const o=s.findings.filter(s=>"high"===s.level);I.push(o.length?`${f()} 依赖清单:${s.scanned.join("、")} 有 ${o.length} 处严重问题(用dependency_audit看详情与处置)`:s.findings.length?`${m()} 依赖清单:${s.scanned.join("、")} 有 ${s.findings.length} 处可改进(用dependency_audit看详情)`:`${d()} 依赖清单:${s.scanned.join("、")}(${s.entries.length} 条依赖,无清单层面问题)`);for(const s of o.slice(0,3))I.push(` · ${s.file}:${s.line} ${s.what}`)}}catch{}return I.push(n.gray(" (渠道能力实测(跨模态嵌入/视觉/实时语音):/doctor capabilities)")),I.join("\n")}