@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,580 @@
1
+ #!/usr/bin/env python3
2
+ # 项目事实清单:扫一遍代码库,给「可研 / 概要设计 / 项目总结 / 结题报告」这类文档取证。只用标准库。
3
+ #
4
+ # 为什么要有它:写项目自己的报告时,模型最容易做的是凭印象报数——「微服务若干个」「代码约十万行」
5
+ # 「支持数十种模型」。评审一问来源就露馅,前后章节报的数还对不上。这里把能从代码里数出来的事实一次数清,
6
+ # 每条都带来源路径,正文引用时写得出「据 huicai-backend/pom.xml」。
7
+ #
8
+ # 纪律:
9
+ # · 只出计数、名称、版本、端口和来源路径;**配置值一律不读出**(application.yml 里常有默认口令、密钥),
10
+ # 端口与服务名走白名单键;compose 只取服务名、镜像、端口映射,环境变量一个字都不出;
11
+ # · 同步副本只算一次:多端工程常把同一份核心代码拷到几个子工程里,按内容哈希去重,
12
+ # 否则代码行数会被重复计入好几倍;
13
+ # · 人名不出:git 只报提交数、起止日期与贡献者**人数**。
14
+ #
15
+ # 用法:python project_facts.py <根目录> 最后一行输出 JSON(含 markdown 事实表与模块树 spec)。
16
+
17
+ import hashlib
18
+ import json
19
+ import os
20
+ import re
21
+ import subprocess
22
+ import sys
23
+ import xml.etree.ElementTree as ET
24
+
25
+ SKIP_DIRS = {
26
+ '.git', '.svn', '.hg', 'node_modules', 'target', 'dist', 'build', 'out', '.next', '.nuxt', 'coverage',
27
+ '.venv', 'venv', 'env', '__pycache__', '.idea', '.vscode', '.gradle', 'vendor', '.cache', '.turbo',
28
+ '.parcel-cache', '.huicai', '.pytest_cache', '.mypy_cache', 'bower_components', '.terraform', 'Pods',
29
+ 'release', 'releases', '.output', 'tmp', '.tmp', 'logs',
30
+ }
31
+ LANGS = {
32
+ '.java': 'Java', '.kt': 'Kotlin', '.kts': 'Kotlin', '.scala': 'Scala', '.groovy': 'Groovy',
33
+ '.js': 'JavaScript', '.mjs': 'JavaScript', '.cjs': 'JavaScript', '.jsx': 'JavaScript',
34
+ '.ts': 'TypeScript', '.tsx': 'TypeScript', '.mts': 'TypeScript', '.vue': 'Vue', '.svelte': 'Svelte',
35
+ '.py': 'Python', '.go': 'Go', '.rs': 'Rust', '.cs': 'C#', '.c': 'C/C++', '.cc': 'C/C++', '.cpp': 'C/C++',
36
+ '.h': 'C/C++', '.hpp': 'C/C++', '.swift': 'Swift', '.m': 'Objective-C', '.php': 'PHP', '.rb': 'Ruby',
37
+ '.dart': 'Dart', '.lua': 'Lua', '.sql': 'SQL', '.sh': 'Shell', '.bash': 'Shell', '.ps1': 'PowerShell',
38
+ '.css': 'CSS/样式', '.scss': 'CSS/样式', '.less': 'CSS/样式', '.html': 'HTML',
39
+ }
40
+ MAX_FILE = 2 * 1024 * 1024
41
+ MAX_FILES = 120000
42
+
43
+ TEST_PATH = re.compile(r'(^|/)(test|tests|__tests__|spec|e2e)(/|$)', re.I)
44
+ TEST_NAME = re.compile(r'(Tests?\.(java|kt|cs)$|\.(test|spec)\.[cm]?[jt]sx?$|^test_.*\.py$|_test\.(py|go)$)')
45
+ CASE_PAT = {
46
+ 'Java': re.compile(r'@(Test|ParameterizedTest)\b'), 'Kotlin': re.compile(r'@Test\b'),
47
+ 'JavaScript': re.compile(r'^\s*(?:it|test)(?:\.each\([^)]*\))?\s*\(', re.M),
48
+ 'TypeScript': re.compile(r'^\s*(?:it|test)(?:\.each\([^)]*\))?\s*\(', re.M),
49
+ 'Python': re.compile(r'^\s*(?:async\s+)?def\s+test_\w*\s*\(', re.M), 'Go': re.compile(r'^func\s+Test\w*\(', re.M),
50
+ 'C#': re.compile(r'\[(Fact|Theory|Test|TestMethod)\b'),
51
+ }
52
+ JAVA_MAP = re.compile(r'@(Get|Post|Put|Delete|Patch)Mapping\b|@RequestMapping\s*\([^)]*method\s*=')
53
+ JS_ROUTE = re.compile(r'\b(?:app|router|server|fastify)\.(?:get|post|put|delete|patch)\s*\(\s*[\'"`]/')
54
+ PY_ROUTE = re.compile(r'@\w+\.(?:get|post|put|delete|patch|route)\s*\(\s*[\'"]/')
55
+ CREATE_TABLE = re.compile(r'\bcreate\s+(?:unlogged\s+|temporary\s+|temp\s+)?table\s+(?:if\s+not\s+exists\s+)?'
56
+ r'((?:[`"\[]?\w+[`"\]]?\.)?[`"\[]?\w+[`"\]]?)', re.I)
57
+ ROUTE_PATH = re.compile(r'\bpath\s*:\s*[\'"`]')
58
+
59
+ FRAMEWORKS_JS = {
60
+ 'vue': 'Vue', 'react': 'React', 'electron': 'Electron', 'vite': 'Vite', 'element-plus': 'Element Plus',
61
+ 'ant-design-vue': 'Ant Design Vue', 'antd': 'Ant Design', 'express': 'Express', 'koa': 'Koa',
62
+ 'fastify': 'Fastify', 'typescript': 'TypeScript', 'pinia': 'Pinia', 'vuex': 'Vuex', 'vue-router': 'Vue Router',
63
+ 'echarts': 'ECharts', 'three': 'three.js', 'axios': 'Axios', 'webpack': 'webpack', 'next': 'Next.js',
64
+ 'nuxt': 'Nuxt', '@nestjs/core': 'NestJS', 'playwright': 'Playwright', 'jest': 'Jest', 'vitest': 'Vitest',
65
+ 'tailwindcss': 'Tailwind CSS', 'monaco-editor': 'Monaco Editor', 'xterm': 'xterm.js', '@xterm/xterm': 'xterm.js',
66
+ 'node-pty': 'node-pty', 'electron-builder': 'electron-builder', 'socket.io': 'Socket.IO', 'openai': 'OpenAI SDK',
67
+ '@modelcontextprotocol/sdk': 'MCP SDK', 'better-sqlite3': 'SQLite', 'markdown-it': 'markdown-it',
68
+ 'ink': 'Ink(终端界面)', 'commander': 'commander', 'yargs': 'yargs',
69
+ }
70
+ FRAMEWORKS_JAVA = {
71
+ 'spring-cloud-starter-gateway': 'Spring Cloud Gateway', 'spring-cloud-starter-openfeign': 'OpenFeign',
72
+ 'spring-cloud-starter-alibaba-nacos-discovery': 'Nacos 注册中心', 'spring-cloud-starter-alibaba-nacos-config': 'Nacos 配置中心',
73
+ 'spring-cloud-starter-alibaba-sentinel': 'Sentinel', 'mybatis-plus-spring-boot3-starter': 'MyBatis-Plus',
74
+ 'mybatis-plus-boot-starter': 'MyBatis-Plus', 'spring-boot-starter-data-redis': 'Redis',
75
+ 'postgresql': 'PostgreSQL', 'mysql-connector-j': 'MySQL', 'mysql-connector-java': 'MySQL',
76
+ 'shardingsphere-jdbc-core': 'ShardingSphere', 'shardingsphere-jdbc': 'ShardingSphere',
77
+ 'spring-kafka': 'Kafka', 'rocketmq-spring-boot-starter': 'RocketMQ', 'spring-boot-starter-amqp': 'RabbitMQ',
78
+ 'elasticsearch-java': 'Elasticsearch', 'minio': 'MinIO', 'seata-spring-boot-starter': 'Seata',
79
+ 'knife4j-openapi3-jakarta-spring-boot-starter': 'Knife4j', 'springdoc-openapi-starter-webmvc-ui': 'springdoc',
80
+ 'spring-boot-starter-security': 'Spring Security', 'jjwt-api': 'JWT', 'spring-boot-starter-webflux': 'WebFlux',
81
+ 'spring-ai-core': 'Spring AI', 'langchain4j': 'LangChain4j', 'pgvector': 'pgvector',
82
+ 'spring-boot-starter-actuator': 'Actuator', 'micrometer-registry-prometheus': 'Prometheus',
83
+ 'caffeine': 'Caffeine', 'redisson-spring-boot-starter': 'Redisson', 'xxl-job-core': 'XXL-JOB',
84
+ }
85
+
86
+
87
+ def rel(root, p):
88
+ return os.path.relpath(p, root).replace(os.sep, '/')
89
+
90
+
91
+ def read_text(p, limit=MAX_FILE):
92
+ try:
93
+ if os.path.getsize(p) > limit:
94
+ return None
95
+ with open(p, 'rb') as f:
96
+ raw = f.read()
97
+ if b'\0' in raw[:4096]:
98
+ return None
99
+ return raw.decode('utf-8', errors='replace')
100
+ except OSError:
101
+ return None
102
+
103
+
104
+ def walk(root):
105
+ n = 0
106
+ for d, dirs, files in os.walk(root):
107
+ dirs[:] = sorted(x for x in dirs if x not in SKIP_DIRS and not x.startswith('.')
108
+ and not (x == 'node' and d.replace(os.sep, '/').endswith('/resources')))
109
+ for f in sorted(files):
110
+ n += 1
111
+ if n > MAX_FILES:
112
+ return
113
+ yield os.path.join(d, f)
114
+
115
+
116
+ # ── 小型 YAML 读取:只取白名单键,绝不整体解析输出 ────────────────────────────
117
+ def yaml_scalars(text, wanted):
118
+ out, stack = {}, []
119
+ for raw in text.splitlines():
120
+ s = raw.strip()
121
+ if s == '---':
122
+ stack = []
123
+ continue
124
+ if not s or s.startswith('#') or s.startswith('- '):
125
+ continue
126
+ line = re.sub(r'\s+#.*$', '', raw.rstrip())
127
+ m = re.match(r'^(\s*)([\w.\-]+)\s*:\s*(.*)$', line)
128
+ if not m:
129
+ continue
130
+ indent, key, val = len(m.group(1)), m.group(2), m.group(3).strip()
131
+ while stack and stack[-1][0] >= indent:
132
+ stack.pop()
133
+ path = '.'.join([k for _, k in stack] + [key])
134
+ if val in ('', '|', '>', '|-', '>-'):
135
+ stack.append((indent, key))
136
+ elif path in wanted and path not in out:
137
+ out[path] = val.strip('\'"')
138
+ return out
139
+
140
+
141
+ def placeholder_default(v):
142
+ """${SERVER_PORT:37005} → 37005(环境变量占位取默认值);没有默认值返回原样。"""
143
+ m = re.fullmatch(r'\$\{[^:}]+:([^}]*)\}', v or '')
144
+ return m.group(1) if m else v
145
+
146
+
147
+ def compose_services(text):
148
+ """docker-compose:只取服务名、镜像、端口映射。环境变量、卷、命令一律不读。"""
149
+ out, in_services, svc_indent, cur, in_ports = [], False, None, None, False
150
+ for raw in text.splitlines():
151
+ if not raw.strip() or raw.strip().startswith('#'):
152
+ continue
153
+ indent = len(raw) - len(raw.lstrip(' '))
154
+ s = raw.strip()
155
+ if indent == 0:
156
+ in_services = s.startswith('services:')
157
+ cur, in_ports = None, False
158
+ continue
159
+ if not in_services:
160
+ continue
161
+ if svc_indent is None:
162
+ svc_indent = indent
163
+ if indent == svc_indent and re.match(r'^[\w.\-]+\s*:\s*$', s):
164
+ cur = {'name': s.rstrip(':').strip(), 'image': '', 'ports': []}
165
+ out.append(cur)
166
+ in_ports = False
167
+ continue
168
+ if cur is None:
169
+ continue
170
+ if indent > svc_indent:
171
+ m = re.match(r'^image\s*:\s*(.+)$', s)
172
+ if m:
173
+ cur['image'] = m.group(1).strip().strip('\'"')
174
+ in_ports = False
175
+ continue
176
+ if re.match(r'^ports\s*:\s*$', s):
177
+ in_ports = True
178
+ continue
179
+ if in_ports and s.startswith('- '):
180
+ cur['ports'].append(s[2:].strip().strip('\'"'))
181
+ continue
182
+ if re.match(r'^[\w.\-]+\s*:', s):
183
+ in_ports = False
184
+ return out
185
+
186
+
187
+ def pom_info(p, root):
188
+ text = read_text(p)
189
+ if text is None:
190
+ return None
191
+ try:
192
+ t = ET.fromstring(text)
193
+ except ET.ParseError:
194
+ return None
195
+ ns = ''
196
+ m = re.match(r'\{(.*)\}', t.tag)
197
+ if m:
198
+ ns = '{' + m.group(1) + '}'
199
+
200
+ def g(el, path):
201
+ x = el.find('/'.join(ns + seg for seg in path.split('/')))
202
+ return x.text.strip() if x is not None and x.text else ''
203
+ info = {
204
+ 'path': rel(root, os.path.dirname(p)) or '.', 'kind': 'maven', 'source': rel(root, p),
205
+ 'artifactId': g(t, 'artifactId'), 'name': g(t, 'name'), 'description': g(t, 'description'),
206
+ 'version': g(t, 'version') or g(t, 'parent/version'),
207
+ 'parent': g(t, 'parent/artifactId'), 'parent_version': g(t, 'parent/version'),
208
+ }
209
+ props = t.find(ns + 'properties')
210
+ pv = {}
211
+ if props is not None:
212
+ for c in props:
213
+ k = c.tag.replace(ns, '')
214
+ if k in ('java.version', 'maven.compiler.source', 'spring-cloud.version', 'spring-cloud-alibaba.version',
215
+ 'spring-boot.version', 'mybatis-plus.version', 'kotlin.version'):
216
+ pv[k] = (c.text or '').strip()
217
+ info['properties'] = pv
218
+ mods = []
219
+ for mm in re.finditer(r'<module>\s*([^<\s]+)\s*</module>[ \t]*(?:<!--\s*(.*?)\s*-->)?', text):
220
+ mods.append({'module': mm.group(1), 'comment': (mm.group(2) or '').strip()})
221
+ info['modules'] = mods
222
+ deps = set()
223
+ for dep in t.iter(ns + 'dependency'):
224
+ a = dep.find(ns + 'artifactId')
225
+ if a is not None and a.text:
226
+ deps.add(a.text.strip())
227
+ info['frameworks'] = sorted({FRAMEWORKS_JAVA[d] for d in deps if d in FRAMEWORKS_JAVA})
228
+ return info
229
+
230
+
231
+ def pkg_info(p, root):
232
+ text = read_text(p)
233
+ try:
234
+ j = json.loads(text or '')
235
+ except ValueError:
236
+ return None
237
+ deps = {}
238
+ for k in ('dependencies', 'devDependencies', 'peerDependencies'):
239
+ deps.update(j.get(k) or {})
240
+ fw = {}
241
+ for name, label in FRAMEWORKS_JS.items():
242
+ if name in deps:
243
+ fw[label] = re.sub(r'^[\^~>=<\s]+', '', str(deps[name]))
244
+ eng = (j.get('engines') or {}).get('node')
245
+ return {'path': rel(root, os.path.dirname(p)) or '.', 'kind': 'npm', 'source': rel(root, p),
246
+ 'name': j.get('name', ''), 'version': j.get('version', ''), 'description': j.get('description', ''),
247
+ 'frameworks': fw, 'node': eng or '', 'bin': sorted((j.get('bin') or {}).keys()) if isinstance(j.get('bin'), dict) else []}
248
+
249
+
250
+ def gradle_info(p, root):
251
+ text = read_text(p) or ''
252
+ plugins = sorted(set(re.findall(r'id\s*\(?\s*["\']([\w.\-]+)["\']\s*\)?(?:\s*version\s*["\']([\w.\-]+)["\'])?', text)))
253
+ kv = re.search(r'kotlin\s*\(\s*["\']jvm["\']\s*\)\s*version\s*["\']([\w.\-]+)', text)
254
+ ver = re.search(r'^\s*version\s*=\s*["\']([^"\']+)', text, re.M)
255
+ grp = re.search(r'^\s*group\s*=\s*["\']([^"\']+)', text, re.M)
256
+ return {'path': rel(root, os.path.dirname(p)) or '.', 'kind': 'gradle', 'source': rel(root, p),
257
+ 'name': os.path.basename(os.path.dirname(p)), 'version': ver.group(1) if ver else '',
258
+ 'group': grp.group(1) if grp else '', 'description': '',
259
+ 'frameworks': {pid: v for pid, v in plugins}, 'kotlin': kv.group(1) if kv else ''}
260
+
261
+
262
+ def git_stats(repo):
263
+ def run(*args):
264
+ try:
265
+ r = subprocess.run(['git', '-C', repo] + list(args), capture_output=True, text=True, timeout=20)
266
+ return r.stdout.strip() if r.returncode == 0 else ''
267
+ except (OSError, subprocess.TimeoutExpired):
268
+ return ''
269
+ commits = run('rev-list', '--count', 'HEAD')
270
+ if not commits:
271
+ return None
272
+ first = run('log', '--reverse', '--format=%ad', '--date=short')
273
+ first = first.splitlines()[0] if first else ''
274
+ authors = run('log', '--format=%ae')
275
+ return {'commits': int(commits), 'first_commit': first, 'last_commit': run('log', '-1', '--format=%ad', '--date=short'),
276
+ 'contributors': len(set(a.lower() for a in authors.splitlines() if a.strip())),
277
+ 'tags': len([x for x in run('tag').splitlines() if x.strip()])}
278
+
279
+
280
+ def md_title(p):
281
+ text = read_text(p, 512 * 1024) or ''
282
+ for line in text.splitlines()[:40]:
283
+ m = re.match(r'^#\s+(.+?)\s*#*\s*$', line)
284
+ if m:
285
+ return m.group(1).strip()
286
+ return os.path.splitext(os.path.basename(p))[0]
287
+
288
+
289
+ def scan(root):
290
+ root = os.path.abspath(root)
291
+ if not os.path.isdir(root):
292
+ raise ValueError(f'目录不存在:{root}')
293
+ lang = {}
294
+ test = {'files': 0, 'loc': 0, 'cases': 0, 'by_lang': {}}
295
+ dup = {'files': 0, 'loc': 0}
296
+ seen = {}
297
+ api = {'total': 0, 'controllers': 0, 'by_module': {}}
298
+ fe = {'routes': 0, 'views': 0, 'by_project': {}}
299
+ tables, sql_files = {}, 0
300
+ manifests, services, composes, docs = [], [], [], []
301
+ files_seen = 0
302
+ truncated = False
303
+
304
+ top_of = lambda rp: rp.split('/')[0] if '/' in rp else '.'
305
+
306
+ for p in walk(root):
307
+ files_seen += 1
308
+ rp = rel(root, p)
309
+ base = os.path.basename(p)
310
+ ext = os.path.splitext(base)[1].lower()
311
+ # 清单文件
312
+ if base == 'pom.xml':
313
+ i = pom_info(p, root)
314
+ if i:
315
+ manifests.append(i)
316
+ elif base == 'package.json':
317
+ i = pkg_info(p, root)
318
+ if i:
319
+ manifests.append(i)
320
+ elif base in ('build.gradle', 'build.gradle.kts'):
321
+ manifests.append(gradle_info(p, root))
322
+ elif re.fullmatch(r'(docker-)?compose(\.[\w-]+)?\.ya?ml|docker-compose(\.[\w-]+)?\.ya?ml', base):
323
+ t = read_text(p) or ''
324
+ sv = compose_services(t)
325
+ if sv:
326
+ composes.append({'file': rp, 'services': sv})
327
+ elif re.fullmatch(r'(application|bootstrap)(-[\w]+)?\.(ya?ml|properties)', base) and '/src/main/resources/' in '/' + rp:
328
+ t = read_text(p) or ''
329
+ if base.endswith('.properties'):
330
+ kv = dict(re.findall(r'^\s*(server\.port|spring\.application\.name)\s*[=:]\s*(\S+)', t, re.M))
331
+ else:
332
+ kv = yaml_scalars(t, {'server.port', 'spring.application.name'})
333
+ if kv:
334
+ services.append({'name': placeholder_default(kv.get('spring.application.name', '')),
335
+ 'port': placeholder_default(kv.get('server.port', '')), 'source': rp,
336
+ 'profile': (re.search(r'-(\w+)\.', base) or [None, ''])[1]})
337
+ if ext == '.md':
338
+ if re.search(r'(^|/)docs?(/|$)', rp, re.I) or '/' not in rp:
339
+ docs.append({'path': rp, 'title': md_title(p)})
340
+ continue
341
+ L = LANGS.get(ext)
342
+ if not L:
343
+ continue
344
+ if base.endswith(('.min.js', '.min.css', '.bundle.js')) or base in ('package-lock.json',):
345
+ continue
346
+ text = read_text(p)
347
+ if text is None:
348
+ continue
349
+ loc = sum(1 for line in text.splitlines() if line.strip())
350
+ if loc and len(text) / max(1, text.count('\n') + 1) > 400: # 平均每行 400+ 字符:压缩/生成物
351
+ continue
352
+ h = hashlib.md5(text.encode('utf-8', errors='replace')).hexdigest()
353
+ if h in seen: # 同步副本、拷贝:只算一次
354
+ dup['files'] += 1
355
+ dup['loc'] += loc
356
+ continue
357
+ seen[h] = rp
358
+ st = lang.setdefault(L, {'files': 0, 'loc': 0})
359
+ st['files'] += 1
360
+ st['loc'] += loc
361
+ is_test = bool(TEST_PATH.search(rp) or TEST_NAME.search(base))
362
+ if is_test:
363
+ test['files'] += 1
364
+ test['loc'] += loc
365
+ pat = CASE_PAT.get(L)
366
+ if pat:
367
+ c = len(pat.findall(text))
368
+ test['cases'] += c
369
+ test['by_lang'][L] = test['by_lang'].get(L, 0) + c
370
+ continue
371
+ mod = top_of(rp)
372
+ if L == 'Java':
373
+ if '@RestController' in text or re.search(r'@Controller\b', text):
374
+ api['controllers'] += 1
375
+ n = len(JAVA_MAP.findall(text))
376
+ if n:
377
+ key = rp.split('/src/')[0] if '/src/' in rp else mod
378
+ api['total'] += n
379
+ api['by_module'][key] = api['by_module'].get(key, 0) + n
380
+ elif L in ('JavaScript', 'TypeScript', 'Python'):
381
+ n = len((PY_ROUTE if L == 'Python' else JS_ROUTE).findall(text))
382
+ if n:
383
+ api['total'] += n
384
+ api['by_module'][mod] = api['by_module'].get(mod, 0) + n
385
+ if L in ('JavaScript', 'TypeScript') and re.search(r'(^|/)(router|routes)(/|\.)', rp):
386
+ n = len(ROUTE_PATH.findall(text))
387
+ if n and ('createRouter' in text or 'VueRouter' in text or 'createBrowserRouter' in text or 'routes' in text):
388
+ fe['routes'] += n
389
+ fe['by_project'].setdefault(mod, {'routes': 0, 'views': 0})['routes'] += n
390
+ if ext in ('.vue', '.tsx', '.jsx') and re.search(r'(^|/)(views|pages)/', rp):
391
+ fe['views'] += 1
392
+ fe['by_project'].setdefault(mod, {'routes': 0, 'views': 0})['views'] += 1
393
+ if L == 'SQL':
394
+ sql_files += 1
395
+ for mt in CREATE_TABLE.finditer(text):
396
+ name = re.sub(r'[`"\[\]]', '', mt.group(1)).split('.')[-1].lower()
397
+ tables.setdefault(name, rp)
398
+ if files_seen >= MAX_FILES:
399
+ truncated = True
400
+
401
+ # git:根目录与一、二级子目录里的仓库
402
+ repos = []
403
+ cands = [root] + [os.path.join(root, d) for d in sorted(os.listdir(root)) if os.path.isdir(os.path.join(root, d))]
404
+ for c in cands:
405
+ if os.path.exists(os.path.join(c, '.git')):
406
+ s = git_stats(c)
407
+ if s:
408
+ s['repo'] = rel(root, c) or '.'
409
+ repos.append(s)
410
+
411
+ return {'root': root, 'files_scanned': files_seen, 'truncated': truncated, 'lang': lang, 'test': test, 'dup': dup,
412
+ 'api': api, 'frontend': fe, 'tables': tables, 'sql_files': sql_files, 'manifests': manifests,
413
+ 'services': services, 'compose': composes, 'docs': docs, 'git': repos}
414
+
415
+
416
+ def build(root):
417
+ S = scan(root)
418
+ warnings = []
419
+ by_lang = sorted(({'lang': k, 'files': v['files'], 'loc': v['loc']} for k, v in S['lang'].items()),
420
+ key=lambda x: -x['loc'])
421
+ total_loc = sum(x['loc'] for x in by_lang)
422
+ prod_loc = total_loc - S['test']['loc']
423
+ poms = [m for m in S['manifests'] if m['kind'] == 'maven']
424
+ parents = [m for m in poms if m['modules']]
425
+ modules = []
426
+ for par in parents:
427
+ for mm in par['modules']:
428
+ child = next((c for c in poms if c['path'] == (par['path'] + '/' + mm['module'] if par['path'] != '.' else mm['module'])), None)
429
+ desc = mm['comment'] or (child or {}).get('description') or (child or {}).get('name') or ''
430
+ modules.append({'parent': par['path'], 'module': mm['module'], 'description': desc,
431
+ 'frameworks': (child or {}).get('frameworks', []),
432
+ 'source': (child or par)['source']})
433
+ # 同一服务名可能有 application.yml 与 application-dev.yml 等多份,取不带 profile 的那份
434
+ svc = {}
435
+ for s in S['services']:
436
+ k = s['name'] or s['source']
437
+ if k not in svc or not s['profile']:
438
+ svc[k] = s
439
+ services = sorted(svc.values(), key=lambda s: (s['port'] or 'zzzz', s['name']))
440
+ top_subprojects, by_path = [], {}
441
+ for m in S['manifests']:
442
+ depth = 0 if m['path'] == '.' else m['path'].count('/') + 1
443
+ if depth > 1 or (m['kind'] == 'maven' and m.get('parent') and any(
444
+ p['path'] != m['path'] and m['path'].startswith(p['path'] + '/') for p in parents)):
445
+ continue
446
+ if m['path'] in by_path: # 同一目录既有 build.gradle 又有 package.json:合并
447
+ t = by_path[m['path']]
448
+ t['kind'] = t['kind'] + '+' + m['kind']
449
+ t['source'] = t['source'] + '、' + m['source']
450
+ t['description'] = t.get('description') or m.get('description', '')
451
+ fw = dict(t['frameworks']) if isinstance(t['frameworks'], dict) else {k: '' for k in t['frameworks']}
452
+ fw.update(m['frameworks'] if isinstance(m['frameworks'], dict) else {k: '' for k in m['frameworks']})
453
+ t['frameworks'] = fw
454
+ t['node'] = t.get('node') or m.get('node', '')
455
+ continue
456
+ m = dict(m)
457
+ by_path[m['path']] = m
458
+ top_subprojects.append(m)
459
+
460
+ # ── markdown 事实表(每条带来源)──
461
+ rows = []
462
+
463
+ def add(k, v, src):
464
+ rows.append(f'| {k} | {v} | {src} |')
465
+ if top_subprojects:
466
+ add('子工程', f'{len(top_subprojects)} 个:' + '、'.join(f'{m["path"]}({m["kind"]})' for m in top_subprojects),
467
+ '各子工程的 pom.xml / package.json / build.gradle')
468
+ if modules:
469
+ add('后端模块(Maven)', f'{len(modules)} 个:' + '、'.join(x['module'] for x in modules),
470
+ '、'.join(sorted({p['source'] for p in parents})))
471
+ if services:
472
+ add('可独立部署的服务(含端口)', f'{len(services)} 个:' + '、'.join(
473
+ f'{s["name"] or "?"}{("(" + s["port"] + ")") if s["port"] else ""}' for s in services),
474
+ '各服务 src/main/resources/application.yml(只读 server.port 与 spring.application.name)')
475
+ for par in parents:
476
+ fw = []
477
+ if par.get('parent') == 'spring-boot-starter-parent' and par.get('parent_version'):
478
+ fw.append(f'Spring Boot {par["parent_version"]}')
479
+ pv = par.get('properties') or {}
480
+ for k, lab in (('spring-cloud.version', 'Spring Cloud'), ('spring-cloud-alibaba.version', 'Spring Cloud Alibaba'),
481
+ ('java.version', 'Java')):
482
+ if pv.get(k):
483
+ fw.append(f'{lab} {pv[k]}')
484
+ if fw:
485
+ add(f'后端技术栈({par["path"]})', '、'.join(fw), par['source'])
486
+ java_fw = sorted({f for m in poms for f in m.get('frameworks', [])})
487
+ if java_fw:
488
+ add('后端中间件与组件', '、'.join(java_fw), '各模块 pom.xml 依赖')
489
+ for m in top_subprojects:
490
+ if m['kind'] == 'maven' or not m['frameworks']:
491
+ continue
492
+ items = [f'{k} {v}'.strip() for k, v in list(m['frameworks'].items())[:8]]
493
+ if m.get('node'):
494
+ items.insert(0, f'Node.js {m["node"]}')
495
+ add(f'{m["path"]} 技术栈', '、'.join(items), m['source'])
496
+ add('代码规模(非空行,去重后)', f'{total_loc:,} 行,其中业务代码 {prod_loc:,} 行、测试代码 {S["test"]["loc"]:,} 行',
497
+ f'扫描 {S["files_scanned"]:,} 个文件')
498
+ add('主要语言', '、'.join(f'{x["lang"]} {x["loc"]:,} 行' for x in by_lang[:6]), '按扩展名统计')
499
+ if S['dup']['files']:
500
+ add('同步副本(未重复计入)', f'{S["dup"]["files"]:,} 个文件 / {S["dup"]["loc"]:,} 行', '内容哈希完全相同的文件')
501
+ if S['api']['total']:
502
+ add('HTTP 接口', f'约 {S["api"]["total"]:,} 个({S["api"]["controllers"]} 个控制器)',
503
+ '按 @Get/Post/Put/Delete/PatchMapping 与路由注册语句计数')
504
+ if S['frontend']['routes'] or S['frontend']['views']:
505
+ add('前端页面', f'路由约 {S["frontend"]["routes"]} 条、页面组件 {S["frontend"]["views"]} 个', 'router/ 与 views|pages/ 目录')
506
+ if S['tables']:
507
+ add('数据库表', f'{len(S["tables"])} 张({S["sql_files"]} 个 SQL 文件)', 'CREATE TABLE 语句去重')
508
+ if S['test']['files']:
509
+ add('自动化测试', f'{S["test"]["files"]:,} 个测试文件、约 {S["test"]["cases"]:,} 个用例', '测试目录与 *Test / *.test.* / test_*.py')
510
+ if S['compose']:
511
+ n = sum(len(c['services']) for c in S['compose'])
512
+ add('容器编排', f'{len(S["compose"])} 个 compose 文件、{n} 个服务定义', '、'.join(c['file'] for c in S['compose'][:4])
513
+ + ('…' if len(S['compose']) > 4 else ''))
514
+ for g in S['git']:
515
+ add(f'版本库 {g["repo"]}', f'{g["commits"]:,} 次提交({g["first_commit"]} 至 {g["last_commit"]}),{g["contributors"]} 位贡献者',
516
+ 'git log')
517
+ if S['docs']:
518
+ add('项目文档', f'{len(S["docs"])} 篇 Markdown', 'docs/ 目录与根目录')
519
+ md = '| 事实 | 数值 | 来源 |\n|---|---|---|\n' + '\n'.join(rows)
520
+
521
+ def short(desc, cap=18):
522
+ d = (desc or '').strip()
523
+ if ' - ' in d:
524
+ d = d.split(' - ')[-1]
525
+ d = re.split(r'[::;;。,,((]|——|—', d)[0].strip()
526
+ return d if len(d) <= cap else d[:cap - 1] + '…'
527
+
528
+ # ── 模块树(交给 render_diagram 的 tree 图型)──
529
+ name = os.path.basename(S['root'])
530
+ root_desc = next((p['description'] for p in parents if p['path'] == '.' and p['description']), '')
531
+ children = []
532
+ for m in top_subprojects:
533
+ if m['path'] == '.':
534
+ continue
535
+ node = {'name': m['path'], 'desc': short(m.get('description'))}
536
+ subs = [x for x in modules if x['parent'] == m['path']]
537
+ if subs:
538
+ node['children'] = [{'name': x['module'], 'desc': short(x['description'])} for x in subs]
539
+ children.append(node)
540
+ if not children and modules:
541
+ children = [{'name': x['module'], 'desc': short(x['description'])} for x in modules]
542
+ # 自左向右 + 不画标题块:这份 spec 多半要进 Word,从上往下画的宽树插进 A4 字只剩 3 磅
543
+ tree = {'type': 'tree', 'title': f'{name} 模块组成', 'direction': 'LR', 'header': False,
544
+ 'root': {'name': name, 'desc': short(root_desc, 24), 'children': children}} \
545
+ if children else None
546
+
547
+ if S['truncated']:
548
+ warnings.append(f'文件数超过 {MAX_FILES:,},只统计了前 {MAX_FILES:,} 个:代码规模偏小,引用时注明「不少于」')
549
+ if not S['git']:
550
+ warnings.append('没有找到 git 仓库:研发周期、提交次数等过程数据要从别处取证')
551
+ if not top_subprojects:
552
+ warnings.append('没有识别到 pom.xml / package.json / build.gradle:技术栈要人工确认')
553
+
554
+ return {
555
+ 'ok': True, 'root': S['root'], 'files_scanned': S['files_scanned'],
556
+ 'code': {'total_loc': total_loc, 'prod_loc': prod_loc, 'test_loc': S['test']['loc'], 'by_language': by_lang,
557
+ 'duplicates': S['dup']},
558
+ 'subprojects': [{k: v for k, v in m.items() if k in ('path', 'kind', 'name', 'version', 'description', 'frameworks', 'source')}
559
+ for m in top_subprojects],
560
+ 'maven_modules': modules, 'services': services, 'compose': S['compose'][:12],
561
+ 'api': S['api'], 'frontend': S['frontend'],
562
+ 'database': {'tables': len(S['tables']), 'sql_files': S['sql_files'], 'names': sorted(S['tables'])[:300]},
563
+ 'tests': S['test'], 'git': S['git'],
564
+ 'docs': {'count': len(S['docs']), 'items': S['docs'][:150]},
565
+ 'markdown': md, 'diagram_tree': tree, 'warnings': warnings,
566
+ }
567
+
568
+
569
+ def main(argv):
570
+ root = argv[1] if len(argv) > 1 else '.'
571
+ try:
572
+ res = build(root)
573
+ except Exception as e: # 如实上报
574
+ res = {'ok': False, 'error': f'{type(e).__name__}: {e}'}
575
+ print(json.dumps(res, ensure_ascii=False))
576
+ return 0 if res.get('ok') else 1
577
+
578
+
579
+ if __name__ == '__main__':
580
+ sys.exit(main(sys.argv))