@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
@@ -0,0 +1,389 @@
1
+ # 表格引擎压力测试台:正向跑批 + **反向造缺陷** + 反误报 + 自动校正闭环。
2
+ #
3
+ # 为什么必须有反向:一个只会说「未发现问题」的体检器和没有体检器是一回事,
4
+ # 而且更糟——它给了人一个虚假的「已检查」印象。Excel 尤其如此:它的事故不改变外观,
5
+ # 只改变数字,肉眼根本复核不了,全靠这一层证明检查真的会响。
6
+ #
7
+ # 反误报同等重要,方向相反:分组小计用 SUBTOTAL、标题横幅在表头之上、说明页没有筛选,
8
+ # 这些都是**正确做法**,把它们判成毛病等于逼人把规范写法改坏。
9
+ #
10
+ # 用法:python xlsx_battery.py [份数] [--keep]
11
+ # 改了 xlsx_layout / xlsx_build / xlsx_check 任何一个,**必须重跑**。
12
+
13
+ import os
14
+ import random
15
+ import shutil
16
+ import sys
17
+ import tempfile
18
+ from collections import Counter
19
+
20
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
21
+ from openpyxl import load_workbook # noqa: E402
22
+ from openpyxl.styles import Font # noqa: E402
23
+ from openpyxl.utils import get_column_letter # noqa: E402
24
+
25
+ import xlsx_build as B # noqa: E402
26
+ import xlsx_check as C # noqa: E402
27
+ import xlsx_layout as L # noqa: E402
28
+
29
+ REGIONS = ['华东', '华南', '华北', '西南', '东北', '西北']
30
+ PROVS = ['上海', '江苏', '广东', '福建', '北京', '四川', '辽宁', '陕西', '浙江', '山东']
31
+ METRICS = [('销售额', 'amount'), ('成本', 'amount'), ('毛利', 'amount'),
32
+ ('数量', 'int'), ('完成率', 'percent'), ('客户数', 'int')]
33
+
34
+
35
+ def random_spec(rng):
36
+ prof = rng.choice(['report', 'plain', 'finance'])
37
+ n_sheet = rng.randint(1, 3)
38
+ sheets = []
39
+ for k in range(n_sheet):
40
+ mets = rng.sample(METRICS, rng.randint(1, 4))
41
+ cols = [{'key': 'region', 'header': '大区'}, {'key': 'prov', 'header': '省份'}]
42
+ cols += [{'key': f'm{i}', 'header': h, 'format': f} for i, (h, f) in enumerate(mets)]
43
+ rows = []
44
+ for _ in range(rng.randint(2, 40)):
45
+ r = [rng.choice(REGIONS), rng.choice(PROVS)]
46
+ for _h, f in mets:
47
+ r.append(round(rng.uniform(0, 1), 4) if f == 'percent'
48
+ else (rng.randint(1, 9999) if f == 'int' else round(rng.uniform(-5e4, 5e6), 2)))
49
+ rows.append(r)
50
+ s = {'name': f'表{k+1}_{rng.choice(["明细","汇总","台账"])}', 'columns': cols, 'rows': rows}
51
+ if rng.random() < 0.5:
52
+ s['title'] = f'{rng.choice(REGIONS)}区域{rng.choice(["销售","成本","经营"])}明细'
53
+ if rng.random() < 0.4:
54
+ s['note'] = '口径:含税;单位万元'
55
+ if rng.random() < 0.5:
56
+ s['total'] = {'label': '合计'}
57
+ if rng.random() < 0.35:
58
+ s['group_by'] = 'region'
59
+ if rng.random() < 0.3 and mets:
60
+ s['chart'] = {'type': rng.choice(['bar', 'line']), 'cat': 'prov', 'values': ['m0']}
61
+ if rng.random() < 0.3 and mets:
62
+ s['conditional'] = [{'col': 'm0', 'type': rng.choice(['data_bar', 'color_scale'])}]
63
+ sheets.append(s)
64
+ spec = {'profile': prof, 'sheets': sheets}
65
+ if rng.random() < 0.4:
66
+ spec['cover'] = {'title': '压测报表', 'fields': [['编制单位', '压测'], ['口径', '含税']]}
67
+ return spec
68
+
69
+
70
+ def forward(n, workdir, verbose=True):
71
+ rng = random.Random(20260901)
72
+ bad, worst = 0, []
73
+ for i in range(n):
74
+ spec = random_spec(rng)
75
+ out = os.path.join(workdir, f'f{i}.xlsx')
76
+ try:
77
+ _p, res, _w = B.build(spec, out)
78
+ except Exception as e:
79
+ bad += 1
80
+ worst.append((f'f{i}', f'生成异常 {type(e).__name__}: {e}'))
81
+ continue
82
+ errs = [x for x in res['issues'] if x['level'] == 'error']
83
+ if errs:
84
+ bad += 1
85
+ worst.append((f"f{i}({spec['profile']})",
86
+ '; '.join(sorted({x['kind'] + ':' + x['msg'][:44] for x in errs}))))
87
+ os.remove(out)
88
+ if verbose and (i + 1) % 40 == 0:
89
+ print(f' 正向 {i+1}/{n} …')
90
+ return bad, worst
91
+
92
+
93
+ # ── 反向:每条规则配一个「故意做坏」的工作簿 ───────────────────────────────
94
+ def _base(workdir, name, rows=None, **kw):
95
+ spec = {'profile': 'report', 'inspect': False, 'sheets': [{
96
+ 'name': '明细',
97
+ 'columns': [{'key': 'prov', 'header': '省份'},
98
+ {'key': 'amt', 'header': '销售额', 'format': 'amount'},
99
+ {'key': 'qty', 'header': '数量', 'format': 'int'}],
100
+ 'rows': rows or [[PROVS[i % len(PROVS)], round(1000 + i * 137.5, 2), i + 1] for i in range(24)],
101
+ **kw}]}
102
+ out = os.path.join(workdir, name)
103
+ B.build(spec, out)
104
+ return out
105
+
106
+
107
+ def _kinds(path):
108
+ return {i['kind'] for i in C.inspect(path)['issues']}
109
+
110
+
111
+ def reverse(workdir):
112
+ cases = []
113
+
114
+ def case(name, expect, mutate, **kw):
115
+ """造一份缺陷件并断言体检会响。
116
+
117
+ **先验证用例真的改到了文件**:openpyxl 有 ws.cell(r,c,None) 这种「看着在写、其实是
118
+ 空操作」的陷阱,用例没造出缺陷却被报成「规则是瞎的」——两件事性质完全不同,
119
+ 混在一起会让人去改本来没问题的检查规则。"""
120
+ p = _base(workdir, name + '.xlsx', **kw)
121
+ before = open(p, 'rb').read()
122
+ wb = load_workbook(p)
123
+ mutate(wb)
124
+ wb.save(p)
125
+ if open(p, 'rb').read() == before:
126
+ cases.append((name, expect, False, {'<用例没改到文件>'}))
127
+ return p
128
+ got = _kinds(p)
129
+ cases.append((name, expect, expect in got, got))
130
+ return p
131
+
132
+ def col(wb, letter, first=2, last=25):
133
+ return [wb['明细'][f'{letter}{r}'] for r in range(first, last + 1)]
134
+
135
+ # 数据类
136
+ case('数字存成文本', 'num-as-text',
137
+ lambda wb: [setattr(c, 'value', f'{c.value:,.2f}') for c in col(wb, 'B') if c.value is not None])
138
+ case('数字文本混杂', 'mixed-type',
139
+ lambda wb: [setattr(c, 'value', f'{c.value:,.2f}') for c in col(wb, 'B', 2, 12) if c.value is not None])
140
+ case('日期存成文本', 'date-as-text',
141
+ lambda wb: [setattr(c, 'value', f'2026-{(i % 12) + 1:02d}-01')
142
+ for i, c in enumerate(col(wb, 'A'))])
143
+ case('首尾空格', 'whitespace',
144
+ lambda wb: [setattr(c, 'value', str(c.value) + ' ') for c in col(wb, 'A', 2, 8)])
145
+ case('完全重复行', 'dup-row',
146
+ lambda wb: [wb['明细'].cell(r, j, wb['明细'].cell(2, j).value)
147
+ for r in (20, 21) for j in (1, 2, 3)])
148
+ case('编号列存成数字', 'id-as-number',
149
+ lambda wb: [wb['明细'].cell(1, 1, '客户编号')]
150
+ + [setattr(c, 'value', 100000 + i) for i, c in enumerate(col(wb, 'A'))])
151
+
152
+ # 结构类
153
+ def merge_body(wb):
154
+ wb['明细'].merge_cells('A5:A8')
155
+ case('数据区合并单元格', 'merged-cells', merge_body)
156
+ case('隐藏行', 'hidden-row',
157
+ lambda wb: [wb['明细'].row_dimensions[r].__setattr__('hidden', True) for r in (5, 6, 7)])
158
+ case('隐藏列', 'hidden-col',
159
+ lambda wb: wb['明细'].column_dimensions['C'].__setattr__('hidden', True))
160
+ case('重名列', 'dup-header', lambda wb: wb['明细'].cell(1, 3, '销售额'))
161
+ # 注意:ws.cell(r,c,None) 是空操作(openpyxl 把 None 当「没传值」),必须显式赋 .value
162
+ case('空列名', 'blank-header', lambda wb: setattr(wb['明细'].cell(1, 3), 'value', None))
163
+ case('隐藏工作表', 'hidden-sheet',
164
+ lambda wb: (wb.create_sheet('偷偷藏的'), setattr(wb['偷偷藏的'], 'sheet_state', 'hidden')))
165
+ case('默认工作表名', 'default-name', lambda wb: wb.create_sheet('Sheet2'))
166
+ case('常量小计混在明细', 'subtotal-inline',
167
+ lambda wb: (wb['明细'].insert_rows(10),
168
+ wb['明细'].cell(10, 1, '小计'), wb['明细'].cell(10, 2, 12345.0)))
169
+
170
+ # 公式类
171
+ case('错误值', 'error-value',
172
+ lambda wb: [wb['明细'].cell(r, 2, '#DIV/0!') for r in (4, 5)])
173
+ case('列内公式不一致', 'mixed-formula',
174
+ lambda wb: [wb['明细'].cell(r, 3, f'=B{r}/100') for r in range(2, 14)])
175
+ case('求和范围没覆盖末行', 'sum-short',
176
+ lambda wb: wb['明细'].cell(27, 2, '=SUM(B2:B10)'))
177
+ case('引用不存在的工作表', 'bad-sheet-ref',
178
+ lambda wb: wb['明细'].cell(27, 2, "='不存在的表'!B2"))
179
+ case('外部工作簿链接', 'external-link',
180
+ lambda wb: wb['明细'].cell(27, 2, "=[预算.xlsx]Sheet1!B2"))
181
+
182
+ # 勾稽类
183
+ def hardcode_total(wb):
184
+ ws = wb['明细']
185
+ ws.cell(26, 1, '合计')
186
+ ws.cell(26, 2, 99999.0) # 写死且与明细对不上
187
+ case('合计写死成常量', 'hardcoded-total', hardcode_total)
188
+ case('合计对不上明细', 'total-mismatch', hardcode_total)
189
+ case('占比合计不等于100%', 'pct-sum',
190
+ lambda wb: [wb['明细'].cell(1, 3, '占比')]
191
+ + [(setattr(wb['明细'].cell(r, 3), 'value', 0.04),
192
+ setattr(wb['明细'].cell(r, 3), 'number_format', '0.0%'))
193
+ for r in range(2, 26)])
194
+
195
+ # 单列清单**不是**表单:早先 max_col<=2 一并算表单,任何单列工作表都被整组跳过检查。
196
+ case('单列表里的超长单元格', 'long-cell',
197
+ lambda wb: (wb.create_sheet('清单'),
198
+ wb['清单'].append(['说明']),
199
+ [wb['清单'].append(['长' * 900]) for _ in range(3)]))
200
+
201
+ # 两列的数据表**不是**说明页——早先按列数判会把整组检查跳过,坏表反而查不出问题
202
+ def two_col_bad(wb):
203
+ wb.create_sheet('两列表')
204
+ ws = wb['两列表']
205
+ ws.append(['省份', '销售额'])
206
+ for i in range(20):
207
+ ws.append([f'省{i}', f'{1000 + i * 137.5:,.2f}'])
208
+ ws.append(['合计', 99999.0])
209
+ case('两列数据表也要查', 'hardcoded-total', two_col_bad)
210
+
211
+ # 呈现类
212
+ case('列宽不足显示井号', 'too-narrow',
213
+ lambda wb: wb['明细'].column_dimensions['B'].__setattr__('width', 5))
214
+ case('同列格式不统一', 'format-mix',
215
+ lambda wb: [setattr(c, 'number_format', '0') for c in col(wb, 'B', 2, 10)])
216
+ case('金额列无千分位', 'no-thousands',
217
+ lambda wb: [setattr(c, 'number_format', 'General') for c in col(wb, 'B')])
218
+ case('没有冻结窗格', 'no-freeze', lambda wb: setattr(wb['明细'], 'freeze_panes', None))
219
+
220
+ def wide_print(wb):
221
+ ws = wb['明细']
222
+ ws.sheet_properties.pageSetUpPr = None
223
+ ws.page_setup.fitToWidth = None
224
+ for j in range(1, 4):
225
+ ws.column_dimensions[get_column_letter(j)].width = 60
226
+ case('打印会横跨多页', 'print-wide', wide_print)
227
+ return cases
228
+
229
+
230
+ # ── 反误报:正确做法**绝不能**被判成问题 ───────────────────────────────────
231
+ def negative(workdir):
232
+ cases = []
233
+
234
+ def nocase(name, forbidden, spec):
235
+ out = os.path.join(workdir, 'neg-' + name + '.xlsx')
236
+ B.build(spec, out)
237
+ got = _kinds(out)
238
+ bad = [k for k in forbidden if k in got]
239
+ cases.append((name, not bad, bad, got))
240
+
241
+ # ── 旧版 .xls:读进来只有值,公式/列宽/合并全丢了 ──────────────────────
242
+ # 这些层必须**停掉**而不是照查:照查会凭空报出「合计是写死的常量」「缺冻结窗格」,
243
+ # 而报告看起来跟真的一样。真实归档里 .xls 占一半,这条错了会污染半数结论。
244
+ def legacy_guard():
245
+ out = os.path.join(workdir, 'neg-legacy.xlsx')
246
+ B.build({'profile': 'report', 'sheets': [{
247
+ 'name': '明细',
248
+ 'columns': [{'key': 'p', 'header': '省份'},
249
+ {'key': 'a', 'header': '销售额', 'format': 'amount'}],
250
+ 'rows': [[PROVS[i % len(PROVS)], round(100 + i * 7.5, 2)] for i in range(30)],
251
+ 'total': {'label': '总计'}}]}, out)
252
+ real = C.Book(out)
253
+ real.legacy = True # 只翻这一个开关,其余完全相同
254
+ got = {i['kind'] for i in
255
+ (C.check_data(real) + C.check_structure(real) + C.check_reconcile(real))}
256
+ banned = {'hardcoded-total', 'no-freeze', 'too-narrow', 'no-autofilter'}
257
+ bad = sorted(got & banned)
258
+ cases.append(('旧格式不得凭空报公式/呈现类', not bad, bad, got))
259
+ legacy_guard()
260
+
261
+ rows = [[REGIONS[i // 4], PROVS[i % len(PROVS)], round(1000 + i * 97.3, 2), i + 1]
262
+ for i in range(24)]
263
+ cols = [{'key': 'region', 'header': '大区'}, {'key': 'prov', 'header': '省份'},
264
+ {'key': 'amt', 'header': '销售额', 'format': 'amount'},
265
+ {'key': 'qty', 'header': '数量', 'format': 'int'}]
266
+
267
+ nocase('分组小计用SUBTOTAL', ['subtotal-inline', 'mixed-formula', 'hardcoded-total', 'total-mismatch'],
268
+ {'profile': 'report', 'sheets': [{'name': '明细', 'columns': cols, 'rows': rows,
269
+ 'group_by': 'region', 'total': {'label': '总计'}}]})
270
+ nocase('标题横幅在表头之上', ['header-not-first', 'merged-cells'],
271
+ {'profile': 'report', 'sheets': [{'name': '明细', 'title': '2026 年 8 月销售明细',
272
+ 'note': '口径:含税,单位万元',
273
+ 'columns': cols, 'rows': rows}]})
274
+ nocase('说明页两列表单', ['no-freeze', 'no-filter', 'print-wide', 'header-not-first', 'merged-cells'],
275
+ {'profile': 'report', 'cover': {'title': '报表说明', 'fields': [
276
+ ['编制单位', '数据一部'], ['口径', '含税'], ['来源', 'ERP']]},
277
+ 'sheets': [{'name': '明细', 'columns': cols, 'rows': rows}]})
278
+ nocase('编号列锁成文本', ['id-as-number', 'num-as-text', 'mixed-type'],
279
+ {'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
280
+ {'key': 'no', 'header': '订单编号', 'format': 'text'},
281
+ {'key': 'amt', 'header': '销售额', 'format': 'amount'}],
282
+ 'rows': [[f'{i:08d}', round(1000 + i * 13.5, 2)] for i in range(24)]}]})
283
+ # 建表引擎按列格式写类型(date 列写真日期、amount 列的「1,234.56」写成数字)。
284
+ # 写错方向会静默毁数据:把订单号转成数字,前导零当场消失。这两条守着那个转换。
285
+ nocase('编号列写文本不被转数字', ['num-as-text', 'id-as-number', 'mixed-type'],
286
+ {'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
287
+ {'key': 'no', 'header': '订单编号', 'format': 'text'},
288
+ {'key': 'amt', 'header': '销售额', 'format': 'amount'}],
289
+ 'rows': [[f'{i:05d}', f'{1000 + i * 11.5:,.2f}'] for i in range(1, 25)]}]})
290
+ nocase('日期列写成真日期', ['date-as-text', 'mixed-type'],
291
+ {'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
292
+ {'key': 'd', 'header': '记账日期', 'format': 'date'},
293
+ {'key': 'amt', 'header': '金额', 'format': 'amount'}],
294
+ 'rows': [[f'2026-{i % 12 + 1:02d}-0{i % 9 + 1}', 1000.0 + i] for i in range(1, 25)]}]})
295
+ nocase('短表不必冻结', ['no-freeze', 'no-filter'],
296
+ {'profile': 'report', 'sheets': [{'name': '小表', 'columns': cols, 'rows': rows[:6]}]})
297
+ nocase('大额金额列宽度够', ['too-narrow'],
298
+ {'profile': 'report', 'sheets': [{'name': '明细', 'columns': [
299
+ {'key': 'p', 'header': '省份'}, {'key': 'a', 'header': '销售额', 'format': 'amount'}],
300
+ 'rows': [[PROVS[i % 10], round(1234567890.12 + i, 2)] for i in range(24)]}]})
301
+ return cases
302
+
303
+
304
+ # ── 校正闭环 ───────────────────────────────────────────────────────────────
305
+ FIXABLE = ['数字存成文本', '首尾空格', '数据区合并单元格', '求和范围没覆盖末行',
306
+ '合计写死成常量', '合计对不上明细', '列宽不足显示井号', '同列格式不统一',
307
+ '金额列无千分位', '没有冻结窗格', '打印会横跨多页', '编号列存成数字']
308
+
309
+
310
+ def fix_round(workdir, cases):
311
+ out = []
312
+ for name, expect, hit, _got in cases:
313
+ if name not in FIXABLE or not hit:
314
+ continue
315
+ src = os.path.join(workdir, name + '.xlsx')
316
+ try:
317
+ dst, done = C.fix(src, os.path.join(workdir, name + '-fixed.xlsx'))
318
+ except Exception as e:
319
+ out.append((name, False, f'修复异常 {type(e).__name__}: {e}'))
320
+ continue
321
+ after, before = C.inspect(dst), C.inspect(src)
322
+ kinds = {i['kind'] for i in after['issues']}
323
+ new_err = ({i['kind'] for i in after['issues'] if i['level'] == 'error'}
324
+ - {i['kind'] for i in before['issues'] if i['level'] == 'error'})
325
+ ok = expect not in kinds and not new_err
326
+ why = (f'修完仍命中 {expect}' if expect in kinds else
327
+ (f'修出新必修 {sorted(new_err)}' if new_err else
328
+ '已修(' + '、'.join(f'{k}{v}' for k, v in done.items()) + ')'))
329
+ out.append((name, ok, why))
330
+ return out
331
+
332
+
333
+ def main(argv):
334
+ n = 120
335
+ for a in argv:
336
+ if a.isdigit():
337
+ n = int(a)
338
+ keep = '--keep' in argv
339
+ workdir = os.path.join(os.getcwd(), '_xbattery') if keep else tempfile.mkdtemp(prefix='xlsxbat-')
340
+ os.makedirs(workdir, exist_ok=True)
341
+ print(f'压测目录:{workdir}')
342
+ fails = 0
343
+
344
+ print(f'\n[1/4] 正向:随机内容 × 三档规范 × {n} 份,断言合规报表零必修')
345
+ bad, worst = forward(n, workdir)
346
+ if bad:
347
+ fails += 1
348
+ print(f' ✗ {bad}/{n} 份出现必修项:')
349
+ for name, why in worst[:8]:
350
+ print(f' {name} → {why}')
351
+ else:
352
+ print(f' ✓ {n} 份全部零必修')
353
+
354
+ print('\n[2/4] 反向:每条规则配一个故意做坏的工作簿,断言体检真的会响')
355
+ cases = reverse(workdir)
356
+ miss = [c for c in cases if not c[2]]
357
+ for name, expect, hit, got in cases:
358
+ print(f" {'✓' if hit else '✗'} {name:<18} 期望 {expect}"
359
+ + ('' if hit else f" 实际命中:{sorted(got)[:7]}"))
360
+ if miss:
361
+ fails += 1
362
+ print(f' ✗ {len(miss)}/{len(cases)} 条规则是瞎的')
363
+ else:
364
+ print(f' ✓ {len(cases)}/{len(cases)} 条规则全部命中')
365
+
366
+ print('\n[3/4] 反误报:正确做法不得被判成问题')
367
+ negs = negative(workdir)
368
+ for name, ok, bad2, _got in negs:
369
+ print(f" {'✓' if ok else '✗'} {name:<18}" + ('' if ok else f' 被误报:{bad2}'))
370
+ if any(not ok for _n, ok, _b, _g in negs):
371
+ fails += 1
372
+ else:
373
+ print(f' ✓ {len(negs)}/{len(negs)} 项正确做法零误报')
374
+
375
+ print('\n[4/4] 校正闭环:fix 后必须真干净,且不得修出新必修')
376
+ fr = fix_round(workdir, cases)
377
+ for name, ok, why in fr:
378
+ print(f" {'✓' if ok else '✗'} {name:<18} {why}")
379
+ if any(not ok for _n, ok, _w in fr):
380
+ fails += 1
381
+
382
+ if not keep:
383
+ shutil.rmtree(workdir, ignore_errors=True)
384
+ print('\n' + ('全部通过。' if not fails else f'有 {fails} 个环节未通过。'))
385
+ return 1 if fails else 0
386
+
387
+
388
+ if __name__ == '__main__':
389
+ sys.exit(main(sys.argv[1:]))