@huicai/cli 2026.804.0 → 2026.1019.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 (486) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +176 -10
  3. package/bin/huicai.js +166 -73
  4. package/package.json +27 -8
  5. package/skills/ab-testing/trigger-cases.json +30 -0
  6. package/skills/academic-writing/trigger-cases.json +30 -0
  7. package/skills/accounting-ops/trigger-cases.json +30 -0
  8. package/skills/ai-agent/trigger-cases.json +30 -0
  9. package/skills/ai-aiops/trigger-cases.json +30 -0
  10. package/skills/ai-coding/trigger-cases.json +30 -0
  11. package/skills/ai-compliance/trigger-cases.json +30 -0
  12. package/skills/ai-eval/trigger-cases.json +30 -0
  13. package/skills/ai-llm-app/trigger-cases.json +30 -0
  14. package/skills/ai-mlops/trigger-cases.json +30 -0
  15. package/skills/ai-multimodal/trigger-cases.json +30 -0
  16. package/skills/ai-prompt/trigger-cases.json +30 -0
  17. package/skills/ai-rag/trigger-cases.json +30 -0
  18. package/skills/anomaly-detection/trigger-cases.json +30 -0
  19. package/skills/api-design/trigger-cases.json +31 -0
  20. package/skills/api-integration/trigger-cases.json +31 -0
  21. package/skills/arch-design/trigger-cases.json +12 -0
  22. package/skills/arch-diagram/SKILL.md +118 -0
  23. package/skills/arch-diagram/trigger-cases.json +30 -0
  24. package/skills/arch-distributed/trigger-cases.json +30 -0
  25. package/skills/arch-multitenant/trigger-cases.json +30 -0
  26. package/skills/audio-brief/SKILL.md +65 -0
  27. package/skills/audio-brief/trigger-cases.json +30 -0
  28. package/skills/audit-assist/trigger-cases.json +30 -0
  29. package/skills/audit-chain/trigger-cases.json +31 -0
  30. package/skills/banking-credit/trigger-cases.json +30 -0
  31. package/skills/brand-design/trigger-cases.json +30 -0
  32. package/skills/business-plan/trigger-cases.json +30 -0
  33. package/skills/capital-ops/trigger-cases.json +30 -0
  34. package/skills/cicd-pipeline/trigger-cases.json +31 -0
  35. package/skills/code-archaeology/SKILL.md +55 -0
  36. package/skills/code-archaeology/trigger-cases.json +9 -0
  37. package/skills/code-quality/SKILL.md +138 -0
  38. package/skills/code-quality/references/languages.md +107 -0
  39. package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
  40. package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
  41. package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
  42. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
  43. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
  44. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
  45. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
  46. package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
  47. package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
  48. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
  49. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
  50. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
  51. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
  52. package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
  53. package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
  54. package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
  55. package/skills/code-quality/scripts/code_battery.py +1072 -0
  56. package/skills/code-quality/scripts/code_check.py +1269 -0
  57. package/skills/code-quality/scripts/code_csharp.py +371 -0
  58. package/skills/code-quality/scripts/code_fix.py +201 -0
  59. package/skills/code-quality/scripts/code_java.py +695 -0
  60. package/skills/code-quality/scripts/code_jsts.py +313 -0
  61. package/skills/code-quality/scripts/code_lens.py +1079 -0
  62. package/skills/code-quality/scripts/code_python.py +536 -0
  63. package/skills/code-quality/scripts/code_testplan.py +239 -0
  64. package/skills/code-quality/trigger-cases.json +45 -0
  65. package/skills/code-review/SKILL.md +24 -0
  66. package/skills/code-review/trigger-cases.json +35 -0
  67. package/skills/commit-and-pr/trigger-cases.json +31 -0
  68. package/skills/content-creation/trigger-cases.json +30 -0
  69. package/skills/corporate-governance/trigger-cases.json +30 -0
  70. package/skills/crawl-chain/trigger-cases.json +31 -0
  71. package/skills/customer-service/trigger-cases.json +30 -0
  72. package/skills/data-analysis/trigger-cases.json +30 -0
  73. package/skills/data-chain/SKILL.md +1 -1
  74. package/skills/data-chain/trigger-cases.json +31 -0
  75. package/skills/data-governance/trigger-cases.json +30 -0
  76. package/skills/data-report-pro/trigger-cases.json +30 -0
  77. package/skills/data-streaming/trigger-cases.json +30 -0
  78. package/skills/data-wrangling/SKILL.md +1 -1
  79. package/skills/data-wrangling/trigger-cases.json +30 -0
  80. package/skills/db-es/trigger-cases.json +30 -0
  81. package/skills/db-migration/trigger-cases.json +30 -0
  82. package/skills/db-mysql/trigger-cases.json +30 -0
  83. package/skills/db-postgres/trigger-cases.json +30 -0
  84. package/skills/db-redis/trigger-cases.json +30 -0
  85. package/skills/debug-root-cause/trigger-cases.json +30 -0
  86. package/skills/dependency-audit/SKILL.md +56 -0
  87. package/skills/dependency-audit/trigger-cases.json +7 -0
  88. package/skills/dependency-upgrade/trigger-cases.json +31 -0
  89. package/skills/dev-cpp/trigger-cases.json +30 -0
  90. package/skills/dev-cross-platform/trigger-cases.json +30 -0
  91. package/skills/dev-csharp/SKILL.md +23 -0
  92. package/skills/dev-csharp/trigger-cases.json +30 -0
  93. package/skills/dev-frontend/SKILL.md +19 -0
  94. package/skills/dev-frontend/trigger-cases.json +30 -0
  95. package/skills/dev-go/trigger-cases.json +30 -0
  96. package/skills/dev-harmony/trigger-cases.json +30 -0
  97. package/skills/dev-java/SKILL.md +20 -0
  98. package/skills/dev-java/trigger-cases.json +30 -0
  99. package/skills/dev-mobile/trigger-cases.json +30 -0
  100. package/skills/dev-node/trigger-cases.json +30 -0
  101. package/skills/dev-php/trigger-cases.json +30 -0
  102. package/skills/dev-python/SKILL.md +21 -0
  103. package/skills/dev-python/trigger-cases.json +30 -0
  104. package/skills/dev-rust/trigger-cases.json +30 -0
  105. package/skills/dev-toolchain/trigger-cases.json +31 -0
  106. package/skills/doc-chain/trigger-cases.json +31 -0
  107. package/skills/doc-compare/trigger-cases.json +30 -0
  108. package/skills/docx-author/SKILL.md +127 -25
  109. package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
  110. package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
  111. package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
  112. package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
  113. package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
  114. package/skills/docx-author/scripts/docx_battery.py +586 -0
  115. package/skills/docx-author/scripts/docx_build.py +995 -0
  116. package/skills/docx-author/scripts/docx_check.py +1306 -0
  117. package/skills/docx-author/scripts/docx_doc.py +14 -154
  118. package/skills/docx-author/scripts/docx_layout.py +490 -0
  119. package/skills/docx-author/scripts/zh_prose.py +639 -0
  120. package/skills/docx-author/trigger-cases.json +58 -0
  121. package/skills/echo-brief/SKILL.md +84 -0
  122. package/skills/echo-brief/references/echo-format.md +68 -0
  123. package/skills/echo-brief/trigger-cases.json +40 -0
  124. package/skills/ecommerce-ops/trigger-cases.json +30 -0
  125. package/skills/education-training/trigger-cases.json +30 -0
  126. package/skills/ehs-safety/trigger-cases.json +30 -0
  127. package/skills/email-writing/trigger-cases.json +30 -0
  128. package/skills/eng-ai/trigger-cases.json +30 -0
  129. package/skills/eng-architecture/trigger-cases.json +31 -0
  130. package/skills/eng-automation/trigger-cases.json +31 -0
  131. package/skills/eng-collaboration/trigger-cases.json +30 -0
  132. package/skills/eng-complex-env/trigger-cases.json +30 -0
  133. package/skills/eng-data-platform/trigger-cases.json +31 -0
  134. package/skills/eng-database/trigger-cases.json +31 -0
  135. package/skills/eng-develop/trigger-cases.json +30 -0
  136. package/skills/eng-docs/SKILL.md +24 -0
  137. package/skills/eng-docs/trigger-cases.json +30 -0
  138. package/skills/eng-efficiency/trigger-cases.json +30 -0
  139. package/skills/eng-infra/trigger-cases.json +30 -0
  140. package/skills/eng-integration/trigger-cases.json +31 -0
  141. package/skills/eng-legacy/trigger-cases.json +31 -0
  142. package/skills/eng-observability/trigger-cases.json +31 -0
  143. package/skills/eng-ops/trigger-cases.json +31 -0
  144. package/skills/eng-performance/trigger-cases.json +31 -0
  145. package/skills/eng-planning/trigger-cases.json +31 -0
  146. package/skills/eng-quality/SKILL.md +8 -0
  147. package/skills/eng-quality/trigger-cases.json +31 -0
  148. package/skills/eng-release/trigger-cases.json +31 -0
  149. package/skills/eng-reliability/trigger-cases.json +31 -0
  150. package/skills/eng-requirements/trigger-cases.json +12 -0
  151. package/skills/eng-research/trigger-cases.json +30 -0
  152. package/skills/eng-security/trigger-cases.json +31 -0
  153. package/skills/eng-ux/trigger-cases.json +30 -0
  154. package/skills/engineering-chain/trigger-cases.json +30 -0
  155. package/skills/env-offline/trigger-cases.json +31 -0
  156. package/skills/env-private-deploy/trigger-cases.json +31 -0
  157. package/skills/env-xinchuang/trigger-cases.json +31 -0
  158. package/skills/equipment-tpm/trigger-cases.json +30 -0
  159. package/skills/esg-report/trigger-cases.json +30 -0
  160. package/skills/event-management/trigger-cases.json +30 -0
  161. package/skills/excel-analysis-chain/SKILL.md +17 -4
  162. package/skills/excel-analysis-chain/trigger-cases.json +31 -0
  163. package/skills/explain-codebase/trigger-cases.json +11 -0
  164. package/skills/file-ingest/trigger-cases.json +30 -0
  165. package/skills/finance-analysis/trigger-cases.json +31 -0
  166. package/skills/finance-chain/trigger-cases.json +31 -0
  167. package/skills/financial-modeling/trigger-cases.json +31 -0
  168. package/skills/find-simplifications/SKILL.md +43 -0
  169. package/skills/find-simplifications/trigger-cases.json +23 -0
  170. package/skills/foreign-trade/trigger-cases.json +30 -0
  171. package/skills/funnel-cohort/trigger-cases.json +30 -0
  172. package/skills/gov-document/trigger-cases.json +30 -0
  173. package/skills/gui-change-evidence/SKILL.md +47 -0
  174. package/skills/gui-change-evidence/trigger-cases.json +23 -0
  175. package/skills/hr-chain/trigger-cases.json +31 -0
  176. package/skills/hr-operations/trigger-cases.json +30 -0
  177. package/skills/info-disclosure/trigger-cases.json +31 -0
  178. package/skills/infra-gateway/trigger-cases.json +31 -0
  179. package/skills/infra-k8s/trigger-cases.json +31 -0
  180. package/skills/infra-mq/trigger-cases.json +30 -0
  181. package/skills/insurance-advisory/trigger-cases.json +30 -0
  182. package/skills/internal-audit/trigger-cases.json +30 -0
  183. package/skills/ip-management/trigger-cases.json +31 -0
  184. package/skills/ipo-listing/trigger-cases.json +31 -0
  185. package/skills/it-audit/trigger-cases.json +31 -0
  186. package/skills/itsm-ops/trigger-cases.json +31 -0
  187. package/skills/knowledge-sop/trigger-cases.json +31 -0
  188. package/skills/legal-chain/trigger-cases.json +31 -0
  189. package/skills/legal-contract/trigger-cases.json +30 -0
  190. package/skills/litigation-support/trigger-cases.json +31 -0
  191. package/skills/logistics-warehouse/trigger-cases.json +30 -0
  192. package/skills/long-doc-analysis/trigger-cases.json +31 -0
  193. package/skills/management-accounting/trigger-cases.json +30 -0
  194. package/skills/manufacturing-chain/trigger-cases.json +31 -0
  195. package/skills/manufacturing-quality/trigger-cases.json +30 -0
  196. package/skills/marketing-analytics/trigger-cases.json +31 -0
  197. package/skills/marketing-chain/trigger-cases.json +31 -0
  198. package/skills/meeting-minutes/trigger-cases.json +30 -0
  199. package/skills/metrics-system/trigger-cases.json +31 -0
  200. package/skills/negotiation-prep/trigger-cases.json +30 -0
  201. package/skills/obs-prometheus/trigger-cases.json +31 -0
  202. package/skills/office-automation/trigger-cases.json +31 -0
  203. package/skills/okr-review/trigger-cases.json +30 -0
  204. package/skills/ops-fleet/trigger-cases.json +31 -0
  205. package/skills/ops-linux/trigger-cases.json +31 -0
  206. package/skills/optimization-solver/trigger-cases.json +30 -0
  207. package/skills/patent-disclosure/SKILL.md +105 -0
  208. package/skills/patent-disclosure/references/figures.md +110 -0
  209. package/skills/patent-disclosure/references/outline.md +162 -0
  210. package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
  211. package/skills/patent-disclosure/trigger-cases.json +31 -0
  212. package/skills/patent-drafting/SKILL.md +108 -0
  213. package/skills/patent-drafting/trigger-cases.json +31 -0
  214. package/skills/patent-portfolio/SKILL.md +81 -0
  215. package/skills/patent-portfolio/trigger-cases.json +31 -0
  216. package/skills/pdf-toolkit/trigger-cases.json +31 -0
  217. package/skills/perf-optimize/trigger-cases.json +31 -0
  218. package/skills/pivot-crosstab/trigger-cases.json +30 -0
  219. package/skills/policy-analysis/trigger-cases.json +31 -0
  220. package/skills/portfolio-analysis/trigger-cases.json +31 -0
  221. package/skills/ppt-chain/SKILL.md +14 -6
  222. package/skills/ppt-chain/trigger-cases.json +31 -0
  223. package/skills/pptx-author/SKILL.md +86 -33
  224. package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
  225. package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
  226. package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
  227. package/skills/pptx-author/scripts/pptx_battery.py +178 -0
  228. package/skills/pptx-author/scripts/pptx_build.py +119 -0
  229. package/skills/pptx-author/scripts/pptx_check.py +1182 -0
  230. package/skills/pptx-author/scripts/pptx_deck.py +736 -145
  231. package/skills/pptx-author/scripts/pptx_layout.py +384 -0
  232. package/skills/pptx-author/trigger-cases.json +15 -0
  233. package/skills/pr-comms/trigger-cases.json +30 -0
  234. package/skills/procurement-supply/trigger-cases.json +31 -0
  235. package/skills/product-design/trigger-cases.json +31 -0
  236. package/skills/production-planning/trigger-cases.json +31 -0
  237. package/skills/project-management/trigger-cases.json +31 -0
  238. package/skills/project-proposal/trigger-cases.json +31 -0
  239. package/skills/prose-standard/SKILL.md +53 -0
  240. package/skills/prose-standard/trigger-cases.json +23 -0
  241. package/skills/qa-api-automation/trigger-cases.json +31 -0
  242. package/skills/qa-mobile-automation/trigger-cases.json +31 -0
  243. package/skills/qa-ui-automation/trigger-cases.json +31 -0
  244. package/skills/quant-analysis/trigger-cases.json +31 -0
  245. package/skills/rag-triage/SKILL.md +64 -0
  246. package/skills/rag-triage/evals.json +57 -0
  247. package/skills/rag-triage/references/triage-playbook.md +75 -0
  248. package/skills/rag-triage/trigger-cases.json +31 -0
  249. package/skills/real-estate-property/trigger-cases.json +30 -0
  250. package/skills/refactor-safely/SKILL.md +1 -1
  251. package/skills/refactor-safely/trigger-cases.json +30 -0
  252. package/skills/regression-forecast/trigger-cases.json +31 -0
  253. package/skills/release-checklist/trigger-cases.json +31 -0
  254. package/skills/report-craft/SKILL.md +76 -0
  255. package/skills/report-craft/references/feasibility.md +118 -0
  256. package/skills/report-craft/references/review-checklist.md +51 -0
  257. package/skills/report-craft/references/visuals.md +114 -0
  258. package/skills/report-craft/references/writing.md +80 -0
  259. package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
  260. package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
  261. package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
  262. package/skills/report-craft/scripts/chart_render.py +1222 -0
  263. package/skills/report-craft/scripts/fin_model.py +602 -0
  264. package/skills/report-craft/scripts/project_facts.py +580 -0
  265. package/skills/report-craft/scripts/report_battery.py +349 -0
  266. package/skills/report-craft/trigger-cases.json +15 -0
  267. package/skills/retail-store-ops/trigger-cases.json +30 -0
  268. package/skills/risk-compliance/trigger-cases.json +31 -0
  269. package/skills/scope-creep/SKILL.md +45 -0
  270. package/skills/scope-creep/trigger-cases.json +7 -0
  271. package/skills/sec-auth/trigger-cases.json +31 -0
  272. package/skills/sec-compliance/trigger-cases.json +31 -0
  273. package/skills/securities-chain/trigger-cases.json +31 -0
  274. package/skills/securities-research/trigger-cases.json +31 -0
  275. package/skills/security-audit/trigger-cases.json +31 -0
  276. package/skills/slides-html/SKILL.md +25 -10
  277. package/skills/slides-html/trigger-cases.json +11 -0
  278. package/skills/sql-analysis/SKILL.md +110 -0
  279. package/skills/sql-analysis/trigger-cases.json +34 -0
  280. package/skills/sql-tuning/trigger-cases.json +31 -0
  281. package/skills/statistical-testing/trigger-cases.json +31 -0
  282. package/skills/strategy-planning/trigger-cases.json +30 -0
  283. package/skills/supply-chain/trigger-cases.json +31 -0
  284. package/skills/tax-compliance/trigger-cases.json +30 -0
  285. package/skills/tech-doc/SKILL.md +262 -0
  286. package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
  287. package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
  288. package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
  289. package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
  290. package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
  291. package/skills/tech-doc/scripts/archive_battery.py +860 -0
  292. package/skills/tech-doc/scripts/archive_build.py +599 -0
  293. package/skills/tech-doc/scripts/archive_check.py +1307 -0
  294. package/skills/tech-doc/scripts/archive_spec.py +499 -0
  295. package/skills/tech-doc/scripts/doc_templates.py +624 -0
  296. package/skills/tech-doc/trigger-cases.json +57 -0
  297. package/skills/tech-research/trigger-cases.json +31 -0
  298. package/skills/tender-bidding/trigger-cases.json +31 -0
  299. package/skills/translation-pro/trigger-cases.json +30 -0
  300. package/skills/treasury-mgmt/trigger-cases.json +31 -0
  301. package/skills/trim-cot-leakage/SKILL.md +58 -0
  302. package/skills/trim-cot-leakage/trigger-cases.json +23 -0
  303. package/skills/web-craft/SKILL.md +103 -0
  304. package/skills/web-craft/references/craft-checklist.md +75 -0
  305. package/skills/web-craft/references/design-direction.md +122 -0
  306. package/skills/web-craft/references/h5-mobile.md +100 -0
  307. package/skills/web-craft/references/motion.md +136 -0
  308. package/skills/web-craft/references/recipes.md +74 -0
  309. package/skills/web-craft/templates/wedding-invitation.html +1180 -0
  310. package/skills/web-craft/trigger-cases.json +14 -0
  311. package/skills/web-scraping/trigger-cases.json +34 -0
  312. package/skills/web-search/SKILL.md +11 -10
  313. package/skills/web-search/trigger-cases.json +31 -0
  314. package/skills/weekly-report/SKILL.md +134 -0
  315. package/skills/weekly-report/trigger-cases.json +11 -0
  316. package/skills/write-tests/trigger-cases.json +31 -0
  317. package/skills/xlsx-analysis/SKILL.md +143 -77
  318. package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
  319. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
  320. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
  321. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
  322. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
  323. package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
  324. package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
  325. package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
  326. package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
  327. package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
  328. package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
  329. package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
  330. package/skills/xlsx-analysis/trigger-cases.json +59 -0
  331. package/squads/hc-defense.md +47 -0
  332. package/squads/hc-rdarchive.md +110 -0
  333. package/squads/hc-redteam.md +50 -0
  334. package/squads/hc-weekly.md +125 -0
  335. package/src/account.js +1 -0
  336. package/src/acp.js +1 -0
  337. package/src/advisor.js +1 -1
  338. package/src/agent.js +1 -1
  339. package/src/api.js +1 -1
  340. package/src/archaeology.js +1 -0
  341. package/src/ask-ui.js +1 -0
  342. package/src/assets-import.js +1 -1
  343. package/src/autofix.js +1 -1
  344. package/src/automation-tools.js +1 -0
  345. package/src/automation.js +1 -1
  346. package/src/autorun.js +1 -1
  347. package/src/banner.js +1 -1
  348. package/src/capability-probe.js +1 -0
  349. package/src/checkpoints.js +1 -1
  350. package/src/code-tools.js +1 -0
  351. package/src/codeindex.js +1 -1
  352. package/src/codemode.js +1 -0
  353. package/src/command-bridge.js +1 -0
  354. package/src/command-ui.js +1 -0
  355. package/src/commands.js +1 -1
  356. package/src/compaction.js +1 -1
  357. package/src/computer.js +1 -0
  358. package/src/config.js +1 -1
  359. package/src/consult.js +1 -1
  360. package/src/context-report.js +1 -1
  361. package/src/context-window.js +1 -0
  362. package/src/context.js +1 -1
  363. package/src/crew.js +1 -0
  364. package/src/crush.js +1 -0
  365. package/src/daemon.js +1 -1
  366. package/src/decisions.js +1 -1
  367. package/src/depaudit.js +1 -0
  368. package/src/device.js +1 -1
  369. package/src/diagram-draw.js +1 -0
  370. package/src/diagram-layouts.js +1 -0
  371. package/src/diagram-raster.js +1 -0
  372. package/src/diagram.js +1 -0
  373. package/src/dictation.js +1 -0
  374. package/src/diffview.js +1 -1
  375. package/src/doctor.js +1 -1
  376. package/src/docx-tools.js +1 -0
  377. package/src/errkind.js +1 -0
  378. package/src/evidence.js +1 -0
  379. package/src/executors.js +1 -1
  380. package/src/expert-catalog.js +1 -1
  381. package/src/experts-store.js +1 -1
  382. package/src/experts.js +1 -1
  383. package/src/external-agents.js +1 -0
  384. package/src/forks.js +1 -1
  385. package/src/fs-observe.js +1 -0
  386. package/src/gitflow.js +1 -1
  387. package/src/goalround.js +1 -0
  388. package/src/hooks.js +1 -1
  389. package/src/input.js +1 -1
  390. package/src/insights.js +1 -1
  391. package/src/jobs.js +1 -0
  392. package/src/kbsync.js +1 -1
  393. package/src/lsp.js +1 -0
  394. package/src/mcp-apps.js +1 -0
  395. package/src/mcp.js +1 -1
  396. package/src/memory.js +1 -1
  397. package/src/notify.js +1 -0
  398. package/src/permissions.js +1 -1
  399. package/src/plan-limits.js +1 -0
  400. package/src/policy.js +1 -0
  401. package/src/pptx-tools.js +1 -0
  402. package/src/presence.js +1 -0
  403. package/src/privatedir.js +1 -0
  404. package/src/pyruntime.js +1 -1
  405. package/src/radar.js +1 -0
  406. package/src/render.js +1 -1
  407. package/src/repl.js +1 -1
  408. package/src/report-tools.js +1 -0
  409. package/src/routing.js +1 -0
  410. package/src/sandbox-probe.js +1 -0
  411. package/src/sandbox-win-launcher.mjs +156 -0
  412. package/src/sandbox.js +1 -0
  413. package/src/scan.js +1 -1
  414. package/src/schedule.js +1 -1
  415. package/src/scopecreep.js +1 -0
  416. package/src/sdk.js +1 -0
  417. package/src/selection.js +1 -0
  418. package/src/session-events.js +1 -0
  419. package/src/session.js +1 -1
  420. package/src/sessionindex.js +1 -0
  421. package/src/sessionlog.js +1 -0
  422. package/src/skill-catalog.js +1 -0
  423. package/src/skill-lint.js +1 -0
  424. package/src/skill-optimize.js +1 -0
  425. package/src/skill-scan.js +1 -0
  426. package/src/skills.js +1 -1
  427. package/src/spill.js +1 -0
  428. package/src/squad.js +1 -1
  429. package/src/subagent.js +1 -1
  430. package/src/surface-state.js +1 -0
  431. package/src/tapd.js +1 -0
  432. package/src/terminal-select.js +1 -0
  433. package/src/terminal.js +1 -0
  434. package/src/test-runner.js +1 -0
  435. package/src/todo.js +1 -1
  436. package/src/toolpipe.js +1 -0
  437. package/src/tools.js +1 -1
  438. package/src/toon.js +1 -0
  439. package/src/transcript.js +1 -1
  440. package/src/tui/app.js +1 -1
  441. package/src/tui/command-menu.js +1 -0
  442. package/src/tui/progress.js +1 -0
  443. package/src/tui/scroll.js +1 -0
  444. package/src/tui/welcome.js +1 -0
  445. package/src/tui/workbench.js +1 -0
  446. package/src/ui-spec.js +1 -0
  447. package/src/uitree.js +1 -0
  448. package/src/uninstall.js +1 -1
  449. package/src/vision.js +1 -0
  450. package/src/visual.js +1 -0
  451. package/src/voice-form.js +1 -0
  452. package/src/voice.js +1 -0
  453. package/src/watch.js +1 -1
  454. package/src/web-audit.js +1 -0
  455. package/src/web-cdp.js +1 -0
  456. package/src/web-inspect.js +1 -0
  457. package/src/websearch.js +1 -0
  458. package/src/weekly.js +1 -0
  459. package/src/workflow.js +1 -0
  460. package/src/worklog.js +1 -0
  461. package/src/xlsx-tools.js +1 -0
  462. package/ui/command-picker.js +79 -0
  463. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  464. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  465. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  466. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  467. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  468. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  469. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  470. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  471. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  472. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  473. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  474. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  475. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  476. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  477. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  478. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  479. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  480. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  481. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  482. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  483. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  484. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  485. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  486. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -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";const a=["- 先了解现状再动手:并行使用 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(c,l={}){const p=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}}(c),m=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}(c),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 f="";if(l.persona&&l.persona.trim()?(f+="# 你的角色设定\n"+l.persona.trim()+"\n\n",f+="# 运行方式\n",f+="你现在运行在用户终端里,可通过工具在用户本机的项目里读写文件、搜索、执行 shell 命令来真正完成任务。请在保持上述角色设定的前提下遵循以下准则:\n"):(f+="你是 huicai,一个运行在用户终端里的专业自治编码与自动化智能体。\n",f+="你能通过工具在用户本机的项目里读写文件、搜索、执行 shell 命令,从而真正完成任务,而不仅是给建议。\n\n",f+="# 工作准则\n"),f+=a.join("\n")+"\n\n",f+="# 运行环境\n",f+=`- 工作目录: ${c}\n`,f+=`- 操作系统: ${t.platform()} ${t.release()} (${t.arch()})\n`,f+=`- 日期: ${(new Date).toISOString().slice(0,10)}\n`,p&&(f+=`- Git 分支: ${p.branch}\n`,p.status)){f+=`- 未提交改动:\n${p.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""}}(c);_&&(f+=`- 顶层条目: ${_}\n`);const u=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(u&&(f+="\n# 编辑器上下文(来自 IDE 集成)\n"+u),d&&(f+=`\n# 全局用户说明(~/.huicai/HUICAI.md)\n${d}\n`),m&&(f+=`\n# 项目说明(${m.name})\n${m.content}\n`),!1!==l.repoMap)try{const n=s(c);n&&(f+=`\n# 项目地图(关键文件与主要符号;用 search_code 按概念检索相关实现)\n${n}\n`)}catch{}const h=o(c);if(h&&(f+=`\n# 可用技能(本地 Agent Skills)\n${h}\n`),!1!==l.memory){const n=i(c);n&&(f+=`\n# 长期记忆(此前会话经 memory_write 记录,跨会话生效)\n${n}\n`,f+="(发现新的、值得跨会话记住的稳定事实/偏好/结论时,用 memory_write 记录。)\n")}return l.append&&String(l.append).trim()&&(f+="\n# 附加指示\n"+String(l.append).trim()+"\n"),f}
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{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_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(n){const e=String(n).indexOf("# 当前状态(每轮可能变化)");return e<0?String(n):String(n).slice(0,e)}export function buildSystemPrompt(s,a={}){const c=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}}(s),l=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}(s),p=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 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 n=i(s);n&&(d+=`\n# 长期记忆(此前会话经memory_write记录,跨会话生效)\n${n}\n`,d+="(发现新的、值得跨会话记住的稳定事实/偏好/结论时,用memory_write记录。)\n")}d+=`\n# 当前状态(每轮可能变化)\n- 日期: ${(new Date).toISOString().slice(0,10)}\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""}}(s);if(_&&(d+=`- 顶层条目: ${_}\n`),c&&(d+=`- Git分支: ${c.branch}\n`,c.status)){d+=`- 未提交改动:\n${c.status.split("\n").slice(0,30).map(n=>" "+n).join("\n")}\n`}if(!1!==a.repoMap)try{const n=o(s);n&&(d+=`\n## 项目地图(关键文件与主要符号;用search_code按概念检索相关实现)\n${n}\n`)}catch{}const u=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}();return u&&(d+="\n# 编辑器上下文(来自IDE集成)\n"+u),a.append&&String(a.append).trim()&&(d+="\n# 附加指示\n"+String(a.append).trim()+"\n"),d}export function capabilityCatalog(n,e={}){let t="",r=[];try{r=s(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=a(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&&l(e.config||{})&&(t+=c+"\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}
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";export const DAEMON_LOG_DIR=r.join(n.homedir(),".huicai","daemon");export async function runDaemon({intervalMs:n=6e4,log:s=o=>process.stdout.write(o+"\n"),once:p=!1,spawnImpl:d}={}){const a=c();if(!a&&!d)throw new Error("无法定位 huicai 可执行文件(daemon 需在 CLI 环境运行)");e.mkdirSync(DAEMON_LOG_DIR,{recursive:!0});const m=d||(n=>{const t=e.openSync(r.join(DAEMON_LOG_DIR,n.id+".log"),"a"),i=o(process.execPath,[a,"-p",n.prompt,"--yolo","--plain"],{cwd:n.cwd,detached:!0,stdio:["ignore",t,t],env:process.env});return i.unref(),i.pid}),f=new Map,l=()=>{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(f.get(n.id)===e)continue;let t=!1;try{t=i(n.cron,o)}catch{t=!1}if(!t)continue;f.set(n.id,e);const c=m(n);r++,s(`[${e}] 触发 #${n.id}(pid ${c}):${String(n.prompt).replace(/\s+/g," ").slice(0,60)}`)}return r},u=l();if(p)return{fired:u};s(`huicai 调度守护已启动:每 ${Math.round(n/1e3)}s 检查 ~/.huicai/schedule.json(${t().length} 个任务)· Ctrl-C 退出`);const h=setInterval(l,n);return await new Promise(o=>{process.on("SIGINT",()=>{clearInterval(h),s("\n调度守护已停止。"),o()})}),{fired:u}}
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")}
@@ -0,0 +1 @@
1
+ import t from"node:fs";import e from"node:path";import{BUNDLED_SKILLS_DIR as r}from"./skills.js";import{ensureRuntime as n,execFileCapture as i,runtimeStatus as o,venvPython as s}from"./pyruntime.js";export function engineDir(){return e.join(r,"docx-author","scripts")}export function archiveEngineDir(){return e.join(r,"tech-doc","scripts")}const c="tech技术文档(A4/宋体小四/1.5 倍行距/首行缩进 2 字符,默认)/ gov党政机关公文GB-T 9704(仿宋三号/固定行距/每面 22 行)/ report科技报告·学术(宋体小四/固定 20 磅行距/十进制编号 1、1.1、1.1.1)";function p(r,n,i){const o=String(n||i||"").trim()||i,s=e.resolve(r,o);if(s!==r&&!s.startsWith(r+e.sep))throw new Error("输出路径越出工作目录:"+o);return t.mkdirSync(e.dirname(s),{recursive:!0}),s}async function a(r,c,p,{timeoutMs:a=18e4,config:d={},dir:l=null}={}){o(d).usable||await n({log:()=>{},config:d});const u=s();if(!t.existsSync(u))throw new Error("托管Python未就绪(设置 → 智能 → 立即准备,或先调用一次run_python)");const h=await i(u,["-X","utf8",e.join(l||engineDir(),c),...p],{cwd:r,timeoutMs:a,env:{PYTHONIOENCODING:"utf-8",MPLBACKEND:"Agg"}});if(h.killed)throw new Error("执行超时被终止");return h.output||""}function d(t){const e=String(t||"").match(/\{[\s\S]*\}\s*$/);if(!e)throw new Error("引擎输出异常:"+String(t||"").slice(-400));return JSON.parse(e[0])}export async function inspectDocsForTurn(e,r,n={}){if(!o(n).usable||!t.existsSync(s()))return null;const i=[];for(const o of[...r].slice(0,4))if(t.existsSync(o))try{const t=d(await a(e,"docx_check.py",[o,"--json"],{config:n,timeoutMs:9e4}));for(const e of t.issues||[])"error"===e.level&&i.push({...e,path:o})}catch{}return{issues:i}}export function docTurnCorrection(t,r){const n=t.issues.slice(0,10);return"【文档自检】你本回合生成的文档经体检仍有以下**必修**问题,请修正后重新生成再结束:\n"+n.map(t=>` [${t.kind}] ${e.relative(r,t.path)||t.path}${t.loc?` · ${t.loc}`:""}\n 问题:${t.msg}${t.detail?`\n 说明:${t.detail}`:""}`+(t.fixable?"\n 可自动修:docx_inspect(path, fix=true)":"")).join("\n")+(t.issues.length>n.length?`\n …… 另有 ${t.issues.length-n.length} 条,用docx_inspect看全量。`:"")+"\n\n内容类问题改Markdown草稿后重新create_doc,再用docx_inspect复检确认必修清零。若确认某条是误报,在回复里说明理由——不要在必修未清零时宣布完成。"}export function makeDocxTools({cwd:r,config:n={}}){return[{readOnly:!1,spec:{type:"function",function:{name:"create_doc",description:`生成Word技术文档(.docx):项目建议书、可研报告、立项申请书、研发任务书、实施计划、需求/设计说明书、测试报告、工作/技术总结、验收报告与申请表、评审纪要等。**你只给「哪一节写什么」,排版由引擎负责**——不要用run_python手写python-docx,那是中文字体不生效、首行缩进忽有忽无、表格宽出版心、图片被拉变形的根源。\n引擎自动处理:封面 / 目录域 / 页眉页脚与页码(正文另起一节从第 1 页重新起算)/ 标题分级与自动编号 / 正文首行缩进 2 字符 / 表格列宽按内容算并压进版心(表头加粗+跨页重复)/ 插图等比缩放不变形 + 图表题注自动编号。\n排版档位profile:${c}。\n传template会按该类文档核对必备章节并回报缺哪节。可选:项目建议书 / 可行性研究报告 / 立项申请书 / 项目研发实施计划 / 研发任务书 / 产品开发需求申请 / 项目开发需求申请 / 需求规格说明书 / 概要设计说明书 / 详细设计说明书 / 测试报告 / 项目工作总结报告 / 项目技术总结报告 / 项目验收报告 / 项目验收申请表 / 项目研发评价总结纪要 / 变更申请单。\n出片后**自动跑文档体检**(结构/排版/表格/插图/错别字/标点/AI味/一致性),报告随结果返回。\n配图先用render_diagram(架构/流程/时序等工程图)或render_chart(数据图表)出png再引用。`,parameters:{type:"object",properties:{path:{type:"string",description:"输出路径(相对工作目录,如 智能运维平台研发任务书.docx)"},spec:{type:"object",description:"整份文档的结构",additionalProperties:!0,properties:{profile:{type:"string",description:"tech(默认)/ gov / report,见工具说明"},template:{type:"string",description:"文档类型(如「研发任务书」):据此定档位并核对必备章节"},title:{type:"string",description:"文档标题(有标题才出封面)"},subtitle:{type:"string",description:"副标题,如「项目研发实施计划」"},org:{type:"string",description:"编制单位"},author:{type:"string",description:"编制人"},date:{type:"string",description:"编制日期,默认今天"},doc_no:{type:"string",description:"文号,如YF-2026-014,排在封面右上"},cover_fields:{type:"array",description:'封面信息栏额外行,如 [["项目编号","XM-01"],["密级","内部"]]',items:{type:"array",items:{type:"string"}}},toc:{type:"boolean",description:"是否插目录域,默认true(Word内按F9 更新)"},cover:{type:"boolean",description:"是否出封面,默认true(有title时)"},header:{type:"string",description:"页眉文字"},footer:{type:"string",description:"页脚文字(页码会接在其后)"},page_numbers:{type:"boolean",description:"是否显示「第X页 共Y页」,默认true"},numbering:{type:"string",description:"cn一、(一)1.(1)/ decimal 1、1.1、1.1.1 / none不自动编号"},blocks:{type:"array",description:'逐块内容。每块 {type, ...}:h1|h2|h3|h4{text}(自动编号,文本已带编号则不重复加)/ p{text}(正文,支持 **粗体**)/ bullets{items[]} / ordered{items[]} / table{rows[[..]],caption,note,style:"grid"|"three-line"}(题注排在表上方)/ kv{pairs[[键,值]],caption}(表单式两列表,验收申请表/变更单用)/ figure{image,caption,width_cm,note}(题注排在图下方)/ note{text}(小字注)/ signature{roles[],org}(签字栏)/ pagebreak。\n**正文里引用图表要写「如图 1 所示」「详见表 2」**,编号按出现顺序自动生成;引用了不存在的编号、或图表从未被正文引用,体检都会报出来。',items:{type:"object",additionalProperties:!0}},markdown:{type:"string",description:"替代blocks:直接给Markdown全文(#/##/- /1./|表格|/![题注](图)/--- 分页;表题写在表格上一行「表:表题」)。交叉引用:题注末尾加 {#fig:arch} / {#tbl:invest},正文写「如@fig:arch所示」,成稿时换成真实编号(图 3-1),不用自己数。宽图横排插入:![图题](图){#fig:arch .landscape}(单独一个横向页)"}}},spec_path:{type:"string",description:"可选:从JSON文件读spec(长文档用,省上下文)"},markdown_path:{type:"string",description:"可选:从Markdown文件读全文(长文档推荐,先write_file落草稿再转)"}},required:["path"]}}},run:async i=>{let o=i?.spec;if("string"==typeof o)try{o=JSON.parse(o)}catch{return"spec不是合法JSON:长文档请先write_file落草稿,再用markdown_path传入。"}const s=i?.spec_path||o?.spec_path,c=i?.markdown_path||o?.markdown_path;if(s&&!o?.blocks&&!o?.markdown){const n=e.resolve(r,String(s));if(!t.existsSync(n))return`spec文件不存在:${s}`;o={...JSON.parse(t.readFileSync(n,"utf8")),...o||{}}}if(c){const n=e.resolve(r,String(c));if(!t.existsSync(n))return`Markdown草稿不存在:${c}`;o={...o||{},markdown:t.readFileSync(n,"utf8"),_md_dir:e.dirname(n)}}if(o&&(delete o.markdown_path,delete o.spec_path),!o)return"create_doc需要spec(或spec_path / markdown_path):至少给blocks或markdown。";if(!(Array.isArray(o.blocks)&&o.blocks.length||String(o.markdown||"").trim()))return"create_doc没拿到任何正文:spec里既没有blocks也没有markdown。长文档请把草稿落盘后用顶层参数markdown_path传入(与path、spec并列,不是写在spec里面)。";const l=p(r,i?.path,"技术文档.docx"),u=e.join(e.dirname(l),`.doc-spec-${Date.now()}.json`);try{t.writeFileSync(u,JSON.stringify(o),"utf8");const i=d(await a(r,"docx_build.py",[u,l],{config:n}));if(i.error)return`生成失败:${i.error}\n(块类型与字段见create_doc参数说明;一块一个type,别混着填)`;const s=e.relative(r,i.path)||e.basename(i.path),c=i.check?.stats,p=[`已生成 ${s}`+(c?`(${c.headings} 标题 / ${c.tables} 表 / ${c.figures} 图 / ${c.chars} 字 / 约 ${c.pages_est} 页)`:"")];return i.warnings?.length&&p.push("【建版提示】\n"+i.warnings.map(t=>" · "+t).join("\n")),i.labels?.length&&p.push("【图表编号】"+i.labels.map(t=>`${t[1]} ${t[2]}`).join(";")+"\n 正文引用请与之一致。"),p.push(i.report||""),i.check?.counts?.error&&p.push("必修项未清零:按上面的提示改spec重新生成,或用docx_inspect(path, fix=true) 修排版类问题;内容类(缺章节、缺数据、套话)必须你自己改。"),p.push("目录是Word域,交付前请提醒用户在Word中按F9(或右键→更新域)刷新目录页码。"),p.filter(Boolean).join("\n")}finally{try{t.unlinkSync(u)}catch{}}}},{readOnly:!1,spec:{type:"function",function:{name:"create_project_docs",description:"按研发项目归档规范,从一张「项目档案卡」一次生成**整套**项目研发文档骨架(约 50 份):1-立项文档(项目建议书/可研报告/立项申请书/立项决议)、产品与项目开发需求申请、5-研发任务书、6-项目研发实施计划、7-进度表(计划 + 四个季度实际)、8-年度研发支出预算表(研发费用归集口径,12 个月分摊)、9-10 验收(验收申请表/验收报告/工作总结/技术总结/研发评价纪要)、5 个阶段评审报告与会议签到表、12 期研发进度报告、12 期月度验收报告、档案清册。\n**为什么用它而不是一份份写**:这几十份文档的封面、编号、章节骨架、签批栏全是同几件事的机械展开。人手抄几十遍,抄出来的就是真实归档里那些毛病——项目名称三种写法、项目编号两个值、联系人栏填成电话、周期性报告缺期。本工具让**所有文档的封面字段都取自同一张卡**,跨文档一致性因此天然为真。\n生成的是骨架:章节标题、必备表格(考核指标表/人员表/指标验收对照表)、签批栏都就位,正文内容通过card.章节内容 逐节注入(写多少注入多少,没注入的留空待填)。12 期进度报告的「项目进度情况」会**按里程碑自动推算**当期完成情况,不是空模板。\n生成后用docx_inspect(目录) 做档案体检,必修清零再交付。",parameters:{type:"object",properties:{path:{type:"string",description:"输出目录(会在其下按项目名建子目录)"},card:{type:"object",additionalProperties:!0,description:"项目档案卡。必填:项目名称、项目编号、项目负责人、建设单位、部门、起止时间。选填:项目联系人(默认同负责人)、联系电话、编制人、年度、预算、考核指标 [[指标,要求值,测试方法/判定依据],…]、人员 [[姓名,单位,职称/职务,分工],…]、里程碑 [[阶段,任务,处理人,工时,开始,结束,交付物],…]、预算科目 [[科目,全年金额],…]、章节内容 {文档类型: {章节名: 正文}}(有多少写多少)"},card_path:{type:"string",description:"可选:从JSON文件读档案卡(内容长时用)"},only:{type:"array",items:{type:"string"},description:'可选:只生成这几类(如 ["研发任务书","立项申请书"]),不给则生成全套'}},required:["path"]}}},run:async i=>{let o=i?.card;if(!o&&i?.card_path&&(o=JSON.parse(t.readFileSync(e.resolve(r,String(i.card_path)),"utf8"))),!o)return"create_project_docs需要card(或card_path):至少给项目名称、项目编号、项目负责人、建设单位、部门、起止时间。";const s=p(r,i?.path||".","."),c=e.join(e.dirname(s),`.card-${Date.now()}.json`);try{t.writeFileSync(c,JSON.stringify(o),"utf8");const p=[c,s];Array.isArray(i?.only)&&i.only.length&&p.push("--only="+i.only.join(","));const l=d(await a(r,"archive_build.py",p,{config:n,dir:archiveEngineDir(),timeoutMs:6e5}));if(l.error)return`生成失败:${l.error}\n(档案卡必填项见create_project_docs参数说明)`;const u=[`已在 ${e.relative(r,l.root)||e.basename(l.root)} 生成 ${l.files} 份归档件`];l.warnings?.length&&u.push("【生成提示】\n"+l.warnings.map(t=>" · "+t).join("\n"));const h=[...new Set((l.made||[]).map(t=>e.dirname(t)).map(t=>"."===t?"(根目录)":t))];return u.push("目录:"+h.join("、")),u.push("下一步:逐份补正文(card.章节内容 可批量注入),然后docx_inspect(目录) 做档案体检,必修清零再交付。"),u.join("\n")}finally{try{t.unlinkSync(c)}catch{}}}},{readOnly:!1,spec:{type:"function",function:{name:"docx_inspect",description:"对**任意** Word文档做全面体检,逐条带位置与具体数字。生成文档后**必须**跑一遍(create_doc会自动跑;自己写python-docx生成的更要跑);用户拿来一份别人写的文档问「排版有没有问题 / 帮我检查一下」也用它。\n查七类:① 结构——标题跳级、空章节、编号断号、缺目录域/页码;② 排版——中文字体未生效、正文字号不统一、首行缩进忽有忽无或用空格假缩进、行距混杂、正文误居中、连续空段;③ 表格——超出版心(打印被裁)、列数不齐、表头未加粗、长表无跨页重复标题行、空格过多、无框线;④ 插图——图片损坏、被拉变形、分辨率过低发虚、超出版心、浮动错位;⑤ 图表题注——缺题注、编号断号、正文引用了不存在的图表、图表从未被正文引用;⑥ 文字——错别字、易混词、中英标点混用、括号引号不配对、段末缺标点、超长句、超长段;⑦ AI味与一致性——套话与黑话密度、空洞副词、段首雷同、段落/章节篇幅异常均匀、通篇缺数字锚点、术语多种写法、关键数字前后矛盾。\n也接受 .md/.txt:只跑文字与AI味那部分——**草稿阶段先查一遍最划算**,比出片后返工便宜得多。\n**path给一个目录时自动转成「档案体检」**,查十二类单份文档看不见的问题:① 齐不齐——必备件、12 期周期性报告、5 个阶段评审报告与签到表是否配对;② 对不对得上——项目名称/编号/负责人/电话/建设单位/起止时间在几十份文档里是否处处一致(能分辨「被截断」与「粒度不同」);③ 干不干净——模板的「提示:」块、「注:由项目单位如实填写…下同」这类给填表人的批注、XX占位符删没删;④ 填没填串——「项目联系人」栏填了电话这类;⑤ 指标勾稽——立项考核指标在验收与总结里是否逐条有对应实测值;⑥ **周期性文档雷同度**——12 期月报份数对,但正文相似度 98%,等于同一份改日期交了十二次;⑦ **选择项一个都没勾**——验收报告的三选一结论 15 个括号全空、专家没签字;⑧ **验收时写「进行中/预计完成」**——结论只有达标与不达标两档,不能把判断推给后面的人;⑨ 时间线自洽(期号vs落款月份、五阶段评审顺序)与「该有图却一张图都没有」;⑩ 表格件的语义自洽——进度 100% 却无实际起止、计划表里填了进度、预算表全零;⑪ **项目名全档一致**——分得清简称(「…系统研发」「…系统」,正常)、错字(「数字权益**开发**系统」实为「开放」,真实归档 25 处贯穿 20 份文件)、串项目(签到表抬头写着上一个项目的名字,两个真实项目各抓到一处);⑬ **跨文档样板段落**——同一段话逐字出现在多种用途不同的文档里(真实归档最严重的一段 449 字,横跨 9 种类型 20 份文件:项目建议书、进度报告、验收总结开同一个头);⑫ 模板写死的字数上限(「项目立项背景(500字内)」实际写了 4000+ 字)、模板点名的必备要素(立项文件号/合同号/起止时间)、签到表有表头没人名、选择框一个都没勾。这一层恰恰是验收与审计现场最容易被当场挑出来的。\nfix=true:自动修可确定性修的(补中文字体、统一字号、真缩进替换假缩进、去多余空段与行尾空格、半角标点转全角、表头加粗与跨页重复、表格缩回版心、图片恢复原比例并缩回版心、题注编号重排),**另存为「-已校正.docx」不覆盖原件**并回报复检结果;改内容才能解决的(缺章节、缺数据、套话太多、章节失衡)会如实说需要人改——不假装修好了。",parameters:{type:"object",properties:{path:{type:"string",description:"要体检的 .docx(或 .md/.txt草稿)路径;**给目录则做整套项目文档的档案体检**"},fix:{type:"boolean",description:"是否顺手自动修复(默认false,只体检)"},inplace:{type:"boolean",description:"fix时直接改原文件(默认false,另存 -已校正.docx)"},strict:{type:"boolean",description:"严格模式:整段加粗、数值区间连字符等也报"},profile:{type:"string",description:`按哪档规范比对:auto(默认,自动判定)/ ${c}`},deep:{type:"boolean",description:"档案体检时顺带对每份docx跑单文档排版体检(慢,默认false)"}},required:["path"]}}},run:async i=>{const o=e.resolve(r,String(i?.path||""));if(!t.existsSync(o))return`文件不存在:${i?.path}`;if(t.statSync(o).isDirectory()){const t=[o];i?.deep&&t.push("--deep");return(await a(r,"archive_check.py",t,{config:n,dir:archiveEngineDir(),timeoutMs:6e5})).trim()||"(档案体检无输出,请检查托管Python是否就绪)"}if(/\.(md|markdown|txt)$/i.test(o)){const t=[o];i?.strict&&t.push("--strict");return((await a(r,"zh_prose.py",t,{config:n})).trim()||"(无输出)")+"\n(草稿只查文字与AI味;排版类问题要等转成 .docx后再跑一次docx_inspect。)"}if(!/\.docx$/i.test(o))return"只支持 .docx(老的 .doc请先在Word里另存为docx),或 .md/.txt草稿";const s=[o];i?.fix&&s.push("--fix"),i?.inplace&&s.push("--inplace"),i?.strict&&s.push("--strict"),i?.profile&&"auto"!==i.profile&&s.push(`--profile=${i.profile}`);return(await a(r,"docx_check.py",s,{config:n})).trim()||"(体检无输出,请检查托管Python是否就绪)"}}]}
package/src/errkind.js ADDED
@@ -0,0 +1 @@
1
+ export const KIND_AUTH="auth";export const KIND_QUOTA="quota";export const KIND_USAGE="usage";const t=new Set(["authentication_error","invalid_api_key"]),r=/(登录已失效|令牌校验失败|缺少访问令牌|令牌(?:已)?(?:失效|无效|过期|不存在)|尚未登录|未登录|invalid[_ ]api[_ ]key|authentication_error|unauthorized|HTTP\s*401)/i,n=/(余额不足|额度不足|请充值|insufficient_quota)/i,i=/(usage_limit_exceeded|用量上限|用量已达|已达当前会员等级)/i;function e(t){if(null==t)return{status:0,type:"",text:""};if("string"==typeof t)return{status:0,type:"",text:t};const r=String(t.message||t.error||t.text||"");return{status:Number(t.status)||0,type:String(t.type||""),text:r}}export function isAuthError(n){const{status:i,type:o,text:u}=e(n);return 401===i||(!!t.has(o)||r.test(u))}export function isQuotaError(t){const{status:r,type:i,text:o}=e(t);return 402===r||("insufficient_quota"===i||n.test(o))}export function isUsageLimitError(t){const{type:r,text:n}=e(t);return"usage_limit_exceeded"===r||i.test(n)}export function classifyError(t){return isUsageLimitError(t)?"usage":isQuotaError(t)?"quota":isAuthError(t)?"auth":""}export function isBillingError(t){return isUsageLimitError(t)||isQuotaError(t)}function o(t){return String(t||"").replace(/\/+$/,"")}export function consoleUrl(t){const r=o(t);return r?`${r}/console/dashboard`:""}export function billingUrl(t){const r=o(t);return r?`${r}/console/billing`:""}export function pricingUrl(t){const r=o(t);return r?`${r}/pricing`:""}export function billingActionForKind(t,r){if("quota"!==t&&"usage"!==t)return null;return{kind:t,label:"quota"===t?"去充值":"升级会员",url:"quota"===t?billingUrl(r):pricingUrl(r),hint:"quota"===t?"账户积分已用尽:可在控制台「充值中心」充值或兑换积分。":"已达当前会员等级的Token用量窗口上限:可等待窗口逐步释放,或升级会员提高上限。"}}export function billingAction(t,r){return billingActionForKind(classifyError(t),r)}