@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
@@ -0,0 +1,586 @@
1
+ # 文档引擎压力测试台:正向跑批 + **反向造缺陷** + 自动校正闭环。
2
+ #
3
+ # 为什么必须有反向:一个只会说「未发现问题」的体检器和没有体检器是一回事,
4
+ # 而且更糟——它给了人一个虚假的「已检查」印象。所以每加一条检查规则,
5
+ # 这里就要配一个「故意做坏的文档」,断言那条规则真的会响;
6
+ # 顺带断言**正向生成的合规文档必须零必修**,防止规则写得太紧变成天天误报。
7
+ #
8
+ # 用法:
9
+ # python docx_battery.py # 正向 120 份 + 反向全部缺陷 + 校正闭环
10
+ # python docx_battery.py 400 # 正向跑 400 份
11
+ # python docx_battery.py --keep # 保留产物到 ./_battery 供人工翻看
12
+ #
13
+ # 改了 docx_layout / docx_build / docx_check 任何一个,**必须重跑本文件**。
14
+
15
+ import os
16
+ import random
17
+ import shutil
18
+ import struct
19
+ import sys
20
+ import tempfile
21
+ import zlib
22
+
23
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
24
+ from docx import Document # noqa: E402
25
+ from docx.enum.text import WD_ALIGN_PARAGRAPH # noqa: E402
26
+ from docx.oxml.ns import qn # noqa: E402
27
+ from docx.shared import Cm, Pt # noqa: E402
28
+
29
+ import docx_build as B # noqa: E402
30
+ import docx_check as C # noqa: E402
31
+ import docx_layout as L # noqa: E402
32
+
33
+ # ── 随机素材 ───────────────────────────────────────────────────────────────
34
+ WORDS = ['系统', '模块', '接口', '数据', '采集', '告警', '模型', '训练', '部署', '验收',
35
+ '指标', '工期', '预算', '风险', '评审', '测试', '上线', '运维', '容灾', '权限',
36
+ '甲方', '监理', '里程碑', '交付物', '需求', '架构', '数据库', '中间件', '并发']
37
+ TAILS = ['已按计划完成', '尚在验证中', '需甲方确认口径', '存在 3 项遗留缺陷',
38
+ '较基线提前 5 天', '成本超支 2.1 万元', '通过第三方检测', '进入试运行']
39
+
40
+
41
+ def sentence(rng):
42
+ return (f'{rng.choice(WORDS)}{rng.choice(WORDS)}在 {rng.randint(1, 12)} 月完成'
43
+ f'{rng.randint(2, 40)} 项{rng.choice(WORDS)},{rng.choice(TAILS)}。')
44
+
45
+
46
+ def para(rng, n=None):
47
+ return ''.join(sentence(rng) for _ in range(n or rng.randint(1, 6)))
48
+
49
+
50
+ def png(path, w, h):
51
+ """不依赖 PIL 的最小 PNG 写出(压测要能在只装了 python-docx 的环境里跑)。"""
52
+ def chunk(tag, data):
53
+ c = tag + data
54
+ return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff)
55
+ raw = b''.join(b'\x00' + bytes([(x * 7) % 256, (y * 11) % 256, 200]) * 1 * w
56
+ for y in range(h) for x in [0])
57
+ raw = b''.join(b'\x00' + b''.join(bytes([(x * 7) % 256, (y * 11) % 256, 200]) for x in range(w))
58
+ for y in range(h))
59
+ with open(path, 'wb') as f:
60
+ f.write(b'\x89PNG\r\n\x1a\n'
61
+ + chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0))
62
+ + chunk(b'IDAT', zlib.compress(raw))
63
+ + chunk(b'IEND', b''))
64
+ return path
65
+
66
+
67
+ def random_spec(rng, img):
68
+ prof = rng.choice(['tech', 'gov', 'report'])
69
+ by_chap = (prof == 'report') # 与 docx_layout 的档位定义一致
70
+ blocks, chap, fig, tab = [], 0, {}, {}
71
+
72
+ def label(kind, ch):
73
+ d = fig if kind == 'fig' else tab
74
+ key = ch if by_chap else 0
75
+ d[key] = d.get(key, 0) + 1
76
+ pre = '图' if kind == 'fig' else '表'
77
+ return f'{pre} {ch}-{d[key]}' if by_chap else f'{pre} {d[key]}'
78
+
79
+ for i in range(rng.randint(3, 7)):
80
+ chap += 1
81
+ blocks.append({'type': 'h1', 'text': f'{rng.choice(WORDS)}{rng.choice(WORDS)}'})
82
+ for _ in range(rng.randint(1, 3)):
83
+ blocks.append({'type': 'p', 'text': para(rng)})
84
+ if rng.random() < 0.5:
85
+ blocks.append({'type': 'h2', 'text': f'{rng.choice(WORDS)}说明'})
86
+ blocks.append({'type': 'p', 'text': para(rng)})
87
+ if rng.random() < 0.45:
88
+ cols = rng.randint(2, 8)
89
+ rows = [[f'{rng.choice(WORDS)}' * rng.randint(1, 6) for _ in range(cols)]
90
+ for _ in range(rng.randint(2, 18))]
91
+ blocks.append({'type': 'table', 'rows': rows, 'caption': f'{rng.choice(WORDS)}明细'})
92
+ blocks.append({'type': 'p', 'text': f'详见{label("tab", chap)}。'})
93
+ if rng.random() < 0.35:
94
+ blocks.append({'type': 'figure', 'image': img, 'caption': f'{rng.choice(WORDS)}示意'})
95
+ blocks.append({'type': 'p', 'text': f'如{label("fig", chap)}所示,{para(rng, 1)}'})
96
+ if rng.random() < 0.3:
97
+ blocks.append({'type': 'bullets', 'items': [para(rng, 1) for _ in range(rng.randint(2, 6))]})
98
+ return {'profile': prof, 'title': f'{rng.choice(WORDS)}项目{rng.choice(["建议书","实施计划","验收报告"])}',
99
+ 'org': '压测单位', 'footer': '压测', 'blocks': blocks}
100
+
101
+
102
+ # ── 正向:合规文档必须零必修 ───────────────────────────────────────────────
103
+ def forward(n, workdir, verbose=True):
104
+ rng = random.Random(20260901)
105
+ imgs = [png(os.path.join(workdir, f'im{i}.png'), w, h)
106
+ for i, (w, h) in enumerate([(1600, 900), (900, 1600), (2400, 600), (1200, 1200)])]
107
+ bad, worst = 0, []
108
+ for i in range(n):
109
+ spec = random_spec(rng, rng.choice(imgs))
110
+ out = os.path.join(workdir, f'f{i}.docx')
111
+ try:
112
+ _p, res, _w, _lb = B.build(spec, out)
113
+ except Exception as e:
114
+ bad += 1
115
+ worst.append((f'f{i}', f'生成异常 {type(e).__name__}: {e}'))
116
+ continue
117
+ errs = [x for x in res['issues'] if x['level'] == 'error']
118
+ if errs:
119
+ bad += 1
120
+ worst.append((f"f{i}({spec['profile']})", '; '.join(sorted({x['kind'] + ':' + x['msg'][:40] for x in errs}))))
121
+ os.remove(out)
122
+ if verbose and (i + 1) % 40 == 0:
123
+ print(f' 正向 {i+1}/{n} …')
124
+ return bad, worst
125
+
126
+
127
+ # ── 反向:每条规则配一个「故意做坏」的文档 ─────────────────────────────────
128
+ def _base(workdir, name, img=None, blocks=None, **kw):
129
+ spec = {'profile': 'tech', 'title': '反向用例', 'org': '压测单位', 'inspect': False,
130
+ 'blocks': blocks or ([{'type': 'h1', 'text': '第一章'}]
131
+ + [{'type': 'p', 'text': '本节记录 %d 项工作,均已完成。' % i} for i in range(6)]
132
+ + [{'type': 'h1', 'text': '第二章'}]
133
+ + [{'type': 'p', 'text': '第二节的内容共 %d 段。' % i} for i in range(4)])}
134
+ spec.update(kw)
135
+ out = os.path.join(workdir, name)
136
+ B.build(spec, out)
137
+ return out
138
+
139
+
140
+ def _kinds(path, strict=False):
141
+ return {i['kind'] for i in C.inspect(path, 'tech', strict)['issues']}
142
+
143
+
144
+ def _strip_eastasia(doc):
145
+ """正文与**样式表**两处都要剥——样式在 styles.xml 里,从 doc.element 遍历不到。"""
146
+ for root in (doc.element, doc.styles.element):
147
+ for el in root.iter():
148
+ if el.tag == qn('w:rFonts') and el.get(qn('w:eastAsia')) is not None:
149
+ del el.attrib[qn('w:eastAsia')]
150
+
151
+
152
+ def _is_cover_table(tb):
153
+ """封面信息栏的确定特征:两列,且左列每个非空格都以「:」结尾。"""
154
+ if not tb.rows or len(tb.rows[0].cells) != 2:
155
+ return False
156
+ lefts = [r.cells[0].text.strip() for r in tb.rows]
157
+ lefts = [x for x in lefts if x]
158
+ return bool(lefts) and all(x.endswith(':') or x.endswith(':') for x in lefts)
159
+
160
+
161
+ def _content_table(doc):
162
+ """取**正文里的**那张表。不能用 tables[0]:封面信息栏也是表格且排在最前,
163
+ 按下标取会把用例打在封面表上——造不出缺陷,却被报成「规则是瞎的」。"""
164
+ for tb in doc.tables:
165
+ if not _is_cover_table(tb):
166
+ return tb
167
+ return doc.tables[-1]
168
+
169
+
170
+ def _bodies(doc, mark='本节记录'):
171
+ """取真正的正文段。反向用例必须打在正文上——打在封面段上体检本来就不该响,
172
+ 那样测出来的「漏报」是假的。"""
173
+ return [p for p in doc.paragraphs if mark in p.text]
174
+
175
+
176
+ def reverse(workdir):
177
+ """返回 [(用例名, 期望命中的 kind, 是否命中, 实际命中集合)]。"""
178
+ cases = []
179
+ img = png(os.path.join(workdir, 'r.png'), 1600, 900)
180
+ small = png(os.path.join(workdir, 'small.png'), 160, 90)
181
+
182
+ def case(name, expect, mutate, **kw):
183
+ p = _base(workdir, name + '.docx', **kw)
184
+ d = Document(p)
185
+ mutate(d)
186
+ d.save(p)
187
+ got = _kinds(p)
188
+ cases.append((name, expect, expect in got, got))
189
+ return p
190
+
191
+ # 排版类
192
+ case('缺东亚字体', 'no-eastasia', _strip_eastasia)
193
+ case('假缩进', 'fake-indent',
194
+ lambda d: [setattr(p.runs[0], 'text', '  ' + p.runs[0].text) for p in _bodies(d)[:3] if p.runs])
195
+ case('字号混杂', 'size-mix',
196
+ lambda d: [setattr(r.font, 'size', Pt(9)) for p in _bodies(d)[:3] for r in p.runs])
197
+ case('首行缩进忽有忽无', 'indent-mix',
198
+ lambda d: [setattr(p.paragraph_format, 'first_line_indent', Pt(0)) for p in _bodies(d)[:3]])
199
+ case('连续空段', 'blank-runs',
200
+ lambda d: [_bodies(d)[2].insert_paragraph_before('') for _ in range(4)])
201
+ case('正文误居中', 'align-mix',
202
+ lambda d: [setattr(p, 'alignment', WD_ALIGN_PARAGRAPH.CENTER) for p in _bodies(d)[:2]])
203
+
204
+ # 结构类
205
+ case('标题跳级', 'level-jump', lambda d: None,
206
+ blocks=[{'type': 'h1', 'text': '一章'}, {'type': 'p', 'text': '正文内容一段。'},
207
+ {'type': 'h3', 'text': '直接三级'}, {'type': 'p', 'text': '正文内容两段。'}])
208
+ case('空章节', 'empty-section', lambda d: None,
209
+ blocks=[{'type': 'h1', 'text': '有内容'}, {'type': 'p', 'text': '正文一段。'},
210
+ {'type': 'h1', 'text': '空壳章节'}, {'type': 'h1', 'text': '又一章'},
211
+ {'type': 'p', 'text': '正文两段。'}])
212
+ case('章节编号断号', 'number-gap', lambda d: None, numbering='none',
213
+ blocks=[{'type': 'h1', 'text': '一、概述'}, {'type': 'p', 'text': '正文一段。'},
214
+ {'type': 'h1', 'text': '三、方案'}, {'type': 'p', 'text': '正文二段。'}])
215
+ case('无页码', 'no-pagenum', lambda d: None, page_numbers=False, toc=False, cover=False)
216
+
217
+ # 表格类
218
+ def widen(d):
219
+ t = _content_table(d)
220
+ t.autofit = False
221
+ for row in t.rows:
222
+ for c in row.cells:
223
+ c.width = Cm(6)
224
+ g = t._tbl.tblGrid
225
+ for gc in g.findall(qn('w:gridCol')):
226
+ gc.set(qn('w:w'), str(int(6 * L.CM_PT * 20)))
227
+ case('表格超版心', 'over-width', widen,
228
+ blocks=[{'type': 'h1', 'text': '表'}, {'type': 'p', 'text': '见下表。'},
229
+ {'type': 'table', 'rows': [['甲', '乙', '丙', '丁'], ['1', '2', '3', '4']],
230
+ 'caption': '宽表'}])
231
+ case('表头未加粗', 'head-not-bold',
232
+ lambda d: [setattr(r.font, 'bold', False) for c in _content_table(d).rows[0].cells
233
+ for p in c.paragraphs for r in p.runs],
234
+ blocks=[{'type': 'h1', 'text': '表'}, {'type': 'p', 'text': '见表 1。'},
235
+ {'type': 'table', 'rows': [['甲', '乙'], ['1', '2']], 'caption': '小表'}])
236
+ case('长表无重复表头', 'no-head-repeat',
237
+ lambda d: (lambda tr: [tr.remove(x) for x in tr.findall(qn('w:tblHeader'))])(
238
+ _content_table(d).rows[0]._tr.get_or_add_trPr()),
239
+ blocks=[{'type': 'h1', 'text': '表'}, {'type': 'p', 'text': '见表 1。'},
240
+ {'type': 'table', 'rows': [[f'行{i}', f'值{i}'] for i in range(20)], 'caption': '长表'}])
241
+
242
+ # 插图类
243
+ def distort(d):
244
+ sh = d.inline_shapes[0]
245
+ sh.height = int(sh.height * 1.6)
246
+ case('图片变形', 'distorted', distort,
247
+ blocks=[{'type': 'h1', 'text': '图'}, {'type': 'p', 'text': '如图 1 所示。'},
248
+ {'type': 'figure', 'image': img, 'caption': '示意'}])
249
+ case('图片超版心', 'over-width', lambda d: setattr(d.inline_shapes[0], 'width', int(Cm(20))),
250
+ blocks=[{'type': 'h1', 'text': '图'}, {'type': 'p', 'text': '如图 1 所示。'},
251
+ {'type': 'figure', 'image': img, 'caption': '示意'}])
252
+ case('图片发虚', 'low-dpi', lambda d: None,
253
+ blocks=[{'type': 'h1', 'text': '图'}, {'type': 'p', 'text': '如图 1 所示。'},
254
+ {'type': 'figure', 'image': small, 'caption': '低清图', 'width_cm': 14}])
255
+ case('图题断号', 'fig-number-gap',
256
+ lambda d: [setattr(p.runs[0], 'text', '图 3 第二张') for p in d.paragraphs
257
+ if p.text.strip().startswith('图 2')],
258
+ blocks=[{'type': 'h1', 'text': '图'}, {'type': 'p', 'text': '如图 1、图 2 所示。'},
259
+ {'type': 'figure', 'image': img, 'caption': '第一张'},
260
+ {'type': 'figure', 'image': img, 'caption': '第二张'}])
261
+ case('引用了不存在的图', 'fig-dangling-ref', lambda d: None,
262
+ blocks=[{'type': 'h1', 'text': '图'}, {'type': 'p', 'text': '如图 1 所示,另见图 7 所示。'},
263
+ {'type': 'figure', 'image': img, 'caption': '唯一一张'}])
264
+ case('图从未被引用', 'fig-unreferenced', lambda d: None,
265
+ blocks=[{'type': 'h1', 'text': '图'}, {'type': 'p', 'text': '这里没有任何指引。'},
266
+ {'type': 'figure', 'image': img, 'caption': '孤图'}])
267
+
268
+ # 文字与 AI 味
269
+ case('错别字', 'typo', lambda d: None,
270
+ blocks=[{'type': 'h1', 'text': '文字'},
271
+ {'type': 'p', 'text': '系统己经按装完成,运行正常,未发现异常。'}])
272
+ case('半角标点', 'half-comma', lambda d: None,
273
+ blocks=[{'type': 'h1', 'text': '文字'},
274
+ {'type': 'p', 'text': '本期完成三项工作,均通过验收,未遗留问题。'}])
275
+ ai = ('值得注意的是,本系统通过深度赋能业务闭环,有效提升了整体效能。'
276
+ '综上所述,该方案具有重要的现实意义,为后续工作奠定了坚实的基础。')
277
+ case('AI 味套话', 'cliche', lambda d: None,
278
+ blocks=[{'type': 'h1', 'text': '综述'}] + [{'type': 'p', 'text': ai} for _ in range(10)])
279
+ case('长句不喘气', 'sent-run', lambda d: None,
280
+ blocks=[{'type': 'h1', 'text': '文字'},
281
+ {'type': 'p', 'text': '本次研制的智能运维平台系统软件与配套工具链在两个生产集群'
282
+ '完成部署并连续运行三十天且未出现任何影响业务的故障。'}])
283
+ case('破折号误用', 'bad-dash', lambda d: None,
284
+ blocks=[{'type': 'h1', 'text': '文字'},
285
+ {'type': 'p', 'text': '本期完成三项工作—其中两项提前交付,一项按期完成。'}])
286
+ case('术语多种写法', 'term-variant', lambda d: None,
287
+ blocks=[{'type': 'h1', 'text': '术语'},
288
+ {'type': 'p', 'text': '本系统提供 API 接口。Api 需鉴权。api 限流 100 QPS。'
289
+ '接口文档见附件,API 版本为 v2。'}])
290
+ case('通篇没有数字锚点', 'no-anchor', lambda d: None,
291
+ blocks=[{'type': 'h1', 'text': '综述'}]
292
+ + [{'type': 'p', 'text': '本部分对相关工作进行了系统梳理与全面阐述,'
293
+ '明确了总体思路与推进方式,为下一阶段奠定基础。'} for _ in range(12)])
294
+
295
+ # 报告型文档的内容硬指标(HuiCai 可研基线里真实出现过的四类)
296
+ feas = {'title': '智能运维平台可行性研究报告', 'date': '2026 年 9 月 23 日'}
297
+ case('可研计划落在编制日前', 'plan-in-past', lambda d: None, **feas,
298
+ blocks=[{'type': 'h1', 'text': '进度安排与年度计划内容'},
299
+ {'type': 'p', 'text': '项目于 2025 年 10 月启动,2026 年 3 月完成开发,2026 年 8 月完成验收。'}])
300
+ case('同一对象数量矛盾', 'count-conflict', lambda d: None,
301
+ blocks=[{'type': 'h1', 'text': '风险'},
302
+ {'type': 'p', 'text': '本项目从技术、市场、运营、财务、合规、进度与知识产权七个维度识别风险。'},
303
+ {'type': 'p', 'text': '项目风险按下述五个维度识别,逐项给出应对措施与责任人。'}])
304
+ # 有题注、但一张架构图/进度/投资/财务/风险都没有(零题注的文档归题注检查管,这条规则刻意不判)
305
+ case('可研缺评审常看的图表', 'report-visuals', lambda d: None, **feas,
306
+ blocks=[{'type': 'h1', 'text': '承担单位和主要研究人员简况'}, {'type': 'p', 'text': '人员见表 1。'},
307
+ {'type': 'table', 'caption': '主要研究人员', 'rows': [['姓名', '分工'], ['[待补]', '技术负责']]}])
308
+ case('可研财务评价缺指标', 'report-finance', lambda d: None, **feas)
309
+ return cases
310
+
311
+
312
+ # ── 反误报:合规写法**绝不能**被判成问题 ───────────────────────────────────
313
+ # 这一档和反向造缺陷同等重要,方向相反:规则越收越紧的过程中,最容易发生的事
314
+ # 不是漏报,而是把「表单表没有表头行」「列表项不带句号」这类正确写法判成毛病。
315
+ # 一个天天误报的体检器,用两次就没人看了。
316
+ def negative(workdir):
317
+ img = png(os.path.join(workdir, 'n.png'), 1600, 900)
318
+ cases = []
319
+
320
+ def nocase(name, forbidden, **kw):
321
+ p = _base(workdir, 'neg-' + name + '.docx', **kw)
322
+ got = _kinds(p)
323
+ bad = [k for k in forbidden if k in got]
324
+ cases.append((name, not bad, bad, got))
325
+
326
+ nocase('表单式两列表', ['head-not-bold'],
327
+ blocks=[{'type': 'h1', 'text': '交付物'}, {'type': 'p', 'text': '交付物清单见表 1。'},
328
+ {'type': 'kv', 'caption': '交付物清单',
329
+ 'pairs': [['软件安装包', 'V1.0.0,光盘 2 份'], ['源代码', 'Git 全量归档 1 份'],
330
+ ['技术文档', '需求/设计/测试各 2 份']]}])
331
+ nocase('列表项无句末标点', ['no-end-punct'],
332
+ blocks=[{'type': 'h1', 'text': '要点'}, {'type': 'p', 'text': '本期完成三项工作。'},
333
+ {'type': 'bullets', 'items': ['告警准确率达 92%', '接入成本降至 1.2 人日',
334
+ '试运行 30 天零故障']}])
335
+ nocase('封面与目录', ['size-mix', 'align-mix', 'no-indent', 'blank-runs'],
336
+ title='正常文档', toc=True, cover=True,
337
+ blocks=[{'type': 'h1', 'text': '第一章'}]
338
+ + [{'type': 'p', 'text': f'第 {i} 段记录了 {i*3} 项工作,均已在 {i+1} 月完成。'}
339
+ for i in range(1, 7)])
340
+ nocase('签字栏', ['align-mix', 'no-end-punct'],
341
+ blocks=[{'type': 'h1', 'text': '结论'}, {'type': 'p', 'text': '同意通过验收。'},
342
+ {'type': 'signature', 'roles': ['编制', '审核', '批准'], 'org': '数字技术研究院'}])
343
+ nocase('表格用—表示无', ['bad-dash'],
344
+ blocks=[{'type': 'h1', 'text': '经费'}, {'type': 'p', 'text': '执行情况见表 1。'},
345
+ {'type': 'table', 'caption': '经费执行',
346
+ 'rows': [['科目', '预算', '实际', '差异说明'], ['设备', '120.0', '118.4', '—'],
347
+ ['人力', '260.0', '268.4', '压测环境多租两月']]}])
348
+ # 决议/通知/签到表这类短文档**一个标题都没有**,「首个标题之前算前置件」那条规则
349
+ # 在它们身上完全失效,封面大标题与文号会被当成正文,报一堆字号/缩进不统一。
350
+ # 靠分节符定位前置件才盖得住——这条守着那个修复。
351
+ nocase('无标题短文档的封面', ['size-mix', 'indent-mix', 'align-mix', 'no-indent'],
352
+ title='关于开展项目研究与开发工作的决议', toc=False,
353
+ blocks=[{'type': 'p', 'text': '经公司管理层会议决定,同意本年度十二项研究与开发项目立项。'},
354
+ {'type': 'kv', 'pairs': [['技术负责人意见', ''], ['总经理意见', '']]}])
355
+ # 中文里几乎每个虚词的叠字都有合法用法(目的的 / 为了了解 / 和和平 / 在在线)。
356
+ # 早先那条「虚词叠字即错」的规则在真实语料上命中 20 处、**全是误报**,还是必修级。
357
+ nocase('合法叠字不得判重复', ['dup-char', 'dup-phrase'],
358
+ blocks=[{'type': 'h1', 'text': '文字'},
359
+ {'type': 'p', 'text': '明确项目目的的重要性,为了了解情况,'
360
+ '在在线账户权益领域推进,与和平发展方向一致。'},
361
+ {'type': 'p', 'text': '把把手换掉,从从容容推进,认认真真核对一遍。'}])
362
+ nocase('正常长度句子', ['sent-run', 'sent-long'],
363
+ blocks=[{'type': 'h1', 'text': '文字'},
364
+ {'type': 'p', 'text': '平均故障发现时间从 42 分钟降到 7.6 分钟,'
365
+ '告警准确率 93.1%,均已达标。'}])
366
+ nocase('图有题注且被引用', ['fig-unreferenced', 'fig-no-caption', 'fig-dangling-ref'],
367
+ blocks=[{'type': 'h1', 'text': '架构'}, {'type': 'p', 'text': '总体架构如图 1 所示。'},
368
+ {'type': 'figure', 'image': img, 'caption': '总体架构'}])
369
+ # 总结/验收写的就是过去:时态规则只管「向前看」的文档
370
+ nocase('总结报告写已完成的进度', ['plan-in-past', 'plan-mostly-past'],
371
+ title='智能运维平台项目工作总结报告', date='2026 年 9 月 23 日',
372
+ blocks=[{'type': 'h1', 'text': '进度安排执行情况'},
373
+ {'type': 'p', 'text': '项目于 2025 年 10 月启动,2026 年 8 月通过验收。'}])
374
+ # 计划从编制之后排,进度一节里顺带提一句已完成的原型——不是「计划落在过去」
375
+ nocase('可研计划在编制日之后', ['plan-in-past', 'plan-mostly-past'],
376
+ title='智能运维平台可行性研究报告', date='2026 年 9 月 23 日',
377
+ blocks=[{'type': 'h1', 'text': '进度安排与年度计划内容'},
378
+ {'type': 'p', 'text': '前期原型已于 2026 年 6 月完成。项目计划 2026 年 10 月启动,'
379
+ '2027 年 3 月完成开发,2027 年 9 月完成验收。'}])
380
+ # 局部与整体、序数、区间,都不是「数量矛盾」
381
+ nocase('局部与整体的数量', ['count-conflict'],
382
+ blocks=[{'type': 'h1', 'text': '组成'},
383
+ {'type': 'p', 'text': '网关包含五个模块,负责鉴权与路由。平台共十二个模块,分属三层。'},
384
+ {'type': 'p', 'text': '第三个阶段是验收,前两个阶段已完成,另有三到五个场景试点。'}])
385
+ # 真实归档里的可研常常一个题注都没有:那时说「缺架构图」是瞎猜(表可能在、只是没题注),交给题注检查去报
386
+ nocase('零题注的可研不猜缺图', ['report-visuals'],
387
+ title='智能运维平台可行性研究报告', date='2026 年 9 月 23 日',
388
+ blocks=[{'type': 'h1', 'text': '经费预算'}, {'type': 'p', 'text': '经费合计 320 万元,明细如下。'},
389
+ {'type': 'table', 'rows': [['科目', '金额'], ['人工', '240']]}])
390
+ # 报告里通篇是引用、文号、版本、公式、专名:书名与括注当一个整体读,箭头与运算符是停顿,
391
+ # 「one-api」这种连字符专名不是 API 的另一种拼法(均为 A/B 实测里的误报)
392
+ nocase('引用公式版本与连字符专名', ['sent-run', 'term-variant'],
393
+ blocks=[{'type': 'h1', 'text': '依据'},
394
+ {'type': 'p', 'text': '艾瑞咨询《2026 年中国企业级 AI Agent 发展洞察报告》(2026 年 8 月发布)指出行业进入落地期。'},
395
+ {'type': 'p', 'text': '工业和信息化部于 2026 年 8 月 12 日印发《信息通信行业发展「十五五」规划》(工信部规〔2026〕188 号)。'},
396
+ {'type': 'p', 'text': '所得税前净现金流量 = 营业收入 + 成本节约 + 回收资产余值 − 建设投资 − 经营成本。'},
397
+ {'type': 'p', 'text': '里程碑依次为高并发治理整改 → 容量基线达标 → 私有化交付包发布 → 等级保护三级测评通过。'},
398
+ {'type': 'p', 'text': '后端采用 Spring Boot 3.2.5 与 Spring Cloud 2023.0.1 与 Spring Cloud Alibaba 2023.0.1.0 构建。'},
399
+ {'type': 'p', 'text': '以 one-api、New API 等开源项目为代表的网关只解决接入;本项目对外提供 OpenAI 兼容 API,并提供 API 密钥管理。'}])
400
+ nocase('可研图表与财务齐全', ['report-visuals', 'report-finance'],
401
+ title='智能运维平台可行性研究报告', date='2026 年 9 月 23 日',
402
+ blocks=[{'type': 'h1', 'text': '方案与效益'},
403
+ {'type': 'p', 'text': '总体架构见图 1,功能模块见图 2,实施进度见图 3;方案比选见表 1,投资见表 2,'
404
+ '现金流量见表 3,敏感性见表 4,风险见表 5。所得税后财务净现值(NPV)355.40 万元,'
405
+ '财务内部收益率(IRR)27.12%,静态投资回收期 4.28 年,敏感性分析显示收入最敏感。'},
406
+ {'type': 'figure', 'image': img, 'caption': '系统总体架构'},
407
+ {'type': 'figure', 'image': img, 'caption': '系统功能模块组成'},
408
+ {'type': 'figure', 'image': img, 'caption': '实施进度计划'},
409
+ {'type': 'table', 'caption': '技术方案比选', 'rows': [['方案', '结论'], ['甲', '选用']]},
410
+ {'type': 'table', 'caption': '投资估算表', 'rows': [['项目', '金额'], ['人工', '300']]},
411
+ {'type': 'table', 'caption': '项目投资现金流量表', 'rows': [['年', '净现金流'], ['2027', '120']]},
412
+ {'type': 'table', 'caption': '敏感性分析表', 'rows': [['因素', '临界点'], ['收入', '-35.6%']]},
413
+ {'type': 'table', 'caption': '风险登记表', 'rows': [['风险', '应对'], ['R1', '双供应商']]}])
414
+ return cases
415
+
416
+
417
+ def bom_markdown(workdir):
418
+ """带 BOM 的 Markdown(Windows 编辑器的常见保存方式)不得吃掉第一个标题。"""
419
+ import docx_build as _DB
420
+ md = os.path.join(workdir, 'bom.md')
421
+ open(md, 'w', encoding='utf-8-sig').write('# 一、概述\n本项目已完成,交付率 98.2%。\n')
422
+ out = os.path.join(workdir, 'bom.docx')
423
+ _DB.md_to_docx(md, out, title='T')
424
+ from docx import Document as _D
425
+ heads = [p.text for p in _D(out).paragraphs if str(p.style.name or '').startswith('Heading')]
426
+ return bool(heads) and '概述' in heads[0], (heads[:2] if heads else '一个标题都没有')
427
+
428
+
429
+ def xref_markdown(workdir):
430
+ """交叉引用:@fig:x / @tbl:x 必须换成引擎算出的真实编号(按章编号下尤其容易数错),
431
+ 写了不存在的标签要告警而不是悄悄留在正文里。"""
432
+ import docx_build as _DB
433
+ img = png(os.path.join(workdir, 'x.png'), 1600, 900)
434
+ md = ('# 一、概述\n\n架构如@fig:arch 所示,投资见@tbl:inv,**另见@fig:arch**,还有@fig:none。\n\n'
435
+ f'![系统总体架构]({img}){{#fig:arch}}\n\n# 二、投资\n\n表:投资估算 {{#tbl:inv}}\n\n'
436
+ '| 科目 | 金额 |\n|---|---|\n| 人工 | 300 |\n')
437
+ out = os.path.join(workdir, 'xref.docx')
438
+ _p, _res, warns, labels = _DB.build({'profile': 'report', 'title': 'T', 'markdown': md, 'inspect': False}, out)
439
+ from docx import Document as _D
440
+ text = '\n'.join(p.text for p in _D(out).paragraphs)
441
+ want = dict((lab_kind, lab) for lab_kind, lab, _c in labels)
442
+ ok = (f"如{want.get('图')} 所示" in text and f"见{want.get('表')}" in text and '@fig:arch' not in text
443
+ and '@tbl:inv' not in text and any('@fig:none' in w for w in warns))
444
+ return ok, f'labels={labels} warns={[w for w in warns if "标签" in w]}'
445
+
446
+
447
+ def landscape_markdown(workdir):
448
+ """横排插图:{.landscape} 的图要落在横向节里、不被判超宽、fix 不把它缩回纵向版心;
449
+ 同名 SVG 显示字太小时,成稿警告要给出纸上的实际磅数。"""
450
+ import docx_build as _DB
451
+ from docx import Document as _D
452
+ img = png(os.path.join(workdir, 'wide.png'), 3000, 1500)
453
+ with open(os.path.join(workdir, 'wide.svg'), 'w', encoding='utf-8') as fh: # 2000 宽、10px 字 ≈ 纵向版心里 2 磅
454
+ fh.write('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2000 1000">'
455
+ '<text font-size="10">说明</text><text font-size="12">节点</text></svg>')
456
+ md = (f'# 一、架构\n\n总体架构如@fig:a 所示。\n\n![总体架构]({img}){{#fig:a .landscape}}\n\n'
457
+ f'# 二、明细\n\n明细见@fig:b。\n\n![服务明细]({img}){{#fig:b}}\n')
458
+ out = os.path.join(workdir, 'land.docx')
459
+ _p, _res, warns, _labels = _DB.build({'profile': 'tech', 'title': 'T', 'markdown': md, 'inspect': False}, out)
460
+ orient = [str(sec.orientation) for sec in _D(out).sections]
461
+ kinds = _kinds(out)
462
+ ok = (any('LANDSCAPE' in o for o in orient) and 'over-width' not in kinds
463
+ and any('横排插入后' in w for w in warns) and any('按版心宽插入后' in w and '横排后' in w for w in warns))
464
+ return ok, f'sections={orient} kinds={sorted(kinds)[:6]} warns={[w[:40] for w in warns if "磅" in w]}'
465
+
466
+
467
+ def heading_shift_markdown(workdir):
468
+ """章从「##」写起(「#」留给文档标题的 Markdown 习惯):成稿要把层级整体上提,
469
+ 否则目录、导航窗格、按章编号的题注都错一级。"""
470
+ import docx_build as _DB
471
+ from docx import Document as _D
472
+ img = png(os.path.join(workdir, 'h.png'), 1600, 900)
473
+ md = f'## 一、概述\n\n如@fig:a 所示。\n\n![甲]({img}){{#fig:a}}\n\n### 1.1 背景\n\n正文。\n\n## 二、方案\n\n![乙]({img}){{#fig:b}}\n'
474
+ out = os.path.join(workdir, 'hshift.docx')
475
+ _p, _r, _w, labels = _DB.build({'profile': 'report', 'title': 'T', 'markdown': md, 'inspect': False}, out)
476
+ styles = [p.style.name for p in _D(out).paragraphs if p.style.name.startswith('Heading')]
477
+ ok = styles[:3] == ['Heading 1', 'Heading 2', 'Heading 1'] and [l[1] for l in labels] == ['图 1-1', '图 2-1']
478
+ return ok, f'styles={styles} labels={labels}'
479
+
480
+
481
+ # ── 校正闭环:修完必须真的干净,且不能修出新错 ─────────────────────────────
482
+ FIXABLE = ['缺东亚字体', '假缩进', '字号混杂', '首行缩进忽有忽无', '连续空段',
483
+ '正文误居中', '表格超版心', '表头未加粗', '长表无重复表头', '图片变形',
484
+ '图片超版心', '图题断号', '半角标点']
485
+
486
+
487
+ def fix_round(workdir, cases):
488
+ out = []
489
+ for name, expect, hit, _got in cases:
490
+ if name not in FIXABLE or not hit:
491
+ continue
492
+ src = os.path.join(workdir, name + '.docx')
493
+ try:
494
+ dst, done = C.fix(src, os.path.join(workdir, name + '-fixed.docx'), 'tech')
495
+ except Exception as e:
496
+ out.append((name, False, f'修复异常 {type(e).__name__}: {e}'))
497
+ continue
498
+ after = C.inspect(dst, 'tech')
499
+ kinds = {i['kind'] for i in after['issues']}
500
+ before = C.inspect(src, 'tech')
501
+ new_err = ({i['kind'] for i in after['issues'] if i['level'] == 'error'}
502
+ - {i['kind'] for i in before['issues'] if i['level'] == 'error'})
503
+ ok = expect not in kinds and not new_err
504
+ why = ''
505
+ if expect in kinds:
506
+ why = f'修完仍命中 {expect}'
507
+ elif new_err:
508
+ why = f'修出新必修 {sorted(new_err)}'
509
+ out.append((name, ok, why or f"已修({'、'.join(f'{k}{v}' for k, v in done.items())})"))
510
+ return out
511
+
512
+
513
+ def main(argv):
514
+ n = 120
515
+ for a in argv:
516
+ if a.isdigit():
517
+ n = int(a)
518
+ keep = '--keep' in argv
519
+ workdir = os.path.join(os.getcwd(), '_battery') if keep else tempfile.mkdtemp(prefix='docxbat-')
520
+ os.makedirs(workdir, exist_ok=True)
521
+ print(f'压测目录:{workdir}')
522
+ fails = 0
523
+
524
+ print(f'\n[1/4] 正向:随机内容 × 三档规范 × {n} 份,断言合规文档零必修')
525
+ bad, worst = forward(n, workdir)
526
+ if bad:
527
+ fails += 1
528
+ print(f' ✗ {bad}/{n} 份出现必修项:')
529
+ for name, why in worst[:8]:
530
+ print(f' {name} → {why}')
531
+ else:
532
+ print(f' ✓ {n} 份全部零必修(引擎不会生产出版式事故)')
533
+
534
+ print('\n[2/4] 反向:每条规则配一个故意做坏的文档,断言体检真的会响')
535
+ cases = reverse(workdir)
536
+ miss = [c for c in cases if not c[2]]
537
+ for name, expect, hit, got in cases:
538
+ print(f" {'✓' if hit else '✗'} {name:<16} 期望 {expect}"
539
+ + ('' if hit else f" 实际命中:{sorted(got)[:6]}"))
540
+ if miss:
541
+ fails += 1
542
+ print(f' ✗ {len(miss)}/{len(cases)} 条规则是瞎的')
543
+ else:
544
+ print(f' ✓ {len(cases)}/{len(cases)} 条规则全部命中')
545
+
546
+ print('\n[3/4] 反误报:合规写法不得被判成问题')
547
+ negs = negative(workdir)
548
+ for name, ok, bad, _got in negs:
549
+ print(f" {'✓' if ok else '✗'} {name:<16}" + ('' if ok else f' 被误报:{bad}'))
550
+ if any(not ok for _n, ok, _b, _g in negs):
551
+ fails += 1
552
+ else:
553
+ print(f' ✓ {len(negs)}/{len(negs)} 项合规写法零误报')
554
+
555
+ ok_bom, why = bom_markdown(workdir)
556
+ print(f" {'✓' if ok_bom else '✗'} {'带 BOM 的 Markdown 不吃标题':<16} {why if not ok_bom else ''}")
557
+ if not ok_bom:
558
+ fails += 1
559
+ ok_x, why = xref_markdown(workdir)
560
+ print(f" {'✓' if ok_x else '✗'} {'交叉引用换成真实编号':<16} {why if not ok_x else ''}")
561
+ if not ok_x:
562
+ fails += 1
563
+ ok_h, why = heading_shift_markdown(workdir)
564
+ print(f" {'✓' if ok_h else '✗'} {'章从 ## 起时层级上提':<16} {why if not ok_h else ''}")
565
+ if not ok_h:
566
+ fails += 1
567
+ ok_l, why = landscape_markdown(workdir)
568
+ print(f" {'✓' if ok_l else '✗'} {'横排插图不判超宽、过密图报磅数':<16} {why if not ok_l else ''}")
569
+ if not ok_l:
570
+ fails += 1
571
+
572
+ print('\n[4/4] 校正闭环:fix 后必须真干净,且不得修出新必修')
573
+ fr = fix_round(workdir, cases)
574
+ for name, ok, why in fr:
575
+ print(f" {'✓' if ok else '✗'} {name:<16} {why}")
576
+ if any(not ok for _n, ok, _w in fr):
577
+ fails += 1
578
+
579
+ if not keep:
580
+ shutil.rmtree(workdir, ignore_errors=True)
581
+ print('\n' + ('全部通过。' if not fails else f'有 {fails} 个环节未通过。'))
582
+ return 1 if fails else 0
583
+
584
+
585
+ if __name__ == '__main__':
586
+ sys.exit(main(sys.argv[1:]))