@huicai/cli 2026.814.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 (485) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +137 -8
  3. package/bin/huicai.js +146 -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 -1
  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 -1
  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 -1
  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 -1
  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 -1
  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 -1
  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/visual.js +1 -1
  450. package/src/voice-form.js +1 -0
  451. package/src/voice.js +1 -0
  452. package/src/watch.js +1 -1
  453. package/src/web-audit.js +1 -0
  454. package/src/web-cdp.js +1 -0
  455. package/src/web-inspect.js +1 -0
  456. package/src/websearch.js +1 -0
  457. package/src/weekly.js +1 -0
  458. package/src/workflow.js +1 -0
  459. package/src/worklog.js +1 -0
  460. package/src/xlsx-tools.js +1 -0
  461. package/ui/command-picker.js +79 -0
  462. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  463. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  464. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  465. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  466. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  467. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  468. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  469. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  470. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  471. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  472. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  473. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  474. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  475. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  476. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  477. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  478. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  479. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  480. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  481. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  482. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  483. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  484. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  485. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
@@ -1,161 +1,21 @@
1
- # Word 文档生成助手(经 run_python 执行)。核心入口 md_to_docx():
2
- # Markdown(#/##/### 标题、-/1. 列表、| 表格、**粗体**、普通段落)→ 成品 docx,
3
- # 自动带:封面(题目/作者/日期)、目录域(Word 内 F9 更新)、页脚"第 X 页 共 Y 页"、
4
- # 中文字体(正文宋体/标题黑体,经 w:eastAsia 正确设置)。
5
- # 直接执行本文件 = 自测:产出 docx_doc_check.docx 并重读校验。
6
- import datetime
7
- import re
1
+ # 兼容垫片:老入口 md_to_docx() / verify() 保留,实现已迁到 docx_build.py + docx_check.py。
2
+ #
3
+ # 为什么保留而不是直接删:外部脚本与历史会话里可能还 read_skill_file 到这个文件名。
4
+ # 新代码请直接用 create_doc 工具,或 import docx_build。
5
+ import os
6
+ import sys
8
7
 
9
- from docx import Document
10
- from docx.enum.text import WD_ALIGN_PARAGRAPH
11
- from docx.oxml.ns import qn
12
- from docx.oxml import OxmlElement
13
- from docx.shared import Pt
14
-
15
-
16
- def _set_ea(style, west, east, size=None, bold=None):
17
- """中文字体必须同时设 w:eastAsia,只设 font.name 中文不生效。"""
18
- style.font.name = west
19
- style.element.rPr.rFonts.set(qn('w:eastAsia'), east)
20
- if size is not None:
21
- style.font.size = Pt(size)
22
- if bold is not None:
23
- style.font.bold = bold
24
-
25
-
26
- def _field(run, instr, placeholder=''):
27
- """在 run 上插入 Word 域(TOC/PAGE/NUMPAGES…)。"""
28
- begin = OxmlElement('w:fldChar')
29
- begin.set(qn('w:fldCharType'), 'begin')
30
- it = OxmlElement('w:instrText')
31
- it.set(qn('xml:space'), 'preserve')
32
- it.text = instr
33
- sep = OxmlElement('w:fldChar')
34
- sep.set(qn('w:fldCharType'), 'separate')
35
- t = OxmlElement('w:t')
36
- t.text = placeholder
37
- end = OxmlElement('w:fldChar')
38
- end.set(qn('w:fldCharType'), 'end')
39
- for el in (begin, it, sep, t, end):
40
- run._r.append(el)
41
-
42
-
43
- def new_doc():
44
- """建空文档并配好中英文字体样式。"""
45
- doc = Document()
46
- _set_ea(doc.styles['Normal'], 'Calibri', '宋体', size=11)
47
- for lvl, size in (('Heading 1', 16), ('Heading 2', 14), ('Heading 3', 12)):
48
- _set_ea(doc.styles[lvl], 'Calibri', '黑体', size=size, bold=True)
49
- return doc
50
-
51
-
52
- def add_cover(doc, title, author='', subtitle=''):
53
- for _ in range(6):
54
- doc.add_paragraph()
55
- p = doc.add_paragraph()
56
- p.alignment = WD_ALIGN_PARAGRAPH.CENTER
57
- r = p.add_run(title)
58
- r.font.size = Pt(28)
59
- r.font.bold = True
60
- r.font.name = 'Calibri'
61
- r._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
62
- if subtitle:
63
- p2 = doc.add_paragraph()
64
- p2.alignment = WD_ALIGN_PARAGRAPH.CENTER
65
- p2.add_run(subtitle).font.size = Pt(14)
66
- meta = doc.add_paragraph()
67
- meta.alignment = WD_ALIGN_PARAGRAPH.CENTER
68
- meta.add_run(f"{author + ' ' if author else ''}{datetime.date.today().strftime('%Y年%m月%d日')}").font.size = Pt(12)
69
- doc.add_page_break()
70
-
71
-
72
- def add_toc(doc):
73
- doc.add_paragraph('目录', style='Heading 1')
74
- _field(doc.add_paragraph().add_run(), 'TOC \\o "1-3" \\h \\z \\u', '(在 Word 中右键→更新域,或按 F9 生成目录)')
75
- doc.add_page_break()
76
-
77
-
78
- def add_page_footer(doc):
79
- p = doc.sections[0].footer.paragraphs[0]
80
- p.alignment = WD_ALIGN_PARAGRAPH.CENTER
81
- p.add_run('第 ')
82
- _field(p.add_run(), 'PAGE', '1')
83
- p.add_run(' 页 共 ')
84
- _field(p.add_run(), 'NUMPAGES', '1')
85
- p.add_run(' 页')
86
-
87
-
88
- def _add_runs(par, text):
89
- """按 **粗体** 拆 run。"""
90
- for i, seg in enumerate(re.split(r'\*\*(.+?)\*\*', text)):
91
- if seg:
92
- par.add_run(seg).bold = (i % 2 == 1)
93
-
94
-
95
- def _flush_table(doc, rows):
96
- cells = [[c.strip() for c in r.strip().strip('|').split('|')] for r in rows]
97
- cells = [r for r in cells if not all(re.fullmatch(r':?-{2,}:?', c or '---') for c in r)] # 去分隔行
98
- if not cells:
99
- return
100
- tb = doc.add_table(rows=len(cells), cols=len(cells[0]))
101
- tb.style = 'Table Grid'
102
- for i, row in enumerate(cells):
103
- for j, val in enumerate(row[:len(tb.columns)]):
104
- par = tb.cell(i, j).paragraphs[0]
105
- _add_runs(par, val)
106
- if i == 0:
107
- for r in par.runs:
108
- r.bold = True
109
-
110
-
111
- def md_to_docx(md_path, out_path, title='', author='', subtitle='', toc=True):
112
- """Markdown → Word。返回 out_path。"""
113
- doc = new_doc()
114
- add_page_footer(doc)
115
- if title:
116
- add_cover(doc, title, author, subtitle)
117
- if toc:
118
- add_toc(doc)
119
- lines = open(md_path, encoding='utf-8').read().splitlines()
120
- table = []
121
- for line in lines + ['']: # 末尾哨兵,冲刷表格
122
- s = line.rstrip()
123
- if s.lstrip().startswith('|'):
124
- table.append(s)
125
- continue
126
- if table:
127
- _flush_table(doc, table)
128
- table = []
129
- if not s.strip():
130
- continue
131
- m = re.match(r'^(#{1,3})\s+(.*)', s)
132
- if m:
133
- doc.add_paragraph(m.group(2).strip(), style=f'Heading {len(m.group(1))}')
134
- elif re.match(r'^\s*-\s+', s):
135
- _add_runs(doc.add_paragraph(style='List Bullet'), re.sub(r'^\s*-\s+', '', s))
136
- elif re.match(r'^\s*\d+[.、]\s*', s):
137
- _add_runs(doc.add_paragraph(style='List Number'), re.sub(r'^\s*\d+[.、]\s*', '', s))
138
- else:
139
- _add_runs(doc.add_paragraph(), s)
140
- doc.save(out_path)
141
- return out_path
8
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
9
+ from docx_build import DocBuilder, build, md_to_blocks, md_to_docx # noqa: F401,E402
142
10
 
143
11
 
144
12
  def verify(path):
145
- """重读校验(验收步骤用):打印标题树与段落/表格数。"""
146
- doc = Document(path)
147
- heads = [(p.style.name, p.text) for p in doc.paragraphs if p.style.name.startswith('Heading')]
148
- print(f'[verify] 段落 {len(doc.paragraphs)} · 表格 {len(doc.tables)} · 标题 {len(heads)}')
149
- for style, text in heads:
150
- print(' ' * (int(style[-1]) - 1) + f'- [{style}] {text}')
151
- return heads
13
+ """重读校验(老接口)。新代码用 docx_check.inspect() —— 它查的远不止标题树。"""
14
+ import docx_check
15
+ res = docx_check.inspect(path)
16
+ print(docx_check.report(res))
17
+ return res
152
18
 
153
19
 
154
20
  if __name__ == '__main__':
155
- md = 'docx_doc_check.md'
156
- open(md, 'w', encoding='utf-8').write(
157
- '# 一、背景\n项目**按期**推进。\n\n## 1.1 目标\n- 目标甲\n- 目标乙\n\n'
158
- '# 二、数据\n| 地区 | 销售额 |\n|---|---|\n| 华东 | 1200 |\n| 华南 | 950 |\n\n1. 结论一\n2. 结论二\n')
159
- p = md_to_docx(md, 'docx_doc_check.docx', title='自测报告', author='Work Twin', subtitle='docx_doc.py 冒烟')
160
- verify(p)
161
- print('OK', p)
21
+ print(__doc__)
@@ -0,0 +1,490 @@
1
+ # Word 技术文档排版度量与规范内核(docx_build 建版 / docx_check 体检 共用同一套度量)。
2
+ #
3
+ # 为什么必须共用:建版时算「这张表放不放得下版心」和体检时算「这张表有没有超出版心」,
4
+ # 只要用两套度量,就一定会出现「生成端说没事、体检端说超宽」的自相矛盾。
5
+ # 一份度量、两个方向用——这是本模块存在的唯一理由(与 pptx_layout 同一条纪律)。
6
+ #
7
+ # 度量口径(Word 渲染,中文宋体/黑体 + 西文 Times New Roman):
8
+ # · 字宽按 em 表逐字累加(中日韩全角 1.0em,Times 数字 0.5em,小写 0.50em…);
9
+ # · 单倍行高 1.32×字号(SimSun ascent+descent≈1.32em);
10
+ # · 版心宽 = 纸宽 − 左右页边距,**所有「会不会超宽」的判断都以它为准**。
11
+ # 估算不可能与 Word 逐像素一致,所以留了 TOL 容差,且报告一律带出
12
+ # 「估算值 vs 可用值」的具体数字,让人能自己判断,而不是只丢一个结论。
13
+
14
+ import re
15
+
16
+ # ── 单位 ───────────────────────────────────────────────────────────────────
17
+ CM_PT = 28.3464567 # 1 厘米 = 28.35 磅
18
+ PT_CM = 1.0 / CM_PT
19
+ EMU_PT = 12700 # 1 磅 = 12700 EMU
20
+ EMU_CM = 360000 # 1 厘米 = 360000 EMU
21
+ PT_IN = 72.0
22
+
23
+ LINE_H = 1.32 # 单倍行距 ≈ 1.32 × 字号(宋体 ascent 1060 + descent 380 / 1000 ≈ 1.44 排版实测取 1.32)
24
+ WRAP_SAFETY = 0.98 # 折行留 2% 余量:字宽估窄一点就会少算一行,少算一行就是「说放得下、实际挤出去」
25
+ TOL = 1.02 # 超限容差:估算超出可用空间 2% 以内不报(度量误差带)
26
+
27
+ # ── 中文字号 → 磅 ──────────────────────────────────────────────────────────
28
+ SIZE_PT = {
29
+ '初号': 42, '小初': 36, '一号': 26, '小一': 24, '二号': 22, '小二': 18,
30
+ '三号': 16, '小三': 15, '四号': 14, '小四': 12, '五号': 10.5, '小五': 9,
31
+ '六号': 7.5, '小六': 6.5, '七号': 5.5, '八号': 5,
32
+ }
33
+ PT_SIZE = {v: k for k, v in SIZE_PT.items()}
34
+
35
+
36
+ def pt_of(size):
37
+ """'小四' / 12 / '12' → 12.0 磅。"""
38
+ if isinstance(size, str):
39
+ s = size.strip()
40
+ if s in SIZE_PT:
41
+ return float(SIZE_PT[s])
42
+ try:
43
+ return float(s)
44
+ except ValueError:
45
+ return 12.0
46
+ return float(size)
47
+
48
+
49
+ def size_name(pt):
50
+ """12.0 → '小四';非标准字号返回 '12pt'(体检报告里要说人话)。"""
51
+ for v, n in PT_SIZE.items():
52
+ if abs(v - pt) < 0.26:
53
+ return n
54
+ return f'{pt:g}pt'
55
+
56
+
57
+ # ── 字宽表(em)────────────────────────────────────────────────────────────
58
+ # 取值不是拍脑袋:中文按全角 1.0em(宋体/黑体/仿宋均为等宽全角,这一条是确定的);
59
+ # 西文按 Times New Roman(Word 中文文档的默认西文字体,unitsPerEm 2048)实测 advance /2048,
60
+ # 并统一向上取一档——**宁可估宽,不可估窄**:估宽了最多是提前折一行留白多一点,
61
+ # 估窄了就是「说这列放得下、实际把表挤出版心」,那才是要命的。
62
+ W_CJK = 1.0
63
+ W_DIGIT = 0.52 # Times 数字 1024/2048 = 0.500
64
+ W_UPPER = 0.73 # Times 'A' 0.72 / 'M' 0.89 / 'I' 0.33,取常见上沿
65
+ W_LOWER = 0.50 # Times 'a' 0.44 / 'm' 0.78 / 'i' 0.28
66
+ W_SPACE = 0.27 # Times space 512/2048 = 0.250
67
+ W_NARROW = 0.28
68
+ W_WIDE = 0.88
69
+
70
+ NARROW = set(".,;:!|'`iIljtf()[]{}-·")
71
+ WIDE = set("@%&WMmw#—…")
72
+
73
+
74
+ def _full_width(cp):
75
+ """全角判定:CJK 汉字、假名、谚文、全角标点、CJK 兼容形。"""
76
+ return (
77
+ 0x1100 <= cp <= 0x115F or 0x2E80 <= cp <= 0xA4CF or 0xAC00 <= cp <= 0xD7A3
78
+ or 0xF900 <= cp <= 0xFAFF or 0xFE10 <= cp <= 0xFE6F or 0xFF00 <= cp <= 0xFF60
79
+ or 0xFFE0 <= cp <= 0xFFE6 or 0x3000 <= cp <= 0x303F
80
+ )
81
+
82
+
83
+ def char_em(ch):
84
+ """单字符宽度(em)。"""
85
+ cp = ord(ch)
86
+ if ch == ' ':
87
+ return W_SPACE
88
+ if ch == '\t':
89
+ return W_SPACE * 4
90
+ if _full_width(cp):
91
+ return W_CJK
92
+ if cp > 0xFFFF:
93
+ return 1.15
94
+ if ch in NARROW:
95
+ return W_NARROW
96
+ if ch in WIDE:
97
+ return W_WIDE
98
+ if '0' <= ch <= '9':
99
+ return W_DIGIT
100
+ if 'A' <= ch <= 'Z':
101
+ return W_UPPER
102
+ if 'a' <= ch <= 'z':
103
+ return W_LOWER
104
+ if cp < 0x2E80:
105
+ return W_LOWER
106
+ return W_CJK
107
+
108
+
109
+ def measure(text, size, bold=False):
110
+ """估算一段文本的渲染宽度(磅)。size 可传 '小四' 或 12。"""
111
+ if not text:
112
+ return 0.0
113
+ pt = pt_of(size)
114
+ w = sum(char_em(ch) for ch in str(text)) * pt
115
+ return w * (1.06 if bold else 1.0) # 加粗按 6% 加宽(宋体加粗是合成粗体,会变宽)
116
+
117
+
118
+ def wrap_count(text, size, width_pt, first_indent_pt=0.0):
119
+ """这段文字在给定宽度里要占几行(首行可另设缩进)。空文本算 1 行——Word 里空段也占一行。"""
120
+ s = str(text or '')
121
+ if not s.strip():
122
+ return 1
123
+ pt = pt_of(size)
124
+ avail_first = max(width_pt - first_indent_pt, pt)
125
+ avail = max(width_pt, pt)
126
+ lines, cur, limit = 1, 0.0, avail_first * WRAP_SAFETY
127
+ for ch in s:
128
+ if ch == '\n':
129
+ lines += 1
130
+ cur, limit = 0.0, avail * WRAP_SAFETY
131
+ continue
132
+ w = char_em(ch) * pt
133
+ if cur + w > limit:
134
+ lines += 1
135
+ cur, limit = w, avail * WRAP_SAFETY
136
+ else:
137
+ cur += w
138
+ return lines
139
+
140
+
141
+ # ── 排版规范档位 ───────────────────────────────────────────────────────────
142
+ # 三档规范都是「有出处的」,不是审美偏好:
143
+ # tech —— 企业技术/项目管理文档通行做法(A4、左右 3.17cm、正文宋体小四、1.5 倍行距、首行缩进 2 字符);
144
+ # gov —— GB/T 9704《党政机关公文格式》:正文仿宋_GB2312 三号,每面 22 行、每行 28 字,
145
+ # 一级「一、」黑体三号、二级「(一)」楷体三号、三级「1.」仿宋三号加粗;
146
+ # report —— 科技报告/学术稿:小四宋体、固定 20 磅行距、十进制编号 1 / 1.1 / 1.1.1。
147
+ # 改这里=改全局排版,建版与体检同时跟着变,不会各改各的。
148
+
149
+ _BASE = {
150
+ 'page': 'A4',
151
+ 'page_w_cm': 21.0,
152
+ 'page_h_cm': 29.7,
153
+ 'margin_cm': {'top': 2.54, 'bottom': 2.54, 'left': 3.17, 'right': 3.17},
154
+ 'body': {'east': '宋体', 'west': 'Times New Roman', 'size': '小四',
155
+ 'line_rule': 'multiple', 'line': 1.5, 'first_indent_chars': 2,
156
+ 'space_before': 0, 'space_after': 0, 'align': 'justify'},
157
+ 'headings': [
158
+ {'east': '黑体', 'west': 'Times New Roman', 'size': '三号', 'bold': False,
159
+ 'align': 'center', 'space_before': 18, 'space_after': 12, 'line': 1.5},
160
+ {'east': '黑体', 'west': 'Times New Roman', 'size': '四号', 'bold': False,
161
+ 'align': 'left', 'space_before': 12, 'space_after': 6, 'line': 1.5},
162
+ {'east': '黑体', 'west': 'Times New Roman', 'size': '小四', 'bold': False,
163
+ 'align': 'left', 'space_before': 6, 'space_after': 6, 'line': 1.5},
164
+ {'east': '楷体', 'west': 'Times New Roman', 'size': '小四', 'bold': True,
165
+ 'align': 'left', 'space_before': 6, 'space_after': 0, 'line': 1.5},
166
+ ],
167
+ 'caption': {'east': '黑体', 'west': 'Times New Roman', 'size': '五号',
168
+ 'align': 'center', 'line': 1.0},
169
+ 'table': {'east': '宋体', 'west': 'Times New Roman', 'size': '五号',
170
+ 'head_bold': True, 'align': 'center', 'line': 1.0, 'style': '三线表'},
171
+ 'numbering': 'cn', # cn = 一、/(一)/1./(1);decimal = 1/1.1/1.1.1;none = 不编号
172
+ 'fig_prefix': '图',
173
+ 'tab_prefix': '表',
174
+ 'caption_by_chapter': False, # True = 图 1-1(按章编号),False = 图 1(全文连续)
175
+ 'toc_levels': 3,
176
+ 'max_image_cm': None, # None = 版心宽
177
+ 'name': 'tech',
178
+ 'label': '技术文档(A4/宋体小四/1.5 倍行距/首行缩进 2 字符)',
179
+ }
180
+
181
+
182
+ def _merge(base, over):
183
+ out = {}
184
+ for k, v in base.items():
185
+ if isinstance(v, dict) and isinstance(over.get(k), dict):
186
+ out[k] = _merge(v, over[k])
187
+ elif isinstance(v, list) and isinstance(over.get(k), list):
188
+ out[k] = [_merge(a, b) if isinstance(a, dict) and isinstance(b, dict) else b
189
+ for a, b in zip(v, over[k])] + over[k][len(v):]
190
+ else:
191
+ out[k] = over[k] if k in over else v
192
+ for k, v in over.items():
193
+ if k not in out:
194
+ out[k] = v
195
+ return out
196
+
197
+
198
+ _OVERRIDES = {
199
+ 'tech': {},
200
+ 'gov': {
201
+ 'name': 'gov',
202
+ 'label': '党政机关公文 GB/T 9704(仿宋三号/固定 28.8 磅行距/每面 22 行)',
203
+ 'margin_cm': {'top': 3.7, 'bottom': 3.5, 'left': 2.8, 'right': 2.6},
204
+ 'body': {'east': '仿宋_GB2312', 'west': 'Times New Roman', 'size': '三号',
205
+ 'line_rule': 'exact', 'line': 28.8, 'first_indent_chars': 2,
206
+ 'space_before': 0, 'space_after': 0, 'align': 'justify'},
207
+ 'headings': [
208
+ {'east': '黑体', 'size': '三号', 'bold': False, 'align': 'left',
209
+ 'space_before': 0, 'space_after': 0, 'line': 28.8, 'line_rule': 'exact'},
210
+ {'east': '楷体_GB2312', 'size': '三号', 'bold': False, 'align': 'left',
211
+ 'space_before': 0, 'space_after': 0, 'line': 28.8, 'line_rule': 'exact'},
212
+ {'east': '仿宋_GB2312', 'size': '三号', 'bold': True, 'align': 'left',
213
+ 'space_before': 0, 'space_after': 0, 'line': 28.8, 'line_rule': 'exact'},
214
+ {'east': '仿宋_GB2312', 'size': '三号', 'bold': True, 'align': 'left',
215
+ 'space_before': 0, 'space_after': 0, 'line': 28.8, 'line_rule': 'exact'},
216
+ ],
217
+ 'table': {'east': '仿宋_GB2312', 'size': '小四', 'head_bold': True},
218
+ 'caption': {'east': '黑体', 'size': '五号'},
219
+ 'numbering': 'cn',
220
+ },
221
+ 'report': {
222
+ 'name': 'report',
223
+ 'label': '科技报告/学术(宋体小四/固定 20 磅行距/十进制编号)',
224
+ 'margin_cm': {'top': 2.54, 'bottom': 2.54, 'left': 3.0, 'right': 3.0},
225
+ 'body': {'line_rule': 'exact', 'line': 20, 'first_indent_chars': 2},
226
+ 'headings': [
227
+ {'east': '黑体', 'size': '三号', 'align': 'center', 'space_before': 18, 'space_after': 12},
228
+ {'east': '黑体', 'size': '四号', 'align': 'left', 'space_before': 12, 'space_after': 6},
229
+ {'east': '黑体', 'size': '小四', 'align': 'left', 'space_before': 6, 'space_after': 6},
230
+ {'east': '黑体', 'size': '小四', 'bold': False, 'align': 'left', 'space_before': 6, 'space_after': 0},
231
+ ],
232
+ 'numbering': 'decimal',
233
+ 'caption_by_chapter': True,
234
+ },
235
+ }
236
+
237
+ PROFILES = {k: _merge(_BASE, v) for k, v in _OVERRIDES.items()}
238
+ PROFILE_NAMES = list(PROFILES)
239
+
240
+
241
+ def profile(name='tech'):
242
+ """取一份规范档位(深拷贝,调用方可以就地改而不污染全局)。"""
243
+ p = PROFILES.get(str(name or 'tech').strip(), PROFILES['tech'])
244
+ return _merge(p, {})
245
+
246
+
247
+ # ── 版心几何 ───────────────────────────────────────────────────────────────
248
+ def body_width_pt(prof):
249
+ """版心宽(磅)——所有「会不会超宽」的判断都用它。"""
250
+ m = prof['margin_cm']
251
+ return (prof['page_w_cm'] - m['left'] - m['right']) * CM_PT
252
+
253
+
254
+ def body_height_pt(prof):
255
+ """版心高(磅)。页眉页脚在页边距内,这里按正文可用高度算。"""
256
+ m = prof['margin_cm']
257
+ return (prof['page_h_cm'] - m['top'] - m['bottom']) * CM_PT
258
+
259
+
260
+ def body_width_cm(prof):
261
+ m = prof['margin_cm']
262
+ return prof['page_w_cm'] - m['left'] - m['right']
263
+
264
+
265
+ def line_height_pt(prof, style=None):
266
+ """一行占多高(磅)。exact=固定值直接返回;multiple=倍数×单倍行高。"""
267
+ st = style or prof['body']
268
+ size = pt_of(st.get('size', prof['body']['size']))
269
+ rule = st.get('line_rule', prof['body'].get('line_rule', 'multiple'))
270
+ val = st.get('line', prof['body'].get('line', 1.5))
271
+ if rule == 'exact':
272
+ return float(val)
273
+ return size * LINE_H * float(val)
274
+
275
+
276
+ def indent_pt(prof, style=None):
277
+ """首行缩进(磅)= 缩进字符数 × 字号。中文排版的「2 字符」是随字号变的,不是固定磅值。"""
278
+ st = style or prof['body']
279
+ return float(st.get('first_indent_chars', 0)) * pt_of(st.get('size', prof['body']['size']))
280
+
281
+
282
+ def est_para_height(text, prof, style=None):
283
+ """一段文字的估算高度(磅),含段前段后。"""
284
+ st = style or prof['body']
285
+ size = st.get('size', prof['body']['size'])
286
+ n = wrap_count(text, size, body_width_pt(prof), indent_pt(prof, st))
287
+ return n * line_height_pt(prof, st) + float(st.get('space_before', 0)) + float(st.get('space_after', 0))
288
+
289
+
290
+ # ── 表格列宽(先量后画的核心)───────────────────────────────────────────────
291
+ def fit_table_widths(rows, prof, size=None, min_col_cm=1.2, pad_pt=8.0):
292
+ """按内容算列宽,总宽压进版心。返回每列宽度(厘米)。
293
+
294
+ 做法:先按「每列最长单元格的自然宽度」分配,超版心时按超出比例压缩,
295
+ 但每列不低于 min_col_cm(压到比这还窄,字会被逼成竖排,那还不如让它折行)。
296
+ 压不下去的(列太多)会把 min 一起等比降,并由调用方决定是否横排页面。
297
+ """
298
+ if not rows:
299
+ return []
300
+ cols = max(len(r) for r in rows)
301
+ size = size or prof['table']['size']
302
+ avail = body_width_pt(prof)
303
+ nat = []
304
+ for j in range(cols):
305
+ w = 0.0
306
+ for r in rows:
307
+ if j < len(r):
308
+ cell = str(r[j] if r[j] is not None else '')
309
+ w = max(w, max((measure(seg, size) for seg in cell.split('\n')), default=0.0))
310
+ nat.append(min(w + pad_pt, avail * 0.62)) # 单列上限 62% 版心,防一列吃掉整张表
311
+ total = sum(nat) or 1.0
312
+ if total <= avail:
313
+ # 有富余就把剩下的按自然宽比例摊回去,让表格铺满版心(半张表宽的表格很难看)
314
+ extra = avail - total
315
+ nat = [w + extra * (w / total) for w in nat]
316
+ return [round(w * PT_CM, 3) for w in nat]
317
+ floor_pt = min_col_cm * CM_PT
318
+ if floor_pt * cols > avail:
319
+ floor_pt = avail / cols * 0.8
320
+ slack = [max(w - floor_pt, 0.0) for w in nat]
321
+ over, slack_sum = total - avail, sum(slack)
322
+ if slack_sum <= 0:
323
+ return [round(avail / cols * PT_CM, 3)] * cols
324
+ ratio = min(over / slack_sum, 1.0)
325
+ out = [w - s * ratio for w, s in zip(nat, slack)]
326
+ scale = avail / sum(out) if sum(out) > avail else 1.0
327
+ return [round(w * scale * PT_CM, 3) for w in out]
328
+
329
+
330
+ def est_table_height(rows, prof, widths_cm=None, size=None, cell_pad_pt=4.0):
331
+ """表格估算高度(磅):逐格算折行数取每行最大。用于判断「这张表会不会顶出页底」。"""
332
+ if not rows:
333
+ return 0.0
334
+ size = size or prof['table']['size']
335
+ widths_cm = widths_cm or fit_table_widths(rows, prof, size)
336
+ lh = pt_of(size) * LINE_H
337
+ h = 0.0
338
+ for r in rows:
339
+ rows_lines = 1
340
+ for j, cell in enumerate(r):
341
+ w = (widths_cm[j] if j < len(widths_cm) else widths_cm[-1]) * CM_PT - cell_pad_pt * 2
342
+ rows_lines = max(rows_lines, wrap_count(str(cell or ''), size, max(w, pt_of(size))))
343
+ h += rows_lines * lh + cell_pad_pt * 2
344
+ return h
345
+
346
+
347
+ # ── 图片 ───────────────────────────────────────────────────────────────────
348
+ def image_box(px_w, px_h, prof, max_w_cm=None, max_h_cm=None, target_dpi=150):
349
+ """按原比例算插图显示尺寸(厘米):先按 target_dpi 折算自然尺寸,超版心/超高再等比缩。
350
+
351
+ **不存在把图拉变形的写法**——宽高永远同一个 scale,这与 pptx 侧同一条纪律。
352
+ """
353
+ if not px_w or not px_h:
354
+ return (body_width_cm(prof) * 0.8, body_width_cm(prof) * 0.5)
355
+ max_w = max_w_cm or prof.get('max_image_cm') or body_width_cm(prof)
356
+ max_h = max_h_cm or (body_height_pt(prof) * PT_CM * 0.78) # 留出题注与上下文
357
+ w_cm = px_w / float(target_dpi) * 2.54
358
+ h_cm = px_h / float(target_dpi) * 2.54
359
+ scale = min(max_w / w_cm, max_h / h_cm, 1.0)
360
+ return (round(w_cm * scale, 3), round(h_cm * scale, 3))
361
+
362
+
363
+ def effective_dpi(px, length_cm):
364
+ """图片实际打印分辨率:像素数 ÷ 显示英寸。<120 在 A4 上肉眼可见发虚。"""
365
+ if not px or not length_cm:
366
+ return 0.0
367
+ return px / (length_cm / 2.54)
368
+
369
+
370
+ # ── 标题编号 ───────────────────────────────────────────────────────────────
371
+ _CN_DIGITS = '零一二三四五六七八九'
372
+
373
+
374
+ def cn_num(n):
375
+ """1→一,11→十一,21→二十一(技术文档章节到不了三位数,够用)。"""
376
+ n = int(n)
377
+ if n <= 0:
378
+ return str(n)
379
+ if n < 10:
380
+ return _CN_DIGITS[n]
381
+ if n < 20:
382
+ return '十' + (_CN_DIGITS[n % 10] if n % 10 else '')
383
+ if n < 100:
384
+ return _CN_DIGITS[n // 10] + '十' + (_CN_DIGITS[n % 10] if n % 10 else '')
385
+ return str(n)
386
+
387
+
388
+ def heading_number(prof, level, counters):
389
+ """按规范给出第 level 级标题的编号文本(counters 为 1..4 级计数器列表)。"""
390
+ style = prof.get('numbering', 'cn')
391
+ if style == 'none':
392
+ return ''
393
+ if style == 'decimal':
394
+ return '.'.join(str(counters[i]) for i in range(level)) + ' '
395
+ # cn:一、/(一)/1./(1)
396
+ if level == 1:
397
+ return f'{cn_num(counters[0])}、'
398
+ if level == 2:
399
+ return f'({cn_num(counters[1])})'
400
+ if level == 3:
401
+ return f'{counters[2]}.'
402
+ return f'({counters[3]})'
403
+
404
+
405
+ # 体检端要能反过来认出这些编号,判断「编号连不连续」——正则与上面的生成规则同源。
406
+ NUM_PATTERNS = [
407
+ ('cn1', re.compile(r'^\s*([一二三四五六七八九十]+)\s*[、..]\s*')),
408
+ ('cn2', re.compile(r'^\s*[((]\s*([一二三四五六七八九十]+)\s*[))]\s*')),
409
+ ('ar1', re.compile(r'^\s*(\d+)\s*[、..]\s*(?!\d)')),
410
+ ('ar2', re.compile(r'^\s*[((]\s*(\d+)\s*[))]\s*')),
411
+ ('dec', re.compile(r'^\s*(\d+(?:\.\d+)+)\s*[、..]?\s+')),
412
+ ('chap', re.compile(r'^\s*第\s*([一二三四五六七八九十\d]+)\s*[章节部分篇]\s*')),
413
+ ]
414
+
415
+
416
+ def parse_number(text):
417
+ """从标题文本里认出编号:返回 (kind, 序号元组, 去掉编号后的正文) 或 None。"""
418
+ s = str(text or '')
419
+ for kind, pat in NUM_PATTERNS:
420
+ m = pat.match(s)
421
+ if not m:
422
+ continue
423
+ raw = m.group(1)
424
+ if kind == 'dec':
425
+ try:
426
+ seq = tuple(int(x) for x in raw.split('.'))
427
+ except ValueError:
428
+ continue
429
+ elif raw.isdigit():
430
+ seq = (int(raw),)
431
+ else:
432
+ seq = (cn_to_int(raw),)
433
+ if seq[0] <= 0:
434
+ continue
435
+ return kind, seq, s[m.end():].strip()
436
+ return None
437
+
438
+
439
+ def cn_to_int(s):
440
+ """一→1,十一→11,二十→20。认不出返回 0。"""
441
+ s = str(s or '').strip()
442
+ if not s:
443
+ return 0
444
+ if s.isdigit():
445
+ return int(s)
446
+ if '十' not in s:
447
+ return _CN_DIGITS.find(s[0]) if s[0] in _CN_DIGITS else 0
448
+ head, _, tail = s.partition('十')
449
+ h = _CN_DIGITS.find(head) if head else 1
450
+ t = _CN_DIGITS.find(tail) if tail else 0
451
+ if h < 0 or t < 0:
452
+ return 0
453
+ return h * 10 + t
454
+
455
+
456
+ def caption_label(prof, kind, index, chapter=None):
457
+ """题注编号文本:图 3 / 图 2-3(按章编号)/ 表 1。"""
458
+ prefix = prof['fig_prefix'] if kind == 'fig' else prof['tab_prefix']
459
+ if prof.get('caption_by_chapter') and chapter:
460
+ return f'{prefix} {chapter}-{index}'
461
+ return f'{prefix} {index}'
462
+
463
+
464
+ CAPTION_RE = re.compile(r'^\s*(图|表|Figure|Fig\.?|Table)\s*([0-9]+(?:[-–.][0-9]+)*)\s*[.、::]?\s*(.*)$', re.I)
465
+ REF_RE = re.compile(r'(?:如|见|参见|详见)?\s*(图|表)\s*([0-9]+(?:[-–.][0-9]+)*)\s*(?:所示|所列|中)?')
466
+
467
+
468
+ def self_test():
469
+ p = profile('tech')
470
+ assert abs(body_width_cm(p) - 14.66) < 0.01, body_width_cm(p)
471
+ assert wrap_count('中' * 40, '小四', body_width_pt(p)) >= 1
472
+ assert wrap_count('中' * 200, '小四', body_width_pt(p)) > 4
473
+ assert pt_of('小四') == 12 and size_name(16) == '三号'
474
+ assert cn_num(21) == '二十一' and cn_to_int('二十一') == 21
475
+ assert parse_number('一、项目背景')[1] == (1,)
476
+ assert parse_number('1.2.3 技术路线')[1] == (1, 2, 3)
477
+ assert parse_number('(三)预期成果')[1] == (3,)
478
+ w = fit_table_widths([['指标', '2025 年实际完成情况说明', '备注'], ['交付率', '98.2%', '—']], p)
479
+ assert abs(sum(w) - body_width_cm(p)) < 0.05, (w, sum(w))
480
+ g = profile('gov')
481
+ assert pt_of(g['body']['size']) == 16 and g['body']['line_rule'] == 'exact'
482
+ assert image_box(1600, 900, p)[0] <= body_width_cm(p) + 1e-6
483
+ a = image_box(4000, 1000, p)
484
+ assert abs(a[0] / a[1] - 4.0) < 1e-6, a # 等比,永不变形
485
+ print('[docx_layout] self-test OK ·',
486
+ f"版心 {body_width_cm(p):.2f}cm / 行高 {line_height_pt(p):.1f}pt / 缩进 {indent_pt(p):.0f}pt")
487
+
488
+
489
+ if __name__ == '__main__':
490
+ self_test()