@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.
- package/LICENSE +391 -32
- package/README.md +176 -10
- package/bin/huicai.js +166 -73
- package/package.json +27 -8
- package/skills/ab-testing/trigger-cases.json +30 -0
- package/skills/academic-writing/trigger-cases.json +30 -0
- package/skills/accounting-ops/trigger-cases.json +30 -0
- package/skills/ai-agent/trigger-cases.json +30 -0
- package/skills/ai-aiops/trigger-cases.json +30 -0
- package/skills/ai-coding/trigger-cases.json +30 -0
- package/skills/ai-compliance/trigger-cases.json +30 -0
- package/skills/ai-eval/trigger-cases.json +30 -0
- package/skills/ai-llm-app/trigger-cases.json +30 -0
- package/skills/ai-mlops/trigger-cases.json +30 -0
- package/skills/ai-multimodal/trigger-cases.json +30 -0
- package/skills/ai-prompt/trigger-cases.json +30 -0
- package/skills/ai-rag/trigger-cases.json +30 -0
- package/skills/anomaly-detection/trigger-cases.json +30 -0
- package/skills/api-design/trigger-cases.json +31 -0
- package/skills/api-integration/trigger-cases.json +31 -0
- package/skills/arch-design/trigger-cases.json +12 -0
- package/skills/arch-diagram/SKILL.md +118 -0
- package/skills/arch-diagram/trigger-cases.json +30 -0
- package/skills/arch-distributed/trigger-cases.json +30 -0
- package/skills/arch-multitenant/trigger-cases.json +30 -0
- package/skills/audio-brief/SKILL.md +65 -0
- package/skills/audio-brief/trigger-cases.json +30 -0
- package/skills/audit-assist/trigger-cases.json +30 -0
- package/skills/audit-chain/trigger-cases.json +31 -0
- package/skills/banking-credit/trigger-cases.json +30 -0
- package/skills/brand-design/trigger-cases.json +30 -0
- package/skills/business-plan/trigger-cases.json +30 -0
- package/skills/capital-ops/trigger-cases.json +30 -0
- package/skills/cicd-pipeline/trigger-cases.json +31 -0
- package/skills/code-archaeology/SKILL.md +55 -0
- package/skills/code-archaeology/trigger-cases.json +9 -0
- package/skills/code-quality/SKILL.md +138 -0
- package/skills/code-quality/references/languages.md +107 -0
- package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
- package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
- package/skills/code-quality/scripts/code_battery.py +1072 -0
- package/skills/code-quality/scripts/code_check.py +1269 -0
- package/skills/code-quality/scripts/code_csharp.py +371 -0
- package/skills/code-quality/scripts/code_fix.py +201 -0
- package/skills/code-quality/scripts/code_java.py +695 -0
- package/skills/code-quality/scripts/code_jsts.py +313 -0
- package/skills/code-quality/scripts/code_lens.py +1079 -0
- package/skills/code-quality/scripts/code_python.py +536 -0
- package/skills/code-quality/scripts/code_testplan.py +239 -0
- package/skills/code-quality/trigger-cases.json +45 -0
- package/skills/code-review/SKILL.md +24 -0
- package/skills/code-review/trigger-cases.json +35 -0
- package/skills/commit-and-pr/trigger-cases.json +31 -0
- package/skills/content-creation/trigger-cases.json +30 -0
- package/skills/corporate-governance/trigger-cases.json +30 -0
- package/skills/crawl-chain/trigger-cases.json +31 -0
- package/skills/customer-service/trigger-cases.json +30 -0
- package/skills/data-analysis/trigger-cases.json +30 -0
- package/skills/data-chain/SKILL.md +1 -1
- package/skills/data-chain/trigger-cases.json +31 -0
- package/skills/data-governance/trigger-cases.json +30 -0
- package/skills/data-report-pro/trigger-cases.json +30 -0
- package/skills/data-streaming/trigger-cases.json +30 -0
- package/skills/data-wrangling/SKILL.md +1 -1
- package/skills/data-wrangling/trigger-cases.json +30 -0
- package/skills/db-es/trigger-cases.json +30 -0
- package/skills/db-migration/trigger-cases.json +30 -0
- package/skills/db-mysql/trigger-cases.json +30 -0
- package/skills/db-postgres/trigger-cases.json +30 -0
- package/skills/db-redis/trigger-cases.json +30 -0
- package/skills/debug-root-cause/trigger-cases.json +30 -0
- package/skills/dependency-audit/SKILL.md +56 -0
- package/skills/dependency-audit/trigger-cases.json +7 -0
- package/skills/dependency-upgrade/trigger-cases.json +31 -0
- package/skills/dev-cpp/trigger-cases.json +30 -0
- package/skills/dev-cross-platform/trigger-cases.json +30 -0
- package/skills/dev-csharp/SKILL.md +23 -0
- package/skills/dev-csharp/trigger-cases.json +30 -0
- package/skills/dev-frontend/SKILL.md +19 -0
- package/skills/dev-frontend/trigger-cases.json +30 -0
- package/skills/dev-go/trigger-cases.json +30 -0
- package/skills/dev-harmony/trigger-cases.json +30 -0
- package/skills/dev-java/SKILL.md +20 -0
- package/skills/dev-java/trigger-cases.json +30 -0
- package/skills/dev-mobile/trigger-cases.json +30 -0
- package/skills/dev-node/trigger-cases.json +30 -0
- package/skills/dev-php/trigger-cases.json +30 -0
- package/skills/dev-python/SKILL.md +21 -0
- package/skills/dev-python/trigger-cases.json +30 -0
- package/skills/dev-rust/trigger-cases.json +30 -0
- package/skills/dev-toolchain/trigger-cases.json +31 -0
- package/skills/doc-chain/trigger-cases.json +31 -0
- package/skills/doc-compare/trigger-cases.json +30 -0
- package/skills/docx-author/SKILL.md +127 -25
- package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
- package/skills/docx-author/scripts/docx_battery.py +586 -0
- package/skills/docx-author/scripts/docx_build.py +995 -0
- package/skills/docx-author/scripts/docx_check.py +1306 -0
- package/skills/docx-author/scripts/docx_doc.py +14 -154
- package/skills/docx-author/scripts/docx_layout.py +490 -0
- package/skills/docx-author/scripts/zh_prose.py +639 -0
- package/skills/docx-author/trigger-cases.json +58 -0
- package/skills/echo-brief/SKILL.md +84 -0
- package/skills/echo-brief/references/echo-format.md +68 -0
- package/skills/echo-brief/trigger-cases.json +40 -0
- package/skills/ecommerce-ops/trigger-cases.json +30 -0
- package/skills/education-training/trigger-cases.json +30 -0
- package/skills/ehs-safety/trigger-cases.json +30 -0
- package/skills/email-writing/trigger-cases.json +30 -0
- package/skills/eng-ai/trigger-cases.json +30 -0
- package/skills/eng-architecture/trigger-cases.json +31 -0
- package/skills/eng-automation/trigger-cases.json +31 -0
- package/skills/eng-collaboration/trigger-cases.json +30 -0
- package/skills/eng-complex-env/trigger-cases.json +30 -0
- package/skills/eng-data-platform/trigger-cases.json +31 -0
- package/skills/eng-database/trigger-cases.json +31 -0
- package/skills/eng-develop/trigger-cases.json +30 -0
- package/skills/eng-docs/SKILL.md +24 -0
- package/skills/eng-docs/trigger-cases.json +30 -0
- package/skills/eng-efficiency/trigger-cases.json +30 -0
- package/skills/eng-infra/trigger-cases.json +30 -0
- package/skills/eng-integration/trigger-cases.json +31 -0
- package/skills/eng-legacy/trigger-cases.json +31 -0
- package/skills/eng-observability/trigger-cases.json +31 -0
- package/skills/eng-ops/trigger-cases.json +31 -0
- package/skills/eng-performance/trigger-cases.json +31 -0
- package/skills/eng-planning/trigger-cases.json +31 -0
- package/skills/eng-quality/SKILL.md +8 -0
- package/skills/eng-quality/trigger-cases.json +31 -0
- package/skills/eng-release/trigger-cases.json +31 -0
- package/skills/eng-reliability/trigger-cases.json +31 -0
- package/skills/eng-requirements/trigger-cases.json +12 -0
- package/skills/eng-research/trigger-cases.json +30 -0
- package/skills/eng-security/trigger-cases.json +31 -0
- package/skills/eng-ux/trigger-cases.json +30 -0
- package/skills/engineering-chain/trigger-cases.json +30 -0
- package/skills/env-offline/trigger-cases.json +31 -0
- package/skills/env-private-deploy/trigger-cases.json +31 -0
- package/skills/env-xinchuang/trigger-cases.json +31 -0
- package/skills/equipment-tpm/trigger-cases.json +30 -0
- package/skills/esg-report/trigger-cases.json +30 -0
- package/skills/event-management/trigger-cases.json +30 -0
- package/skills/excel-analysis-chain/SKILL.md +17 -4
- package/skills/excel-analysis-chain/trigger-cases.json +31 -0
- package/skills/explain-codebase/trigger-cases.json +11 -0
- package/skills/file-ingest/trigger-cases.json +30 -0
- package/skills/finance-analysis/trigger-cases.json +31 -0
- package/skills/finance-chain/trigger-cases.json +31 -0
- package/skills/financial-modeling/trigger-cases.json +31 -0
- package/skills/find-simplifications/SKILL.md +43 -0
- package/skills/find-simplifications/trigger-cases.json +23 -0
- package/skills/foreign-trade/trigger-cases.json +30 -0
- package/skills/funnel-cohort/trigger-cases.json +30 -0
- package/skills/gov-document/trigger-cases.json +30 -0
- package/skills/gui-change-evidence/SKILL.md +47 -0
- package/skills/gui-change-evidence/trigger-cases.json +23 -0
- package/skills/hr-chain/trigger-cases.json +31 -0
- package/skills/hr-operations/trigger-cases.json +30 -0
- package/skills/info-disclosure/trigger-cases.json +31 -0
- package/skills/infra-gateway/trigger-cases.json +31 -0
- package/skills/infra-k8s/trigger-cases.json +31 -0
- package/skills/infra-mq/trigger-cases.json +30 -0
- package/skills/insurance-advisory/trigger-cases.json +30 -0
- package/skills/internal-audit/trigger-cases.json +30 -0
- package/skills/ip-management/trigger-cases.json +31 -0
- package/skills/ipo-listing/trigger-cases.json +31 -0
- package/skills/it-audit/trigger-cases.json +31 -0
- package/skills/itsm-ops/trigger-cases.json +31 -0
- package/skills/knowledge-sop/trigger-cases.json +31 -0
- package/skills/legal-chain/trigger-cases.json +31 -0
- package/skills/legal-contract/trigger-cases.json +30 -0
- package/skills/litigation-support/trigger-cases.json +31 -0
- package/skills/logistics-warehouse/trigger-cases.json +30 -0
- package/skills/long-doc-analysis/trigger-cases.json +31 -0
- package/skills/management-accounting/trigger-cases.json +30 -0
- package/skills/manufacturing-chain/trigger-cases.json +31 -0
- package/skills/manufacturing-quality/trigger-cases.json +30 -0
- package/skills/marketing-analytics/trigger-cases.json +31 -0
- package/skills/marketing-chain/trigger-cases.json +31 -0
- package/skills/meeting-minutes/trigger-cases.json +30 -0
- package/skills/metrics-system/trigger-cases.json +31 -0
- package/skills/negotiation-prep/trigger-cases.json +30 -0
- package/skills/obs-prometheus/trigger-cases.json +31 -0
- package/skills/office-automation/trigger-cases.json +31 -0
- package/skills/okr-review/trigger-cases.json +30 -0
- package/skills/ops-fleet/trigger-cases.json +31 -0
- package/skills/ops-linux/trigger-cases.json +31 -0
- package/skills/optimization-solver/trigger-cases.json +30 -0
- package/skills/patent-disclosure/SKILL.md +105 -0
- package/skills/patent-disclosure/references/figures.md +110 -0
- package/skills/patent-disclosure/references/outline.md +162 -0
- package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
- package/skills/patent-disclosure/trigger-cases.json +31 -0
- package/skills/patent-drafting/SKILL.md +108 -0
- package/skills/patent-drafting/trigger-cases.json +31 -0
- package/skills/patent-portfolio/SKILL.md +81 -0
- package/skills/patent-portfolio/trigger-cases.json +31 -0
- package/skills/pdf-toolkit/trigger-cases.json +31 -0
- package/skills/perf-optimize/trigger-cases.json +31 -0
- package/skills/pivot-crosstab/trigger-cases.json +30 -0
- package/skills/policy-analysis/trigger-cases.json +31 -0
- package/skills/portfolio-analysis/trigger-cases.json +31 -0
- package/skills/ppt-chain/SKILL.md +14 -6
- package/skills/ppt-chain/trigger-cases.json +31 -0
- package/skills/pptx-author/SKILL.md +86 -33
- package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
- package/skills/pptx-author/scripts/pptx_battery.py +178 -0
- package/skills/pptx-author/scripts/pptx_build.py +119 -0
- package/skills/pptx-author/scripts/pptx_check.py +1182 -0
- package/skills/pptx-author/scripts/pptx_deck.py +736 -145
- package/skills/pptx-author/scripts/pptx_layout.py +384 -0
- package/skills/pptx-author/trigger-cases.json +15 -0
- package/skills/pr-comms/trigger-cases.json +30 -0
- package/skills/procurement-supply/trigger-cases.json +31 -0
- package/skills/product-design/trigger-cases.json +31 -0
- package/skills/production-planning/trigger-cases.json +31 -0
- package/skills/project-management/trigger-cases.json +31 -0
- package/skills/project-proposal/trigger-cases.json +31 -0
- package/skills/prose-standard/SKILL.md +53 -0
- package/skills/prose-standard/trigger-cases.json +23 -0
- package/skills/qa-api-automation/trigger-cases.json +31 -0
- package/skills/qa-mobile-automation/trigger-cases.json +31 -0
- package/skills/qa-ui-automation/trigger-cases.json +31 -0
- package/skills/quant-analysis/trigger-cases.json +31 -0
- package/skills/rag-triage/SKILL.md +64 -0
- package/skills/rag-triage/evals.json +57 -0
- package/skills/rag-triage/references/triage-playbook.md +75 -0
- package/skills/rag-triage/trigger-cases.json +31 -0
- package/skills/real-estate-property/trigger-cases.json +30 -0
- package/skills/refactor-safely/SKILL.md +1 -1
- package/skills/refactor-safely/trigger-cases.json +30 -0
- package/skills/regression-forecast/trigger-cases.json +31 -0
- package/skills/release-checklist/trigger-cases.json +31 -0
- package/skills/report-craft/SKILL.md +76 -0
- package/skills/report-craft/references/feasibility.md +118 -0
- package/skills/report-craft/references/review-checklist.md +51 -0
- package/skills/report-craft/references/visuals.md +114 -0
- package/skills/report-craft/references/writing.md +80 -0
- package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
- package/skills/report-craft/scripts/chart_render.py +1222 -0
- package/skills/report-craft/scripts/fin_model.py +602 -0
- package/skills/report-craft/scripts/project_facts.py +580 -0
- package/skills/report-craft/scripts/report_battery.py +349 -0
- package/skills/report-craft/trigger-cases.json +15 -0
- package/skills/retail-store-ops/trigger-cases.json +30 -0
- package/skills/risk-compliance/trigger-cases.json +31 -0
- package/skills/scope-creep/SKILL.md +45 -0
- package/skills/scope-creep/trigger-cases.json +7 -0
- package/skills/sec-auth/trigger-cases.json +31 -0
- package/skills/sec-compliance/trigger-cases.json +31 -0
- package/skills/securities-chain/trigger-cases.json +31 -0
- package/skills/securities-research/trigger-cases.json +31 -0
- package/skills/security-audit/trigger-cases.json +31 -0
- package/skills/slides-html/SKILL.md +25 -10
- package/skills/slides-html/trigger-cases.json +11 -0
- package/skills/sql-analysis/SKILL.md +110 -0
- package/skills/sql-analysis/trigger-cases.json +34 -0
- package/skills/sql-tuning/trigger-cases.json +31 -0
- package/skills/statistical-testing/trigger-cases.json +31 -0
- package/skills/strategy-planning/trigger-cases.json +30 -0
- package/skills/supply-chain/trigger-cases.json +31 -0
- package/skills/tax-compliance/trigger-cases.json +30 -0
- package/skills/tech-doc/SKILL.md +262 -0
- package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
- package/skills/tech-doc/scripts/archive_battery.py +860 -0
- package/skills/tech-doc/scripts/archive_build.py +599 -0
- package/skills/tech-doc/scripts/archive_check.py +1307 -0
- package/skills/tech-doc/scripts/archive_spec.py +499 -0
- package/skills/tech-doc/scripts/doc_templates.py +624 -0
- package/skills/tech-doc/trigger-cases.json +57 -0
- package/skills/tech-research/trigger-cases.json +31 -0
- package/skills/tender-bidding/trigger-cases.json +31 -0
- package/skills/translation-pro/trigger-cases.json +30 -0
- package/skills/treasury-mgmt/trigger-cases.json +31 -0
- package/skills/trim-cot-leakage/SKILL.md +58 -0
- package/skills/trim-cot-leakage/trigger-cases.json +23 -0
- package/skills/web-craft/SKILL.md +103 -0
- package/skills/web-craft/references/craft-checklist.md +75 -0
- package/skills/web-craft/references/design-direction.md +122 -0
- package/skills/web-craft/references/h5-mobile.md +100 -0
- package/skills/web-craft/references/motion.md +136 -0
- package/skills/web-craft/references/recipes.md +74 -0
- package/skills/web-craft/templates/wedding-invitation.html +1180 -0
- package/skills/web-craft/trigger-cases.json +14 -0
- package/skills/web-scraping/trigger-cases.json +34 -0
- package/skills/web-search/SKILL.md +11 -10
- package/skills/web-search/trigger-cases.json +31 -0
- package/skills/weekly-report/SKILL.md +134 -0
- package/skills/weekly-report/trigger-cases.json +11 -0
- package/skills/write-tests/trigger-cases.json +31 -0
- package/skills/xlsx-analysis/SKILL.md +143 -77
- package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
- package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
- package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
- package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
- package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
- package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
- package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
- package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
- package/skills/xlsx-analysis/trigger-cases.json +59 -0
- package/squads/hc-defense.md +47 -0
- package/squads/hc-rdarchive.md +110 -0
- package/squads/hc-redteam.md +50 -0
- package/squads/hc-weekly.md +125 -0
- package/src/account.js +1 -0
- package/src/acp.js +1 -0
- package/src/advisor.js +1 -1
- package/src/agent.js +1 -1
- package/src/api.js +1 -1
- package/src/archaeology.js +1 -0
- package/src/ask-ui.js +1 -0
- package/src/assets-import.js +1 -1
- package/src/autofix.js +1 -1
- package/src/automation-tools.js +1 -0
- package/src/automation.js +1 -1
- package/src/autorun.js +1 -1
- package/src/banner.js +1 -1
- package/src/capability-probe.js +1 -0
- package/src/checkpoints.js +1 -1
- package/src/code-tools.js +1 -0
- package/src/codeindex.js +1 -1
- package/src/codemode.js +1 -0
- package/src/command-bridge.js +1 -0
- package/src/command-ui.js +1 -0
- package/src/commands.js +1 -1
- package/src/compaction.js +1 -1
- package/src/computer.js +1 -0
- package/src/config.js +1 -1
- package/src/consult.js +1 -1
- package/src/context-report.js +1 -1
- package/src/context-window.js +1 -0
- package/src/context.js +1 -1
- package/src/crew.js +1 -0
- package/src/crush.js +1 -0
- package/src/daemon.js +1 -1
- package/src/decisions.js +1 -1
- package/src/depaudit.js +1 -0
- package/src/device.js +1 -1
- package/src/diagram-draw.js +1 -0
- package/src/diagram-layouts.js +1 -0
- package/src/diagram-raster.js +1 -0
- package/src/diagram.js +1 -0
- package/src/dictation.js +1 -0
- package/src/diffview.js +1 -1
- package/src/doctor.js +1 -1
- package/src/docx-tools.js +1 -0
- package/src/errkind.js +1 -0
- package/src/evidence.js +1 -0
- package/src/executors.js +1 -1
- package/src/expert-catalog.js +1 -1
- package/src/experts-store.js +1 -1
- package/src/experts.js +1 -1
- package/src/external-agents.js +1 -0
- package/src/forks.js +1 -1
- package/src/fs-observe.js +1 -0
- package/src/gitflow.js +1 -1
- package/src/goalround.js +1 -0
- package/src/hooks.js +1 -1
- package/src/input.js +1 -1
- package/src/insights.js +1 -1
- package/src/jobs.js +1 -0
- package/src/kbsync.js +1 -1
- package/src/lsp.js +1 -0
- package/src/mcp-apps.js +1 -0
- package/src/mcp.js +1 -1
- package/src/memory.js +1 -1
- package/src/notify.js +1 -0
- package/src/permissions.js +1 -1
- package/src/plan-limits.js +1 -0
- package/src/policy.js +1 -0
- package/src/pptx-tools.js +1 -0
- package/src/presence.js +1 -0
- package/src/privatedir.js +1 -0
- package/src/pyruntime.js +1 -1
- package/src/radar.js +1 -0
- package/src/render.js +1 -1
- package/src/repl.js +1 -1
- package/src/report-tools.js +1 -0
- package/src/routing.js +1 -0
- package/src/sandbox-probe.js +1 -0
- package/src/sandbox-win-launcher.mjs +156 -0
- package/src/sandbox.js +1 -0
- package/src/scan.js +1 -1
- package/src/schedule.js +1 -1
- package/src/scopecreep.js +1 -0
- package/src/sdk.js +1 -0
- package/src/selection.js +1 -0
- package/src/session-events.js +1 -0
- package/src/session.js +1 -1
- package/src/sessionindex.js +1 -0
- package/src/sessionlog.js +1 -0
- package/src/skill-catalog.js +1 -0
- package/src/skill-lint.js +1 -0
- package/src/skill-optimize.js +1 -0
- package/src/skill-scan.js +1 -0
- package/src/skills.js +1 -1
- package/src/spill.js +1 -0
- package/src/squad.js +1 -1
- package/src/subagent.js +1 -1
- package/src/surface-state.js +1 -0
- package/src/tapd.js +1 -0
- package/src/terminal-select.js +1 -0
- package/src/terminal.js +1 -0
- package/src/test-runner.js +1 -0
- package/src/todo.js +1 -1
- package/src/toolpipe.js +1 -0
- package/src/tools.js +1 -1
- package/src/toon.js +1 -0
- package/src/transcript.js +1 -1
- package/src/tui/app.js +1 -1
- package/src/tui/command-menu.js +1 -0
- package/src/tui/progress.js +1 -0
- package/src/tui/scroll.js +1 -0
- package/src/tui/welcome.js +1 -0
- package/src/tui/workbench.js +1 -0
- package/src/ui-spec.js +1 -0
- package/src/uitree.js +1 -0
- package/src/uninstall.js +1 -1
- package/src/vision.js +1 -0
- package/src/visual.js +1 -0
- package/src/voice-form.js +1 -0
- package/src/voice.js +1 -0
- package/src/watch.js +1 -1
- package/src/web-audit.js +1 -0
- package/src/web-cdp.js +1 -0
- package/src/web-inspect.js +1 -0
- package/src/websearch.js +1 -0
- package/src/weekly.js +1 -0
- package/src/workflow.js +1 -0
- package/src/worklog.js +1 -0
- package/src/xlsx-tools.js +1 -0
- package/ui/command-picker.js +79 -0
- package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
- package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
- package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
- package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
- package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
- package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
- package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
- package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
- package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
- package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
- package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
- package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
- package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
- package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
- package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
- package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
- package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
- package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
- package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
- package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
- package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
- package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
- package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
- package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# JS / TS / Vue 专属体检:异步陷阱 + 类型逃逸 + Vue 响应式与模板。
|
|
2
|
+
#
|
|
3
|
+
# ── 这一份为什么要比 Python 那份保守 ──
|
|
4
|
+
# Python 有标准库 ast,能判类型层级、作用域、名字绑定,所以敢做很细的推断。
|
|
5
|
+
# JS/TS 这边只有「掩码视图 + 括号配对」——托管 Python 里没有可靠的 JS 解析器,
|
|
6
|
+
# 而为了体检去装一个解析器,等于让体检在最需要它的时候先卡几分钟。
|
|
7
|
+
#
|
|
8
|
+
# 权衡的结果是:**判据必须是确定信号,拿不准就不报**。
|
|
9
|
+
# `.forEach(async (x) => …)` —— 形态唯一,forEach 不会等 Promise,报;
|
|
10
|
+
# `a?.b.c` —— 前半段保护了、后半段没保护,报;
|
|
11
|
+
# `x === NaN` —— 永远为 false,报;
|
|
12
|
+
# 「这个变量是不是数组」 —— 猜不准,只在有声明依据时才报。
|
|
13
|
+
#
|
|
14
|
+
# ── 分组 ──
|
|
15
|
+
# logic.js-* 异步与经典陷阱(浮空 Promise、forEach async、for-in 数组、sort、NaN)
|
|
16
|
+
# logic.ts-* 类型逃逸(as any、@ts-ignore、返回 any)
|
|
17
|
+
# framework.vue-* Vue 3(模板的 v-for/v-if/v-html,脚本的 props 与响应式)
|
|
18
|
+
#
|
|
19
|
+
# Vue 的一半问题在 <template> 里,而 template 一度被整个丢弃(只抠 script)——
|
|
20
|
+
# 那时 `v-for` 少了 `:key` 这类最常见的问题一条也查不到,报告却照样显示「已体检」。
|
|
21
|
+
|
|
22
|
+
import os
|
|
23
|
+
import re
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
27
|
+
|
|
28
|
+
# ── 异步 ────────────────────────────────────────────────────────────────────
|
|
29
|
+
_RE_ASYNC_ITER = re.compile(
|
|
30
|
+
r'\.\s*(forEach|map|filter|some|every|find|findIndex|sort|reduce)\s*\(\s*async\b')
|
|
31
|
+
_RE_THEN_NO_CATCH = re.compile(r'\.\s*then\s*\(')
|
|
32
|
+
# `a?.b.c`:前半段做了可选链保护,后半段又直接点出去——a 为空时前半段返回 undefined,后半段照样崩
|
|
33
|
+
_RE_OPTIONAL_THEN_DOT = re.compile(r'\?\.\s*([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)')
|
|
34
|
+
_RE_NAN_CMP = re.compile(r'(===|!==|==|!=)\s*NaN\b|\bNaN\s*(===|!==|==|!=)')
|
|
35
|
+
_RE_PARSE_INT = re.compile(r'\bparseInt\s*\(([^,()]*(?:\([^()]*\))?[^,()]*)\)')
|
|
36
|
+
_RE_SORT_BARE = re.compile(r'\.\s*sort\s*\(\s*\)')
|
|
37
|
+
_RE_DELETE_INDEX = re.compile(r'\bdelete\s+([A-Za-z_$][\w$]*)\s*\[')
|
|
38
|
+
_RE_JSON_PARSE = re.compile(r'\bJSON\s*\.\s*parse\s*\(')
|
|
39
|
+
_RE_FOR_IN = re.compile(r'\bfor\s*\(\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s+in\s+([A-Za-z_$][\w$.]*)\s*\)')
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _line_of(lens, off):
|
|
43
|
+
return lens.text.count('\n', 0, off) + 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _unit_of(lens, line):
|
|
47
|
+
u = lens.unit_at(line)
|
|
48
|
+
return u.label if u else None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _enclosing_try(code, off, limit=3000):
|
|
52
|
+
"""这个偏移量是否落在某个 try 块里(粗判:往前找 try{ 且括号未闭合)。"""
|
|
53
|
+
head = code.rfind('try', max(0, off - limit), off)
|
|
54
|
+
if head < 0:
|
|
55
|
+
return False
|
|
56
|
+
brace = code.find('{', head)
|
|
57
|
+
if brace < 0 or brace > off:
|
|
58
|
+
return False
|
|
59
|
+
depth = 0
|
|
60
|
+
for i in range(brace, min(len(code), off + 1)):
|
|
61
|
+
if code[i] == '{':
|
|
62
|
+
depth += 1
|
|
63
|
+
elif code[i] == '}':
|
|
64
|
+
depth -= 1
|
|
65
|
+
if depth == 0:
|
|
66
|
+
return False # try 块在 off 之前就结束了
|
|
67
|
+
return depth > 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def check_js_traps(lens, ctx, out, add):
|
|
71
|
+
"""JS 的异步与经典陷阱。判据一律取确定形态,拿不准不报。"""
|
|
72
|
+
code = lens.code
|
|
73
|
+
|
|
74
|
+
# ── 数组方法里传 async 回调:forEach 根本不等 Promise,循环瞬间跑完 ──
|
|
75
|
+
for m in _RE_ASYNC_ITER.finditer(code):
|
|
76
|
+
ln = _line_of(lens, m.start())
|
|
77
|
+
method = m.group(1)
|
|
78
|
+
if method in ('map', 'filter', 'some', 'every', 'find', 'findIndex'):
|
|
79
|
+
hint = (f'`{method}` 会得到一个 Promise 数组:用 `await Promise.all(arr.map(async …))`;'
|
|
80
|
+
f'{method} 需要布尔结果的(filter/some/every)要先 await 再过滤')
|
|
81
|
+
else:
|
|
82
|
+
hint = ('`forEach` 不返回也不等待 Promise —— 改成 `for (const x of arr) { await … }` 顺序执行,'
|
|
83
|
+
'或 `await Promise.all(arr.map(async x => …))` 并发执行')
|
|
84
|
+
add(out, ctx, 'logic.js-async-in-array-method', 'error', lens, ln,
|
|
85
|
+
f'`.{method}(async …)` —— 回调返回的是 Promise,而 `{method}` 不会等它。'
|
|
86
|
+
'结果是循环"瞬间跑完",后面的代码在数据还没就绪时就执行了',
|
|
87
|
+
hint, _unit_of(lens, ln))
|
|
88
|
+
|
|
89
|
+
# ── a?.b.c:前半段防了空,后半段没防,等于白防 ──
|
|
90
|
+
for m in _RE_OPTIONAL_THEN_DOT.finditer(code):
|
|
91
|
+
ln = _line_of(lens, m.start())
|
|
92
|
+
add(out, ctx, 'logic.js-optional-chain-broken', 'warn', lens, ln,
|
|
93
|
+
f'`?.{m.group(1)}.{m.group(2)}` —— 可选链只保护了第一层:'
|
|
94
|
+
f'前面为空时 `?.{m.group(1)}` 得到 undefined,紧接着的 `.{m.group(2)}` 照样抛 TypeError',
|
|
95
|
+
f'整条链都要带上:`?.{m.group(1)}?.{m.group(2)}`。'
|
|
96
|
+
'只在第一个点上写 `?.` 是最常见的误用——它给人"已经防过空"的错觉',
|
|
97
|
+
_unit_of(lens, ln))
|
|
98
|
+
|
|
99
|
+
# ── 与 NaN 比较:NaN 不等于任何值,包括它自己 ──
|
|
100
|
+
for m in _RE_NAN_CMP.finditer(code):
|
|
101
|
+
ln = _line_of(lens, m.start())
|
|
102
|
+
add(out, ctx, 'logic.js-nan-compare', 'error', lens, ln,
|
|
103
|
+
'和 `NaN` 做比较 —— NaN 不等于任何值**包括它自己**,这个判断永远为 false',
|
|
104
|
+
'用 `Number.isNaN(x)`(不要用全局 `isNaN`,它会先把参数转成数字,'
|
|
105
|
+
'`isNaN("abc")` 也是 true)', _unit_of(lens, ln))
|
|
106
|
+
|
|
107
|
+
# ── for...in 遍历数组:拿到的是字符串下标,还会带出原型链上的属性 ──
|
|
108
|
+
for m in _RE_FOR_IN.finditer(code):
|
|
109
|
+
target = m.group(2)
|
|
110
|
+
ln = _line_of(lens, m.start())
|
|
111
|
+
# 只在有声明依据时才报——猜"这个变量是不是数组"会误伤大量对象遍历
|
|
112
|
+
decl = re.search(rf'\b(?:const|let|var)\s+{re.escape(target)}\s*=\s*(\[|Array|new Array|[\w.]+\.(?:map|filter|split|concat|slice)\s*\()',
|
|
113
|
+
code[:m.start()])
|
|
114
|
+
# 只认声明依据,**不靠名字猜**。
|
|
115
|
+
# `prevFiles` 是 `{ rel: {...} }` 这样的映射对象,却因为以 files 结尾被当成数组报了出来——
|
|
116
|
+
# 这已经是第二次栽在「按命名推断类型」上(Java 那轮是 `code == getCode()` 两边都是 int)。
|
|
117
|
+
if not decl:
|
|
118
|
+
continue
|
|
119
|
+
add(out, ctx, 'logic.js-for-in-array', 'error', lens, ln,
|
|
120
|
+
f'`for...in` 遍历数组 `{target}` —— 得到的是**字符串下标**("0"、"1"),'
|
|
121
|
+
'还会把原型链上的可枚举属性一起带出来,顺序也不保证',
|
|
122
|
+
f'数组用 `for (const x of {target})`(要下标就 `{target}.entries()`);'
|
|
123
|
+
'`for...in` 只给对象用', _unit_of(lens, ln))
|
|
124
|
+
|
|
125
|
+
# ── sort() 不带比较函数 ──
|
|
126
|
+
# 只报**排数字**的情况:默认 sort 把元素转成字符串再比,对字符串数组来说这正是想要的,
|
|
127
|
+
# `Object.keys(v).sort()` / `[...set].sort()` 都是完全正确的写法。
|
|
128
|
+
# 一开始没做这个区分,在真实代码上报出 20 条、无一为真。
|
|
129
|
+
for m in _RE_SORT_BARE.finditer(code):
|
|
130
|
+
ln = _line_of(lens, m.start())
|
|
131
|
+
recv = re.search(r'([A-Za-z_$][\w$.\]]*)\s*\.\s*sort\s*\(\s*\)$', code[:m.end()])
|
|
132
|
+
name = recv.group(1).split('.')[-1] if recv else ''
|
|
133
|
+
line_txt = lens.line(ln)
|
|
134
|
+
numeric = bool(re.search(r'(ids|nums?|counts?|sizes?|ages?|prices?|amounts?|scores?|ports?|years?|lens?)$',
|
|
135
|
+
name, re.I))
|
|
136
|
+
if not numeric:
|
|
137
|
+
# 声明处是数字字面量数组,或经过 map(Number) 的,也算数字数组
|
|
138
|
+
decl = re.search(rf'\b(?:const|let|var)\s+{re.escape(name)}\s*=\s*\[\s*-?\d', code) if name else None
|
|
139
|
+
numeric = bool(decl) or bool(re.search(r'\.map\s*\(\s*Number\s*\)|\.map\s*\(\s*\w+\s*=>\s*\+', line_txt))
|
|
140
|
+
if not numeric:
|
|
141
|
+
continue
|
|
142
|
+
add(out, ctx, 'logic.js-sort-no-comparator', 'warn', lens, ln,
|
|
143
|
+
'`sort()` 没有传比较函数 —— 它会把元素**转成字符串**再排,'
|
|
144
|
+
'`[10, 9, 100].sort()` 得到的是 `[10, 100, 9]`',
|
|
145
|
+
'数字用 `sort((a, b) => a - b)`;中文用 `sort((a, b) => a.localeCompare(b, "zh"))`',
|
|
146
|
+
_unit_of(lens, ln))
|
|
147
|
+
|
|
148
|
+
# ── parseInt 不带基数 ──
|
|
149
|
+
for m in _RE_PARSE_INT.finditer(code):
|
|
150
|
+
ln = _line_of(lens, m.start())
|
|
151
|
+
add(out, ctx, 'logic.js-parseint-radix', 'info', lens, ln,
|
|
152
|
+
'`parseInt()` 没有指定基数 —— 解析行为依赖输入的前缀(`0x` 会被当十六进制)',
|
|
153
|
+
'明确写成 `parseInt(x, 10)`;只是想转数字用 `Number(x)`,'
|
|
154
|
+
'它对 "12abc" 会给 NaN 而不是 12', _unit_of(lens, ln))
|
|
155
|
+
|
|
156
|
+
# ── delete 数组元素:留下一个空洞,length 不变 ──
|
|
157
|
+
for m in _RE_DELETE_INDEX.finditer(code):
|
|
158
|
+
ln = _line_of(lens, m.start())
|
|
159
|
+
line_txt = lens.line(ln)
|
|
160
|
+
if not re.search(r'\[\s*\d|\[\s*i\b|\[\s*idx|\[\s*index', line_txt):
|
|
161
|
+
continue # 删对象的键是正常用法,只管按下标删的
|
|
162
|
+
add(out, ctx, 'logic.js-delete-array-index', 'error', lens, ln,
|
|
163
|
+
f'`delete {m.group(1)}[...]` 删数组元素 —— 它只是把那一格置空,'
|
|
164
|
+
'**length 不变**,留下一个 empty 洞,后面遍历会拿到 undefined',
|
|
165
|
+
f'用 `{m.group(1)}.splice(下标, 1)`;按条件删整批用 `filter` 重建', _unit_of(lens, ln))
|
|
166
|
+
|
|
167
|
+
# ── JSON.parse 没有防护:输入一旦不是合法 JSON 就直接抛 ──
|
|
168
|
+
for m in _RE_JSON_PARSE.finditer(code):
|
|
169
|
+
ln = _line_of(lens, m.start())
|
|
170
|
+
seg = lens.line(ln)
|
|
171
|
+
if 'JSON.stringify' in seg:
|
|
172
|
+
continue # JSON.parse(JSON.stringify(x)) 是深拷贝惯用法
|
|
173
|
+
if _enclosing_try(code, m.start()):
|
|
174
|
+
continue
|
|
175
|
+
u = lens.unit_at(ln)
|
|
176
|
+
if u and re.search(r'\btry\b', u.body):
|
|
177
|
+
continue # 函数体里有 try,粗判已被兜住
|
|
178
|
+
add(out, ctx, 'logic.js-json-parse-unguarded', 'warn', lens, ln,
|
|
179
|
+
'`JSON.parse()` 没有放在 try 里 —— 输入不是合法 JSON 时直接抛异常,'
|
|
180
|
+
'而"输入不合法"恰恰是要处理的正常情况(半截响应、空串、被截断的缓存)',
|
|
181
|
+
'包一层 try/catch 并给出降级值;或者写个 `safeParse(text, fallback)` 统一处理',
|
|
182
|
+
_unit_of(lens, ln))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ── TypeScript 类型逃逸 ─────────────────────────────────────────────────────
|
|
186
|
+
_RE_AS_ANY = re.compile(r'\bas\s+any\b|<any>')
|
|
187
|
+
_RE_TS_IGNORE = re.compile(r'@ts-(ignore|expect-error|nocheck)\b')
|
|
188
|
+
_RE_ANY_RETURN = re.compile(r'\)\s*:\s*(any|any\[\]|Promise\s*<\s*any\s*>)\s*[{;=]')
|
|
189
|
+
_RE_ANY_PARAM = re.compile(r'[(,]\s*\w+\s*:\s*any\b')
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def check_ts_escapes(lens, ctx, out, add):
|
|
193
|
+
"""TypeScript 的类型逃逸:写了 TS 却把类型系统关掉的那几种写法。"""
|
|
194
|
+
if lens.lang not in ('ts', 'js') and not lens.is_vue:
|
|
195
|
+
return
|
|
196
|
+
code = lens.code
|
|
197
|
+
is_ts = lens.lang == 'ts' or re.search(r'lang=["\']ts', lens.text or '')
|
|
198
|
+
if not is_ts:
|
|
199
|
+
return
|
|
200
|
+
|
|
201
|
+
for m in _RE_AS_ANY.finditer(code):
|
|
202
|
+
ln = _line_of(lens, m.start())
|
|
203
|
+
add(out, ctx, 'logic.ts-as-any', 'warn', lens, ln,
|
|
204
|
+
'`as any` 断言 —— 这一行之后所有的类型检查都失效了,'
|
|
205
|
+
'写错属性名、传错参数都不会报错',
|
|
206
|
+
'断言到**具体类型**(`as UserDto`);类型确实拿不准就用 `unknown` + 类型守卫,'
|
|
207
|
+
'它会强制你在用之前先判一次', _unit_of(lens, ln))
|
|
208
|
+
|
|
209
|
+
for m in _RE_TS_IGNORE.finditer(lens.text):
|
|
210
|
+
ln = _line_of(lens, m.start())
|
|
211
|
+
tail = lens.line(ln)[m.start() - lens.text.rfind('\n', 0, m.start()) - 1:]
|
|
212
|
+
if len(tail.strip()) > 24:
|
|
213
|
+
continue # 后面跟了说明,算有理由
|
|
214
|
+
add(out, ctx, 'logic.ts-ignore-no-reason', 'warn', lens, ln,
|
|
215
|
+
f'`@ts-{m.group(1)}` 没写理由 —— 它会把下一行的类型错误整个静音,'
|
|
216
|
+
'而没人知道当初为什么要静音、现在还需不需要',
|
|
217
|
+
'在同一行补一句为什么(`@ts-expect-error 第三方类型声明缺 xxx,已提 issue #123`)。'
|
|
218
|
+
'`@ts-expect-error` 优于 `@ts-ignore`:问题修好后它会主动报"这里已经不需要了"',
|
|
219
|
+
_unit_of(lens, ln))
|
|
220
|
+
|
|
221
|
+
for m in _RE_ANY_RETURN.finditer(code):
|
|
222
|
+
ln = _line_of(lens, m.start())
|
|
223
|
+
add(out, ctx, 'logic.ts-any-return', 'warn', lens, ln,
|
|
224
|
+
'函数返回类型标成了 `any` —— 调用方从这里开始失去全部类型信息,'
|
|
225
|
+
'一处 any 会顺着调用链扩散出去',
|
|
226
|
+
'写出真实的返回类型;结构复杂就先定义 interface。'
|
|
227
|
+
'实在不确定用 `unknown`,它至少强制调用方先收窄', _unit_of(lens, ln))
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# ── Vue 3 ──────────────────────────────────────────────────────────────────
|
|
231
|
+
# 一个开始标签连同它的全部属性。属性里可能带引号包着的表达式(含 `>`),
|
|
232
|
+
# 所以不能简单地一路吃到第一个 `>`——先把引号段整体跳过。
|
|
233
|
+
_RE_TAG = re.compile(r'<([a-zA-Z][\w.-]*)((?:"[^"]*"|\'[^\']*\'|[^<>"\'])*)>', re.S)
|
|
234
|
+
_RE_HAS_KEY = re.compile(r'(?:^|\s)(?::key|v-bind:key|key)\s*=')
|
|
235
|
+
_RE_KEY_INDEX = re.compile(r'(?::key|v-bind:key)\s*=\s*["\'](?:\s*)(index|idx|i)\s*["\']')
|
|
236
|
+
_RE_V_FOR = re.compile(r'(?:^|\s)v-for\s*=')
|
|
237
|
+
_RE_V_IF = re.compile(r'(?:^|\s)v-(?:if|else-if)\s*=')
|
|
238
|
+
_RE_V_HTML = re.compile(r'(?:^|\s)v-html\s*=')
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def check_vue(lens, ctx, out, add):
|
|
242
|
+
"""Vue 3 单文件组件:模板里的指令用法 + 脚本里的响应式陷阱。"""
|
|
243
|
+
if not lens.is_vue:
|
|
244
|
+
return
|
|
245
|
+
tmpl = lens.vue_template
|
|
246
|
+
base = lens.vue_template_line
|
|
247
|
+
|
|
248
|
+
if tmpl:
|
|
249
|
+
for m in _RE_TAG.finditer(tmpl):
|
|
250
|
+
tag, attrs = m.group(1), m.group(2) or ''
|
|
251
|
+
ln = base + tmpl.count('\n', 0, m.start())
|
|
252
|
+
has_for = bool(_RE_V_FOR.search(attrs))
|
|
253
|
+
|
|
254
|
+
# ── v-for 没有 :key:Vue 只能按位置复用节点,列表一变动状态就串到别的行上 ──
|
|
255
|
+
if has_for and not _RE_HAS_KEY.search(attrs):
|
|
256
|
+
add(out, ctx, 'framework.vue-vfor-no-key', 'error', lens, ln,
|
|
257
|
+
f'`<{tag}>` 上有 v-for 却没有 `:key` —— Vue 只能按位置复用 DOM 节点,'
|
|
258
|
+
'列表一旦插入/删除/重排,输入框里的内容、勾选状态就会串到别的行上',
|
|
259
|
+
'补 `:key="item.id"`(要用**数据本身的稳定标识**)', None)
|
|
260
|
+
|
|
261
|
+
# ── :key 用下标:等于没给 key,重排时照样串 ──
|
|
262
|
+
if has_for and _RE_KEY_INDEX.search(attrs):
|
|
263
|
+
add(out, ctx, 'framework.vue-key-is-index', 'warn', lens, ln,
|
|
264
|
+
f'`<{tag}>` 的 `:key` 用的是下标 —— 下标跟着位置走,'
|
|
265
|
+
'插入或删除一行之后所有后续项的 key 都变了,等于没给 key',
|
|
266
|
+
'用数据里的稳定 id;确实没有 id 就在数据入口处生成一个并保存下来', None)
|
|
267
|
+
|
|
268
|
+
# ── v-if 与 v-for 写在同一个元素上 ──
|
|
269
|
+
if has_for and _RE_V_IF.search(attrs):
|
|
270
|
+
add(out, ctx, 'framework.vue-vif-with-vfor', 'error', lens, ln,
|
|
271
|
+
f'`<{tag}>` 上 v-if 与 v-for 写在同一个元素 —— Vue 3 里 **v-if 优先级更高**,'
|
|
272
|
+
'它先执行,此时 v-for 的循环变量还不存在,会直接报未定义',
|
|
273
|
+
'把 v-if 移到外层 `<template v-for>` 上,'
|
|
274
|
+
'或者先在 computed 里把数据过滤好再渲染(后者顺带避免每次渲染都重算)', None)
|
|
275
|
+
|
|
276
|
+
# ── v-html:内容会被当 HTML 解析 ──
|
|
277
|
+
if _RE_V_HTML.search(attrs):
|
|
278
|
+
add(out, ctx, 'framework.vue-v-html', 'warn', lens, ln,
|
|
279
|
+
f'`<{tag}>` 用了 `v-html` —— 内容会被当成 HTML 解析执行,'
|
|
280
|
+
'一旦其中混入用户输入就是 XSS',
|
|
281
|
+
'能用 `{{ }}` 插值就别用 v-html(插值会转义);'
|
|
282
|
+
'确实要渲染富文本,先过一遍白名单净化(DOMPurify 之类)', None)
|
|
283
|
+
|
|
284
|
+
# ── 脚本侧:直接改 props / reactive 解构 ──
|
|
285
|
+
code = lens.code
|
|
286
|
+
props_names = set()
|
|
287
|
+
for m in re.finditer(r'\b(?:const|let)\s+(\w+)\s*=\s*defineProps\b', code):
|
|
288
|
+
props_names.add(m.group(1))
|
|
289
|
+
for m in re.finditer(r'\bdefineProps\s*<\s*\{([^}]*)\}', code):
|
|
290
|
+
props_names.add('props')
|
|
291
|
+
if 'defineProps' in code and not props_names:
|
|
292
|
+
props_names.add('props')
|
|
293
|
+
|
|
294
|
+
for name in props_names:
|
|
295
|
+
for m in re.finditer(rf'\b{re.escape(name)}\.(\w+)\s*(?:=(?!=)|\+\+|--|\+=|-=)', code):
|
|
296
|
+
ln = _line_of(lens, m.start())
|
|
297
|
+
add(out, ctx, 'framework.vue-mutate-props', 'error', lens, ln,
|
|
298
|
+
f'直接修改了 props:`{name}.{m.group(1)} = …` —— props 是**单向数据流**,'
|
|
299
|
+
'子组件改它不会同步回父组件,父组件下次重渲染还会把值覆盖回去',
|
|
300
|
+
'把值 emit 给父组件让它改(`emit("update:xxx", 新值)` + 父侧 v-model),'
|
|
301
|
+
'或在子组件里用 ref 存一份本地副本', _unit_of(lens, ln))
|
|
302
|
+
|
|
303
|
+
# ── reactive 解构:解出来的是普通值,响应性当场断掉 ──
|
|
304
|
+
for m in re.finditer(r'(?:const|let)\s*\{([^}]+)\}\s*=\s*(\w+)\b', code):
|
|
305
|
+
var = m.group(2)
|
|
306
|
+
if not re.search(rf'\b(?:const|let)\s+{re.escape(var)}\s*=\s*reactive\s*\(', code):
|
|
307
|
+
continue
|
|
308
|
+
ln = _line_of(lens, m.start())
|
|
309
|
+
add(out, ctx, 'framework.vue-reactive-destructure', 'error', lens, ln,
|
|
310
|
+
f'解构了 `reactive` 对象 `{var}` —— 解出来的是**当时的普通值**,'
|
|
311
|
+
'响应性就此断掉:后面改它,视图不会更新',
|
|
312
|
+
f'用 `toRefs({var})` 解构(拿到的是 ref,保持响应),'
|
|
313
|
+
'或者干脆整个对象用,不解构', _unit_of(lens, ln))
|