@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,1072 @@
|
|
|
1
|
+
# 代码体检引擎的压力测试台:反向造缺陷 + 反误报 + 正向跑批 + 修复闭环。
|
|
2
|
+
#
|
|
3
|
+
# 为什么反向是主角:
|
|
4
|
+
# 一个只会说「未发现问题」的体检器和没有体检器是一回事,而且更糟——
|
|
5
|
+
# 它给人一个虚假的「已检查」印象。所以每条规则都要配一个**故意做坏的样本**,
|
|
6
|
+
# 断言那条规则真的会响;只要有一条规则从来没响过,这里就红。
|
|
7
|
+
#
|
|
8
|
+
# 为什么还要反误报:
|
|
9
|
+
# 规则写紧一点很容易,代价是天天在合规代码上乱叫,然后人把整个工具关掉。
|
|
10
|
+
# 所以另一半样本是**刻意写得很规矩的代码**,断言体检必修项为零。
|
|
11
|
+
#
|
|
12
|
+
# 用法:
|
|
13
|
+
# python code_battery.py # 全部:反向 + 反误报 + 正向 300 份 + 修复闭环
|
|
14
|
+
# python code_battery.py 800 # 正向跑 800 份
|
|
15
|
+
# python code_battery.py --quick # 只跑反向与反误报(秒级,改规则时用)
|
|
16
|
+
#
|
|
17
|
+
# 改了 code_lens / code_check / code_fix 任何一个,**必须重跑本文件**。
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import random
|
|
21
|
+
import re
|
|
22
|
+
import sys
|
|
23
|
+
import tempfile
|
|
24
|
+
|
|
25
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
26
|
+
import code_check as C # noqa: E402
|
|
27
|
+
import code_lens as L # noqa: E402
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# ══ 反向样本:每条规则一份「故意做坏的代码」 ═══════════════════════════════
|
|
31
|
+
# 格式:规则名 -> (文件名, 源码)。文件名决定语言与「是不是测试文件」。
|
|
32
|
+
DEFECTS = {
|
|
33
|
+
# ── 逻辑 ──
|
|
34
|
+
'logic.empty-catch': ('a.js', 'function f(x) {\n try {\n parse(x);\n } catch (e) {}\n return 1;\n}\n'),
|
|
35
|
+
'logic.empty-finally': ('a.js', 'function f() {\n try { go(); } catch (e) { log(e); } finally {}\n}\n'),
|
|
36
|
+
'logic.bare-except': ('a.py', 'def f():\n try:\n go()\n except:\n handle()\n'),
|
|
37
|
+
'logic.constant-condition': ('a.js', 'function f(x) {\n if (true) {\n return x;\n }\n return 0;\n}\n'),
|
|
38
|
+
'logic.self-compare': ('a.js', 'function f(a, b) {\n if (a === a) { return 1; }\n return b;\n}\n'),
|
|
39
|
+
'logic.self-assign': ('a.js', 'function f(count) {\n count = count;\n return count;\n}\n'),
|
|
40
|
+
'logic.assign-in-condition': ('a.js', 'function f(x) {\n if (x = 5) { return 1; }\n return 0;\n}\n'),
|
|
41
|
+
'logic.loose-eq': ('a.js', 'function f(a, b) {\n return a == b;\n}\n'),
|
|
42
|
+
'logic.mutable-default': ('a.py', 'def add(item, bucket=[]):\n bucket.append(item)\n return bucket\n'),
|
|
43
|
+
'logic.await-in-loop': (
|
|
44
|
+
'a.js',
|
|
45
|
+
'async function f(ids) {\n const out = [];\n for (const id of ids) {\n'
|
|
46
|
+
' const r = await fetchOne(id);\n out.push(r);\n }\n return out;\n}\n'),
|
|
47
|
+
'logic.java-string-eq': (
|
|
48
|
+
'A.java',
|
|
49
|
+
'public class A {\n public boolean same(String name, String code) {\n'
|
|
50
|
+
' return name == code;\n }\n}\n'),
|
|
51
|
+
# 与字符串字面量比较是更常见的形态,而它一度完全漏报(规则跑在掩码视图上,字面量早被抹空)
|
|
52
|
+
'logic.java-string-eq@literal': (
|
|
53
|
+
'B.java',
|
|
54
|
+
'public class B {\n public boolean active(Employee emp) {\n'
|
|
55
|
+
' return emp.getStatus() == "ACTIVE";\n }\n}\n'),
|
|
56
|
+
'logic.float-eq': ('a.js', 'function f(total) {\n if (total === 0.3) { return 1; }\n return 0;\n}\n'),
|
|
57
|
+
'logic.dead-code': (
|
|
58
|
+
'a.js',
|
|
59
|
+
'function f(x) {\n return x;\n log("never");\n}\n'),
|
|
60
|
+
# ── 注释 ──
|
|
61
|
+
'comment.param-mismatch': (
|
|
62
|
+
'a.js',
|
|
63
|
+
'/**\n * 取一行。\n * @param {string} userId 用户 id\n * @param {number} limit 条数\n */\n'
|
|
64
|
+
'export function fetchRow(id) {\n return db.get(id);\n}\n'),
|
|
65
|
+
'comment.param-missing': (
|
|
66
|
+
'a.js',
|
|
67
|
+
'/**\n * 取一行。\n * @param {string} id 用户 id\n */\n'
|
|
68
|
+
'export function fetchRow(id, limit, offset) {\n return db.get(id, limit, offset);\n}\n'),
|
|
69
|
+
'comment.return-mismatch': (
|
|
70
|
+
'a.js',
|
|
71
|
+
'/**\n * 算一下。\n * @returns {number} 结果\n */\n'
|
|
72
|
+
'export function calc(a, b) {\n store(a + b);\n}\n'),
|
|
73
|
+
'comment.name-mismatch': (
|
|
74
|
+
'a.js',
|
|
75
|
+
'export function loadUser(id) {\n return db.user(id);\n}\n\n'
|
|
76
|
+
'/**\n * loadUser: 按 id 取用户。\n */\nexport function loadOrder(id) {\n return db.order(id);\n}\n'),
|
|
77
|
+
'comment.missing-doc': (
|
|
78
|
+
'a.js',
|
|
79
|
+
'export function processBatch(items, opts) {\n let n = 0;\n for (const it of items) {\n'
|
|
80
|
+
' n += it.size;\n }\n const extra = opts.extra || 0;\n const total = n + extra;\n'
|
|
81
|
+
' return total;\n}\n'),
|
|
82
|
+
'comment.no-inline': (
|
|
83
|
+
'a.js',
|
|
84
|
+
'/** 汇总。 */\nexport function sum(rows) {\n' + ' let t = 0;\n' * 2
|
|
85
|
+
+ ''.join(f' t += rows[{i}].v;\n' for i in range(40)) + ' return t;\n}\n'),
|
|
86
|
+
'comment.commented-code': (
|
|
87
|
+
'a.js',
|
|
88
|
+
'function f(x) {\n // const old = compute(x);\n // if (old > 0) {\n // return old;\n'
|
|
89
|
+
' // }\n return x;\n}\n'),
|
|
90
|
+
'comment.echo': ('a.js', 'function f(user) {\n // 设置 name\n user.name = "x";\n return user;\n}\n'),
|
|
91
|
+
'comment.todo-orphan': ('a.js', 'function f(x) {\n // TODO 这里以后要支持批量\n return x;\n}\n'),
|
|
92
|
+
# ── 可读性 ──
|
|
93
|
+
'readability.long-function': (
|
|
94
|
+
'a.js', 'function big(x) {\n' + ''.join(f' const v{i} = x + {i};\n' for i in range(90)) + ' return x;\n}\n'),
|
|
95
|
+
'readability.deep-nesting': (
|
|
96
|
+
'a.js',
|
|
97
|
+
'function deep(a, b, c, d, e) {\n if (a) {\n if (b) {\n if (c) {\n if (d) {\n'
|
|
98
|
+
' if (e) {\n return 1;\n }\n }\n }\n }\n }\n return 0;\n}\n'),
|
|
99
|
+
'readability.high-complexity': (
|
|
100
|
+
'a.js',
|
|
101
|
+
'function branchy(x) {\n' + ''.join(f' if (x === {i}) return {i};\n' for i in range(20)) + ' return -1;\n}\n'),
|
|
102
|
+
'readability.too-many-params': (
|
|
103
|
+
'a.js', 'export function many(a, b, c, d, e, f, g, h) {\n return a + b + c + d + e + f + g + h;\n}\n'),
|
|
104
|
+
'readability.cryptic-param': (
|
|
105
|
+
'a.js',
|
|
106
|
+
'/** 处理。 */\nexport function handle(q) {\n' + ''.join(f' const s{i} = q + {i};\n' for i in range(18))
|
|
107
|
+
+ ' return q;\n}\n'),
|
|
108
|
+
'readability.long-file': ('a.js', ''.join(f'const v{i} = {i};\n' for i in range(1300))),
|
|
109
|
+
'readability.long-line': ('a.js', 'const x = "' + 'y' * 200 + '";\n'),
|
|
110
|
+
'readability.trailing-space': ('a.js', 'const x = 1; \nconst y = 2;\n'),
|
|
111
|
+
'readability.magic-number': ('a.js', 'function f(n) {\n return n * 86399;\n}\n'),
|
|
112
|
+
# ── 结构 ──
|
|
113
|
+
'structure.hardcoded-path': (
|
|
114
|
+
'src/a.js', 'function load() {\n return read("/Users/alice/work/data/config.json");\n}\n'),
|
|
115
|
+
'structure.debug-left': ('src/a.js', 'function f(x) {\n console.log("here", x);\n return x;\n}\n'),
|
|
116
|
+
'structure.duplicate-block': (
|
|
117
|
+
'a.js',
|
|
118
|
+
'function one(rows) {\n const acc = [];\n for (const r of rows) {\n acc.push(r.value * 2);\n'
|
|
119
|
+
' acc.push(r.value + 1);\n acc.push(r.label.trim());\n }\n return acc;\n}\n'
|
|
120
|
+
'function two(rows) {\n const acc = [];\n for (const r of rows) {\n acc.push(r.value * 2);\n'
|
|
121
|
+
' acc.push(r.value + 1);\n acc.push(r.label.trim());\n }\n return acc;\n}\n'),
|
|
122
|
+
# ── 测试 ──
|
|
123
|
+
'test.only-left': (
|
|
124
|
+
'a.test.js', 'describe("x", () => {\n it.only("works", () => {\n expect(f(1)).toBe(2);\n });\n});\n'),
|
|
125
|
+
'test.skip-no-reason': (
|
|
126
|
+
'a.test.js', 'describe("x", () => {\n it.skip("works", () => {\n expect(f(1)).toBe(2);\n });\n});\n'),
|
|
127
|
+
'test.weak-assert': (
|
|
128
|
+
'a.test.js', 'describe("x", () => {\n it("works", () => {\n assert(true);\n });\n});\n'),
|
|
129
|
+
'test.no-assert': (
|
|
130
|
+
'a_test.py', 'def test_user_created():\n user = make_user("a")\n save(user)\n reload(user)\n'),
|
|
131
|
+
# JS 主流写法单独一条:it('…', () => {…}) 不是函数声明,走的是另一条检测路径。
|
|
132
|
+
# 少了这条,JS 测试文件里「没有断言的测试」永远不会被报出来。
|
|
133
|
+
'test.no-assert@js': (
|
|
134
|
+
'b.test.js', 'describe("订单", () => {\n it("创建成功", () => {\n create(1);\n });\n});\n'),
|
|
135
|
+
'test.vague-name': (
|
|
136
|
+
'a_test.py', 'def test_1():\n assert make_user("a").name == "a"\n'),
|
|
137
|
+
'test.uncovered': None, # 跨文件规则,单独造(见 _defect_uncovered)
|
|
138
|
+
# ── 安全 ──
|
|
139
|
+
'security.hardcoded-secret': (
|
|
140
|
+
'a.js', 'const config = {\n apiKey: "sk_live_9f2b7c41d8e6a05374bc",\n};\nexport default config;\n'),
|
|
141
|
+
'security.sql-concat': (
|
|
142
|
+
'a.js', 'function find(id) {\n return db.query("SELECT * FROM users WHERE id = " + id);\n}\n'),
|
|
143
|
+
'security.command-injection': (
|
|
144
|
+
'a.js', 'function run(name) {\n return execSync("git checkout " + name);\n}\n'),
|
|
145
|
+
'security.eval': ('a.js', 'function run(src) {\n return eval(src);\n}\n'),
|
|
146
|
+
'security.weak-random': (
|
|
147
|
+
'a.js', 'function makeToken() {\n const token = Math.random().toString(36);\n return token;\n}\n'),
|
|
148
|
+
# ── 一致性 ──
|
|
149
|
+
'consistency.signature-drift': None, # 跨文件规则(见 _defect_drift)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ══ 反误报样本:刻意写得很规矩的代码,必修项必须为零 ═══════════════════════
|
|
154
|
+
CLEAN = {
|
|
155
|
+
'clean.js': '''/**
|
|
156
|
+
* 用户仓储:把「按 id 取用户」这件事收在一处,调用方不必关心缓存与降级。
|
|
157
|
+
* @param {string} id 用户 id(非空)
|
|
158
|
+
* @param {object} [opts] 可选项
|
|
159
|
+
* @returns {Promise<object|null>} 用户对象;不存在时返回 null
|
|
160
|
+
*/
|
|
161
|
+
export async function fetchUser(id, opts = {}) {
|
|
162
|
+
if (!id) return null;
|
|
163
|
+
try {
|
|
164
|
+
// 先走缓存:用户资料读多写少,回源一次的代价远高于一次缓存未命中
|
|
165
|
+
const hit = await cache.get(id);
|
|
166
|
+
if (hit != null) return hit;
|
|
167
|
+
const row = await db.query('SELECT * FROM users WHERE id = ?', [id]);
|
|
168
|
+
return row || null;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
logger.warn('取用户失败,降级为 null', { id, err });
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
''',
|
|
175
|
+
# 解构参数 + JSDoc `@param {object} p` / `@param p.field` —— 这是最规范的那种写法,
|
|
176
|
+
# 曾经被 param-mismatch 整份判成「写了不存在的形参」。固化在这里防回归。
|
|
177
|
+
'clean-destructure.js': '''/**
|
|
178
|
+
* 跑一条验证命令,失败就让智能体修,直到通过或到上限。
|
|
179
|
+
*
|
|
180
|
+
* @param {object} p
|
|
181
|
+
* @param {string} p.command 验证命令
|
|
182
|
+
* @param {number} [p.maxIters] 最多修复轮数(默认 5)
|
|
183
|
+
* @param {string} p.cwd 工作目录
|
|
184
|
+
* @returns {Promise<{passed:boolean}>} 是否通过
|
|
185
|
+
*/
|
|
186
|
+
export async function runVerifyLoop({ command, maxIters = 5, cwd }) {
|
|
187
|
+
for (let i = 0; i < maxIters; i += 1) {
|
|
188
|
+
// 每轮都重新跑一次:上一轮的修复可能带出新的失败,用旧结果判断会漏
|
|
189
|
+
const result = await capture(cwd, command);
|
|
190
|
+
if (result.code === 0) return { passed: true };
|
|
191
|
+
}
|
|
192
|
+
return { passed: false };
|
|
193
|
+
}
|
|
194
|
+
''',
|
|
195
|
+
'clean.py': '''"""结算:把金额一律按「分」的整数处理,避免浮点误差进账。"""
|
|
196
|
+
|
|
197
|
+
DEFAULT_RATE = 100
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def settle(orders, rate=None):
|
|
201
|
+
"""按费率汇总订单金额。
|
|
202
|
+
|
|
203
|
+
:param orders: 订单列表,每项需有 amount_cents 字段
|
|
204
|
+
:param rate: 费率(万分比整数),不传则用默认值
|
|
205
|
+
:return: 结算金额(分)
|
|
206
|
+
"""
|
|
207
|
+
if not orders:
|
|
208
|
+
return 0
|
|
209
|
+
factor = rate if rate is not None else DEFAULT_RATE
|
|
210
|
+
total = 0
|
|
211
|
+
for order in orders:
|
|
212
|
+
total += order.amount_cents * factor
|
|
213
|
+
return total // 10000
|
|
214
|
+
''',
|
|
215
|
+
'Clean.java': '''package com.example;
|
|
216
|
+
|
|
217
|
+
import java.util.Objects;
|
|
218
|
+
|
|
219
|
+
/** 订单服务:只负责编排,具体持久化交给仓储。 */
|
|
220
|
+
public class OrderService {
|
|
221
|
+
|
|
222
|
+
private final OrderRepo repo;
|
|
223
|
+
|
|
224
|
+
public OrderService(OrderRepo repo) {
|
|
225
|
+
this.repo = repo;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 按编号查订单。
|
|
230
|
+
*
|
|
231
|
+
* @param orderNo 订单号
|
|
232
|
+
* @return 订单;不存在时返回 null
|
|
233
|
+
*/
|
|
234
|
+
public Order findByNo(String orderNo) {
|
|
235
|
+
if (Objects.equals(orderNo, null)) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
return repo.selectByNo(orderNo);
|
|
240
|
+
} catch (DataAccessException ex) {
|
|
241
|
+
log.warn("查订单失败 orderNo={}", orderNo, ex);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
''',
|
|
247
|
+
'clean.test.js': '''import { fetchUser } from './clean.js';
|
|
248
|
+
|
|
249
|
+
describe('fetchUser', () => {
|
|
250
|
+
it('空 id 直接返回 null,不打库', async () => {
|
|
251
|
+
const result = await fetchUser('');
|
|
252
|
+
expect(result).toBe(null);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('命中缓存时不回源', async () => {
|
|
256
|
+
cache.set('u1', { id: 'u1' });
|
|
257
|
+
const result = await fetchUser('u1');
|
|
258
|
+
expect(result.id).toBe('u1');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
''',
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
# ══ Java 反向样本 ═══════════════════════════════════════════════════════════
|
|
266
|
+
# Java 的坑几乎全是「编译通过、运行不报错、出事时离现场很远」那一类,
|
|
267
|
+
# 所以每一条都必须有自己的缺陷样本——靠人肉复核"这条规则应该能响吧",
|
|
268
|
+
# 迟早会有某条正则写错了却没人发现,而报告照样显示「已体检」。
|
|
269
|
+
def _j(body, cls='Bad', head=''):
|
|
270
|
+
"""包一个最小可解析的 Java 类。"""
|
|
271
|
+
return f'package com.x;\n{head}\npublic class {cls} {{\n{body}\n}}\n'
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
JAVA_DEFECTS = {
|
|
275
|
+
'logic.java-print-stack-trace': (
|
|
276
|
+
'P.java', _j(' public void a() {\n try { go(); } catch (Exception e) { e.printStackTrace(); }\n }')),
|
|
277
|
+
'logic.java-catch-throwable': (
|
|
278
|
+
'P.java', _j(' public void a() {\n try { go(); } catch (Throwable t) { log.error("x", t); }\n }')),
|
|
279
|
+
'logic.java-broad-catch': (
|
|
280
|
+
'P.java', _j(' public void a() {\n try {\n'
|
|
281
|
+
+ ' step();\n' * 28
|
|
282
|
+
+ ' } catch (Exception e) { log.error("failed", e); }\n }')),
|
|
283
|
+
'logic.java-bigdecimal-double': (
|
|
284
|
+
'P.java', _j(' public void a(double d) {\n BigDecimal x = new BigDecimal(d);\n }')),
|
|
285
|
+
'logic.java-bigdecimal-equals': (
|
|
286
|
+
'P.java', _j(' public boolean a(BigDecimal amount, BigDecimal price) {\n'
|
|
287
|
+
' return amount.equals(price);\n }')),
|
|
288
|
+
'logic.java-unboxing-eq': (
|
|
289
|
+
'P.java', _j(' private Integer userId;\n private Integer otherId;\n'
|
|
290
|
+
' public boolean a() {\n return userId == otherId;\n }')),
|
|
291
|
+
'logic.java-shared-dateformat': (
|
|
292
|
+
'P.java', _j(' private static SimpleDateFormat FMT = new SimpleDateFormat("yyyy");')),
|
|
293
|
+
'logic.java-resource-leak': (
|
|
294
|
+
'P.java', _j(' public void a() throws Exception {\n'
|
|
295
|
+
' java.io.FileInputStream in = new java.io.FileInputStream("/tmp/x");\n'
|
|
296
|
+
' in.read();\n }')),
|
|
297
|
+
'logic.java-aslist-modify': (
|
|
298
|
+
'P.java', _j(' public void a() {\n List<String> xs = Arrays.asList("a", "b");\n'
|
|
299
|
+
' xs.add("c");\n }')),
|
|
300
|
+
'logic.java-concurrent-modify': (
|
|
301
|
+
'P.java', _j(' public void a(List<String> items) {\n for (String s : items) {\n'
|
|
302
|
+
' if (s.isEmpty()) { items.remove(s); }\n }\n }')),
|
|
303
|
+
'logic.java-concat-in-loop': (
|
|
304
|
+
'P.java', _j(' public String a(List<String> xs) {\n String out = "";\n'
|
|
305
|
+
' for (String x : xs) {\n out += x;\n }\n return out;\n }')),
|
|
306
|
+
'logic.java-int-division': (
|
|
307
|
+
'P.java', _j(' public double a(int hit, int total) {\n double rate = hit / total;\n'
|
|
308
|
+
' return rate;\n }')),
|
|
309
|
+
'logic.java-switch-no-default': (
|
|
310
|
+
'P.java', _j(' public void a(int t) {\n switch (t) {\n'
|
|
311
|
+
' case 1: go(); break;\n case 2: go(); break;\n }\n }')),
|
|
312
|
+
'logic.java-optional-get': (
|
|
313
|
+
'P.java', _j(' public String a(Optional<String> o) {\n return o.get();\n }')),
|
|
314
|
+
'logic.java-finally-return': (
|
|
315
|
+
'P.java', _j(' public int a() {\n try { return 1; } finally { return 2; }\n }')),
|
|
316
|
+
'logic.java-equals-no-hashcode': (
|
|
317
|
+
'P.java', _j(' @Override\n public boolean equals(Object o) { return this == o; }')),
|
|
318
|
+
# ── Spring / 持久层 ──
|
|
319
|
+
'framework.tx-not-effective': (
|
|
320
|
+
'S.java', _j(' @Transactional\n private void save(Order o) {\n mapper.insert(o);\n }',
|
|
321
|
+
cls='S', head='@Service')),
|
|
322
|
+
'framework.tx-no-rollback-for': (
|
|
323
|
+
'S.java', _j(' @Transactional\n public void save(Order o) throws BizException {\n'
|
|
324
|
+
' mapper.insert(o);\n }', cls='S', head='@Service')),
|
|
325
|
+
'framework.tx-swallow': (
|
|
326
|
+
'S.java', _j(' @Transactional\n public void save(Order o) {\n try {\n'
|
|
327
|
+
' mapper.insert(o);\n'
|
|
328
|
+
' } catch (DataAccessException e) {\n log.error("写入失败", e);\n }\n }',
|
|
329
|
+
cls='S', head='@Service')),
|
|
330
|
+
'framework.self-invocation': (
|
|
331
|
+
'S.java', _j(' @Async\n public void record(String x) { }\n\n'
|
|
332
|
+
' public void success(String x) {\n record(x);\n }',
|
|
333
|
+
cls='S', head='@Service')),
|
|
334
|
+
'framework.field-injection': (
|
|
335
|
+
'S.java', _j(' @Autowired\n private OrderMapper mapper;', cls='S', head='@Service')),
|
|
336
|
+
'framework.value-no-default': (
|
|
337
|
+
'S.java', _j(' @Value("${order.timeout}")\n private int timeout;', cls='S', head='@Service')),
|
|
338
|
+
'framework.singleton-mutable-state': (
|
|
339
|
+
'S.java', _j(' private List<String> cache = new ArrayList<>();', cls='S', head='@Service')),
|
|
340
|
+
'framework.mapping-no-method': (
|
|
341
|
+
'S.java', _j(' @RequestMapping("/save")\n public Result save(Order o) {\n'
|
|
342
|
+
' return Result.ok();\n }', cls='S', head='@RestController')),
|
|
343
|
+
'framework.mybatis-dollar': (
|
|
344
|
+
'M.java', _j(' @Select("SELECT * FROM orders WHERE status = ${status}")\n'
|
|
345
|
+
' List<Order> byStatus(String status);', cls='M', head='@Mapper')),
|
|
346
|
+
# ── dev-java「事务失效八景 / 常见坑 TOP」对应的机器规则 ──
|
|
347
|
+
'framework.tx-new-thread': (
|
|
348
|
+
'S.java', _j(' @Transactional\n public void save(Order o) {\n mapper.insert(o);\n'
|
|
349
|
+
' new Thread(() -> notifyUser(o)).start();\n }', cls='S', head='@Service')),
|
|
350
|
+
'logic.java-threadlocal-leak': (
|
|
351
|
+
'P.java', _j(' private static final ThreadLocal<String> CTX = new ThreadLocal<>();\n\n'
|
|
352
|
+
' public void handle(String tid) {\n CTX.set(tid);\n doWork();\n }')),
|
|
353
|
+
'logic.java-executors-factory': (
|
|
354
|
+
'P.java', _j(' private ExecutorService pool = Executors.newFixedThreadPool(10);')),
|
|
355
|
+
'logic.java-week-year': (
|
|
356
|
+
'P.java', _j(' public String fmt(Date d) {\n'
|
|
357
|
+
' return new SimpleDateFormat("YYYY-MM-dd").format(d);\n }')),
|
|
358
|
+
# ── Javadoc / JUnit ──
|
|
359
|
+
'comment.javadoc-throws': (
|
|
360
|
+
'P.java', _j(' /**\n * 保存。\n * @throws BizException 业务异常\n */\n'
|
|
361
|
+
' public void save(Order o) {\n mapper.insert(o);\n }')),
|
|
362
|
+
'test.junit-assert-order': (
|
|
363
|
+
'PTest.java', _j(' @Test\n public void a() {\n'
|
|
364
|
+
' assertEquals(service.compute(1), 42);\n }', cls='PTest')),
|
|
365
|
+
'test.junit-try-fail': (
|
|
366
|
+
'PTest.java', _j(' @Test\n public void a() {\n try {\n service.run(null);\n'
|
|
367
|
+
' fail("应该抛异常");\n } catch (IllegalArgumentException e) {\n'
|
|
368
|
+
' assertNotNull(e.getMessage());\n }\n }', cls='PTest')),
|
|
369
|
+
}
|
|
370
|
+
DEFECTS.update(JAVA_DEFECTS)
|
|
371
|
+
|
|
372
|
+
# ── Java 反误报样本:规矩写的 Spring 代码,必修必须为零 ──
|
|
373
|
+
CLEAN['CleanSpring.java'] = '''package com.example.order;
|
|
374
|
+
|
|
375
|
+
import java.math.BigDecimal;
|
|
376
|
+
import java.time.format.DateTimeFormatter;
|
|
377
|
+
import java.util.Objects;
|
|
378
|
+
import java.util.Optional;
|
|
379
|
+
|
|
380
|
+
/** 订单服务:只做编排,持久化交给仓储。 */
|
|
381
|
+
@Service
|
|
382
|
+
@RequiredArgsConstructor
|
|
383
|
+
public class OrderService {
|
|
384
|
+
|
|
385
|
+
private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
386
|
+
|
|
387
|
+
private final OrderRepository repository;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 按订单号结算。
|
|
391
|
+
*
|
|
392
|
+
* @param orderNo 订单号,不可为空
|
|
393
|
+
* @param rate 费率(万分比)
|
|
394
|
+
* @return 结算金额;订单不存在时返回 null
|
|
395
|
+
*/
|
|
396
|
+
@Transactional(rollbackFor = Exception.class)
|
|
397
|
+
public BigDecimal settle(String orderNo, int rate) {
|
|
398
|
+
if (Objects.equals(orderNo, null)) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
Optional<Order> found = repository.findByNo(orderNo);
|
|
402
|
+
if (found.isEmpty()) {
|
|
403
|
+
// 订单不存在是正常业务分支,不记 error 免得把告警刷满
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
Order order = found.get();
|
|
407
|
+
BigDecimal amount = BigDecimal.valueOf(order.getAmountCents())
|
|
408
|
+
.multiply(BigDecimal.valueOf(rate))
|
|
409
|
+
.movePointLeft(4);
|
|
410
|
+
repository.markSettled(order.getId(), amount);
|
|
411
|
+
return amount;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
'''
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
# ══ Python 反向样本 ═══════════════════════════════════════════════════════════
|
|
418
|
+
# Python 的坑分两种:一种是「语言层面反直觉」(共享可变类属性、闭包延迟绑定、
|
|
419
|
+
# except 顺序),另一种是「数据处理静默出错」(读表丢前导零、链式赋值改到副本)。
|
|
420
|
+
# 后一种最危险——程序跑完了、没有任何报错、结果是错的。
|
|
421
|
+
PY_DEFECTS = {
|
|
422
|
+
'logic.py-mutable-class-attr': (
|
|
423
|
+
'a.py', 'class Config:\n items = []\n'),
|
|
424
|
+
'logic.py-dataclass-mutable': (
|
|
425
|
+
'a.py', 'from dataclasses import dataclass\n\n\n@dataclass\nclass Row:\n tags: list = []\n'),
|
|
426
|
+
'logic.py-late-binding': (
|
|
427
|
+
'a.py', 'def build():\n fns = []\n for i in range(3):\n'
|
|
428
|
+
' fns.append(lambda: i)\n return fns\n'),
|
|
429
|
+
# 元组解包的循环变量一度漏报,而 `for idx, row in df.iterrows()` 正是 pandas 场景的标配写法
|
|
430
|
+
'logic.py-late-binding@unpack': (
|
|
431
|
+
'a.py', 'def build(df):\n fns = []\n for idx, row in df.iterrows():\n'
|
|
432
|
+
' fns.append(lambda: row["金额"])\n return fns\n'),
|
|
433
|
+
'logic.py-except-order': (
|
|
434
|
+
'a.py', 'def f(x):\n try:\n return int(x)\n'
|
|
435
|
+
' except Exception:\n return 0\n except ValueError:\n return -1\n'),
|
|
436
|
+
'logic.py-mutate-during-iteration': (
|
|
437
|
+
'a.py', 'def prune(items):\n for x in items:\n if not x:\n'
|
|
438
|
+
' items.remove(x)\n return items\n'),
|
|
439
|
+
'logic.py-is-literal': (
|
|
440
|
+
'a.py', 'def f(v):\n if v is 200:\n return True\n return False\n'),
|
|
441
|
+
'logic.py-eq-singleton': (
|
|
442
|
+
'a.py', 'def f(v):\n if v == None:\n return True\n return False\n'),
|
|
443
|
+
'logic.py-raise-without-from': (
|
|
444
|
+
'a.py', 'def f(s):\n try:\n return int(s)\n'
|
|
445
|
+
' except ValueError:\n raise RuntimeError("解析失败")\n'),
|
|
446
|
+
'logic.py-shadow-builtin': (
|
|
447
|
+
'a.py', 'def f():\n list = [1, 2]\n return list\n'),
|
|
448
|
+
'logic.py-assert-in-production': (
|
|
449
|
+
'a.py', 'def charge(amount):\n assert amount > 0, "金额必须为正"\n return amount\n'),
|
|
450
|
+
'logic.py-type-compare': (
|
|
451
|
+
'a.py', 'def f(v):\n if type(v) == str:\n return v\n return ""\n'),
|
|
452
|
+
'logic.py-blocking-in-async': (
|
|
453
|
+
'a.py', 'import time\n\n\nasync def fetch(url):\n time.sleep(1)\n return url\n'),
|
|
454
|
+
'logic.py-open-without-with': (
|
|
455
|
+
'a.py', 'def read(path):\n f = open(path)\n return f.read()\n'),
|
|
456
|
+
'logic.py-logging-fstring': (
|
|
457
|
+
'a.py', 'def run(log, n):\n log.info(f"处理了 {n} 条")\n'),
|
|
458
|
+
'logic.py-return-in-finally': (
|
|
459
|
+
'a.py', 'def f(x):\n try:\n return int(x)\n finally:\n return 0\n'),
|
|
460
|
+
'logic.empty-catch-narrow': (
|
|
461
|
+
'a.py', 'def f(d):\n try:\n return d["k"]\n except KeyError:\n pass\n'),
|
|
462
|
+
# ── pandas:事故不改外观、只改数字 ──
|
|
463
|
+
'framework.py-pandas-chained-assign': (
|
|
464
|
+
'a.py', 'import pandas as pd\n\n\ndef fix(df):\n df["单号"]["行"] = 1\n return df\n'),
|
|
465
|
+
'framework.py-read-no-dtype': (
|
|
466
|
+
'a.py', 'import pandas as pd\n\n\ndef load(path):\n return pd.read_excel(path)\n'),
|
|
467
|
+
'framework.py-pandas-append': (
|
|
468
|
+
'a.py', 'import pandas as pd\n\n\ndef add(df, row):\n'
|
|
469
|
+
' return df.append(row, ignore_index=True)\n'),
|
|
470
|
+
'framework.py-pandas-iterrows': (
|
|
471
|
+
'a.py', 'import pandas as pd\n\n\ndef walk(df):\n for idx, row in df.iterrows():\n'
|
|
472
|
+
' print(row)\n'),
|
|
473
|
+
'framework.py-pandas-inplace': (
|
|
474
|
+
'a.py', 'import pandas as pd\n\n\ndef clean(df):\n df.fillna(0, inplace=True)\n return df\n'),
|
|
475
|
+
# ── dev-python「常见坑 TOP」对应的机器规则 ──
|
|
476
|
+
'logic.py-open-no-encoding': (
|
|
477
|
+
'a.py', 'def read(path):\n return open(path).read()\n'),
|
|
478
|
+
'logic.py-naive-datetime': (
|
|
479
|
+
'a.py', 'from datetime import datetime\n\n\ndef stamp():\n return datetime.now()\n'),
|
|
480
|
+
'logic.py-decimal-float': (
|
|
481
|
+
'a.py', 'from decimal import Decimal\n\n\ndef price():\n return Decimal(0.1)\n'),
|
|
482
|
+
'logic.py-join-absolute': (
|
|
483
|
+
'a.py', 'import os\n\n\ndef full(base):\n return os.path.join(base, "/etc/passwd")\n'),
|
|
484
|
+
# ── 安全 ──
|
|
485
|
+
'security.py-subprocess-shell': (
|
|
486
|
+
'a.py', 'import subprocess\n\n\ndef run(name):\n'
|
|
487
|
+
' return subprocess.run("git checkout " + name, shell=True)\n'),
|
|
488
|
+
'security.py-yaml-load': (
|
|
489
|
+
'a.py', 'import yaml\n\n\ndef load(text):\n return yaml.load(text)\n'),
|
|
490
|
+
'security.py-pickle': (
|
|
491
|
+
'a.py', 'import pickle\n\n\ndef load(raw):\n return pickle.loads(raw)\n'),
|
|
492
|
+
}
|
|
493
|
+
DEFECTS.update(PY_DEFECTS)
|
|
494
|
+
|
|
495
|
+
# ── Python 反误报样本:规矩写的数据处理脚本,必修必须为零 ──
|
|
496
|
+
CLEAN['clean_pandas.py'] = '''"""对账:把两张表按单号对齐,找出金额对不上的行。"""
|
|
497
|
+
|
|
498
|
+
import logging
|
|
499
|
+
|
|
500
|
+
import pandas as pd
|
|
501
|
+
|
|
502
|
+
logger = logging.getLogger(__name__)
|
|
503
|
+
|
|
504
|
+
# 单号一律按文本读:它带前导零,一旦被推断成数字就再也补不回来
|
|
505
|
+
ID_DTYPE = {"单号": str, "客户编号": str}
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def reconcile(left_path, right_path):
|
|
509
|
+
"""比对两张表的金额。
|
|
510
|
+
|
|
511
|
+
:param left_path: 左表路径(业务系统导出)
|
|
512
|
+
:param right_path: 右表路径(财务系统导出)
|
|
513
|
+
:return: 金额不一致的行;两边都为空时返回空表
|
|
514
|
+
"""
|
|
515
|
+
left = pd.read_excel(left_path, dtype=ID_DTYPE)
|
|
516
|
+
right = pd.read_excel(right_path, dtype=ID_DTYPE)
|
|
517
|
+
if left.empty or right.empty:
|
|
518
|
+
logger.info("有一边是空表,跳过对账 left=%s right=%s", len(left), len(right))
|
|
519
|
+
return pd.DataFrame()
|
|
520
|
+
|
|
521
|
+
merged = left.merge(right, on="单号", how="outer", suffixes=("_业务", "_财务"))
|
|
522
|
+
# 金额按分比较:浮点直接相减会在小数位上抖出假差异
|
|
523
|
+
diff = (merged["金额_业务"].fillna(0) * 100).round() != (merged["金额_财务"].fillna(0) * 100).round()
|
|
524
|
+
return merged.loc[diff]
|
|
525
|
+
'''
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
# ══ JS / TS / Vue 反向样本 ═══════════════════════════════════════════════════
|
|
529
|
+
# JS 这一侧只有「掩码 + 括号配对」,没有真 AST,所以每条规则的判据都是刻意收窄过的
|
|
530
|
+
# ——越是收窄,越要有样本盯着它「还响不响」。
|
|
531
|
+
JSTS_DEFECTS = {
|
|
532
|
+
'logic.js-async-in-array-method': (
|
|
533
|
+
'a.js', 'export async function run(items) {\n'
|
|
534
|
+
' items.forEach(async (x) => { await save(x); });\n}\n'),
|
|
535
|
+
'logic.js-optional-chain-broken': (
|
|
536
|
+
'a.js', 'export function f(cfg) {\n return cfg?.user.name;\n}\n'),
|
|
537
|
+
'logic.js-nan-compare': (
|
|
538
|
+
'a.js', 'export function f(n) {\n if (n === NaN) { return 0; }\n return n;\n}\n'),
|
|
539
|
+
'logic.js-for-in-array': (
|
|
540
|
+
'a.js', 'export function f() {\n const list = [1, 2, 3];\n'
|
|
541
|
+
' for (const i in list) { console.log(i); }\n}\n'),
|
|
542
|
+
'logic.js-sort-no-comparator': (
|
|
543
|
+
'a.js', 'export function f() {\n const ids = [10, 9, 100];\n ids.sort();\n return ids;\n}\n'),
|
|
544
|
+
'logic.js-delete-array-index': (
|
|
545
|
+
'a.js', 'export function f(rows) {\n delete rows[0];\n return rows;\n}\n'),
|
|
546
|
+
'logic.js-parseint-radix': (
|
|
547
|
+
'a.js', 'export function f(s) {\n return parseInt(s);\n}\n'),
|
|
548
|
+
'logic.js-json-parse-unguarded': (
|
|
549
|
+
'a.js', 'export function f(raw) {\n return JSON.parse(raw);\n}\n'),
|
|
550
|
+
# ── TypeScript 类型逃逸 ──
|
|
551
|
+
'logic.ts-as-any': (
|
|
552
|
+
'a.ts', 'export function f(raw: string) {\n return (raw as any).parse();\n}\n'),
|
|
553
|
+
'logic.ts-ignore-no-reason': (
|
|
554
|
+
'a.ts', 'export function f(raw: string) {\n // @ts-ignore\n return raw.parse();\n}\n'),
|
|
555
|
+
'logic.ts-any-return': (
|
|
556
|
+
'a.ts', 'export function load(raw: string): any {\n return raw;\n}\n'),
|
|
557
|
+
# ── Vue 3:一半的问题在 <template> 里 ──
|
|
558
|
+
'framework.vue-vfor-no-key': (
|
|
559
|
+
'A.vue', '<script setup>\nconst items = []\n</script>\n\n'
|
|
560
|
+
'<template>\n <li v-for="item in items">{{ item.name }}</li>\n</template>\n'),
|
|
561
|
+
'framework.vue-key-is-index': (
|
|
562
|
+
'A.vue', '<script setup>\nconst rows = []\n</script>\n\n'
|
|
563
|
+
'<template>\n <li v-for="(row, index) in rows" :key="index">{{ row.t }}</li>\n</template>\n'),
|
|
564
|
+
'framework.vue-vif-with-vfor': (
|
|
565
|
+
'A.vue', '<script setup>\nconst rows = []\n</script>\n\n'
|
|
566
|
+
'<template>\n <li v-for="row in rows" :key="row.id" v-if="row.ok">{{ row.t }}</li>\n</template>\n'),
|
|
567
|
+
'framework.vue-v-html': (
|
|
568
|
+
'A.vue', '<script setup>\nconst html = ""\n</script>\n\n'
|
|
569
|
+
'<template>\n <div v-html="html" />\n</template>\n'),
|
|
570
|
+
'framework.vue-mutate-props': (
|
|
571
|
+
'A.vue', '<script setup>\nconst props = defineProps({ title: String })\n'
|
|
572
|
+
'function rename() {\n props.title = "x"\n}\n</script>\n\n'
|
|
573
|
+
'<template>\n <div @click="rename">{{ props.title }}</div>\n</template>\n'),
|
|
574
|
+
'framework.vue-reactive-destructure': (
|
|
575
|
+
'A.vue', '<script setup>\nimport { reactive } from "vue"\n'
|
|
576
|
+
'const state = reactive({ count: 0 })\nconst { count } = state\n</script>\n\n'
|
|
577
|
+
'<template>\n <div>{{ count }}</div>\n</template>\n'),
|
|
578
|
+
}
|
|
579
|
+
DEFECTS.update(JSTS_DEFECTS)
|
|
580
|
+
|
|
581
|
+
# ── 反误报:规矩写的 Vue 3 + TS 组件,必修必须为零 ──
|
|
582
|
+
CLEAN['CleanList.vue'] = '''<script setup lang="ts">
|
|
583
|
+
import { computed, ref, toRefs } from 'vue'
|
|
584
|
+
|
|
585
|
+
interface Row {
|
|
586
|
+
id: string
|
|
587
|
+
name: string
|
|
588
|
+
amountCents: number
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const props = defineProps<{ rows: Row[]; keyword: string }>()
|
|
592
|
+
const emit = defineEmits<{ (e: 'select', id: string): void }>()
|
|
593
|
+
|
|
594
|
+
const { rows } = toRefs(props)
|
|
595
|
+
const pageSize = ref(20)
|
|
596
|
+
|
|
597
|
+
// 过滤放在 computed 里而不是模板上:模板里写 v-if 会在每次渲染时重算一遍
|
|
598
|
+
const visible = computed(() => rows.value.filter((r) => r.name.includes(props.keyword)))
|
|
599
|
+
|
|
600
|
+
function pick(id: string): void {
|
|
601
|
+
emit('select', id)
|
|
602
|
+
}
|
|
603
|
+
</script>
|
|
604
|
+
|
|
605
|
+
<template>
|
|
606
|
+
<ul class="rows">
|
|
607
|
+
<li v-for="row in visible" :key="row.id" @click="pick(row.id)">
|
|
608
|
+
{{ row.name }} — {{ (row.amountCents / 100).toFixed(2) }}
|
|
609
|
+
</li>
|
|
610
|
+
</ul>
|
|
611
|
+
</template>
|
|
612
|
+
'''
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# ══ C# 反向样本 ═══════════════════════════════════════════════════════════════
|
|
616
|
+
# C# 的坑与 Java 同属「编译通过、运行不报错、出事时离现场很远」那一类,
|
|
617
|
+
# 但有几个是它独有的,而且都属于**写错了反而更像对的**:
|
|
618
|
+
# `async void` 比 `async Task` 短,异常却无人能接;
|
|
619
|
+
# `.Result` 比 `await` 直观,在 Web 环境必然死锁;
|
|
620
|
+
# `throw ex;` 比 `throw;` 更像在做事,堆栈却被重置。
|
|
621
|
+
CS_DEFECTS = {
|
|
622
|
+
'logic.cs-async-void': (
|
|
623
|
+
'A.cs', 'public class A {\n public async void Handle(long id) {\n'
|
|
624
|
+
' await Save(id);\n }\n}\n'),
|
|
625
|
+
'logic.cs-async-no-await': (
|
|
626
|
+
'A.cs', 'public class A {\n public async Task<int> Get() {\n return 1;\n }\n}\n'),
|
|
627
|
+
'logic.cs-blocking-async': (
|
|
628
|
+
'A.cs', 'public class A {\n public Order Get(long id) {\n'
|
|
629
|
+
' return _repo.GetAsync(id).Result;\n }\n}\n'),
|
|
630
|
+
'logic.cs-throw-ex': (
|
|
631
|
+
'A.cs', 'public class A {\n public void Run(long id) {\n try {\n'
|
|
632
|
+
' Work(id);\n } catch (Exception ex) {\n'
|
|
633
|
+
' Log(ex);\n throw ex;\n }\n }\n}\n'),
|
|
634
|
+
'logic.cs-lock-public': (
|
|
635
|
+
'A.cs', 'public class A {\n public void Sync() {\n lock (this) {\n'
|
|
636
|
+
' Work();\n }\n }\n}\n'),
|
|
637
|
+
'logic.cs-datetime-now': (
|
|
638
|
+
'A.cs', 'public class A {\n public DateTime Stamp() {\n return DateTime.Now;\n }\n}\n'),
|
|
639
|
+
'logic.cs-static-random': (
|
|
640
|
+
'A.cs', 'public class A {\n private static Random _rng = new Random();\n}\n'),
|
|
641
|
+
'logic.cs-double-for-money': (
|
|
642
|
+
'A.cs', 'public class A {\n private double totalAmount;\n}\n'),
|
|
643
|
+
'logic.cs-query-in-loop': (
|
|
644
|
+
'A.cs', 'public class A {\n public void Load(List<long> ids) {\n'
|
|
645
|
+
' foreach (var id in ids) {\n'
|
|
646
|
+
' var row = _db.Orders.FirstOrDefault(x => x.Id == id);\n'
|
|
647
|
+
' Use(row);\n }\n }\n}\n'),
|
|
648
|
+
'logic.cs-string-concat-loop': (
|
|
649
|
+
'A.cs', 'public class A {\n public string Join(List<string> parts) {\n'
|
|
650
|
+
' string result = "";\n foreach (var p in parts) {\n'
|
|
651
|
+
' result += p;\n }\n return result;\n }\n}\n'),
|
|
652
|
+
'logic.cs-linq-multiple-enumeration': (
|
|
653
|
+
'A.cs', 'public class A {\n public void Go(List<long> ids) {\n'
|
|
654
|
+
' var q = ids.Where(x => x > 0);\n var n = q.Count();\n'
|
|
655
|
+
' var f = q.FirstOrDefault();\n Use(n, f);\n }\n}\n'),
|
|
656
|
+
'logic.cs-count-vs-any': (
|
|
657
|
+
'A.cs', 'public class A {\n public bool Has(List<long> ids) {\n'
|
|
658
|
+
' return ids.Count() > 0;\n }\n}\n'),
|
|
659
|
+
'logic.cs-disposable-no-using': (
|
|
660
|
+
'A.cs', 'public class A {\n public void Read(string path) {\n'
|
|
661
|
+
' var fs = new FileStream(path, FileMode.Open);\n fs.ReadByte();\n }\n}\n'),
|
|
662
|
+
'logic.cs-sql-raw-concat': (
|
|
663
|
+
'A.cs', 'public class A {\n public void Find(string name) {\n'
|
|
664
|
+
' _db.Orders.FromSqlRaw($"SELECT * FROM Orders WHERE Name = \'{name}\'");\n }\n}\n'),
|
|
665
|
+
'logic.cs-di-wrong-lifetime': (
|
|
666
|
+
'A.cs', 'public class Startup {\n public void Configure(IServiceCollection services) {\n'
|
|
667
|
+
' services.AddSingleton<AppDbContext>();\n }\n}\n'),
|
|
668
|
+
'logic.cs-parse-no-culture': (
|
|
669
|
+
'A.cs', 'public class A {\n public decimal P(string s) {\n return decimal.Parse(s);\n }\n}\n'),
|
|
670
|
+
# ── dev-csharp「常见坑 TOP」对应的机器规则 ──
|
|
671
|
+
'logic.cs-new-httpclient': (
|
|
672
|
+
'A.cs', 'public class A {\n private HttpClient _c = new HttpClient();\n}\n'),
|
|
673
|
+
'logic.cs-case-compare': (
|
|
674
|
+
'A.cs', 'public class A {\n public bool Same(string a, string b) {\n'
|
|
675
|
+
' return a.ToLower() == b.ToLower();\n }\n}\n'),
|
|
676
|
+
'logic.cs-event-no-unsubscribe': (
|
|
677
|
+
'A.cs', 'public class A {\n public void Init(Timer timer) {\n'
|
|
678
|
+
' timer.Elapsed += OnTick;\n }\n'
|
|
679
|
+
' private void OnTick(object s, EventArgs e) { }\n}\n'),
|
|
680
|
+
'test.cs-async-void-test': (
|
|
681
|
+
'ATests.cs', 'public class ATests {\n [Fact]\n public async void Works() {\n'
|
|
682
|
+
' Assert.Equal(1, await GetAsync());\n }\n}\n'),
|
|
683
|
+
}
|
|
684
|
+
DEFECTS.update(CS_DEFECTS)
|
|
685
|
+
|
|
686
|
+
# ── C# 反误报样本 ──
|
|
687
|
+
# 本仓库没有 C# 代码,这一组的误报率没法像前四轮那样在真实项目上治。
|
|
688
|
+
# 补偿办法是把**真实项目里最常见、又最容易被误伤的正确写法**都写进来:
|
|
689
|
+
# 物化之后再多次使用的 LINQ、用于真实计数的 Count()、using 声明、UtcNow、decimal 算钱、
|
|
690
|
+
# `throw;` 原样重抛、私有锁对象、async Task 事件处理。这些正是误报的高风险区。
|
|
691
|
+
CLEAN['CleanService.cs'] = '''using System;
|
|
692
|
+
using System.Collections.Generic;
|
|
693
|
+
using System.Globalization;
|
|
694
|
+
using System.Linq;
|
|
695
|
+
using System.Threading.Tasks;
|
|
696
|
+
|
|
697
|
+
namespace Demo.Orders
|
|
698
|
+
{
|
|
699
|
+
/// <summary>订单结算:只做编排,持久化交给仓储。</summary>
|
|
700
|
+
public sealed class SettlementService
|
|
701
|
+
{
|
|
702
|
+
private readonly IOrderRepository _repository;
|
|
703
|
+
private readonly object _gate = new object();
|
|
704
|
+
|
|
705
|
+
public SettlementService(IOrderRepository repository)
|
|
706
|
+
{
|
|
707
|
+
_repository = repository;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/// <summary>按订单号结算,返回金额;订单不存在时返回 null。</summary>
|
|
711
|
+
public async Task<decimal?> SettleAsync(string orderNo, decimal rate)
|
|
712
|
+
{
|
|
713
|
+
if (string.IsNullOrWhiteSpace(orderNo))
|
|
714
|
+
{
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
var order = await _repository.FindByNoAsync(orderNo).ConfigureAwait(false);
|
|
719
|
+
if (order is null)
|
|
720
|
+
{
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// 先物化,后面要用两次——延迟查询被枚举两次会重复执行
|
|
725
|
+
var lines = order.Lines.Where(x => x.Enabled).ToList();
|
|
726
|
+
var lineCount = lines.Count;
|
|
727
|
+
var amount = lines.Sum(x => x.AmountCents) * rate / 10000m;
|
|
728
|
+
|
|
729
|
+
lock (_gate)
|
|
730
|
+
{
|
|
731
|
+
_lastSettledAt = DateTime.UtcNow;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
await _repository.MarkSettledAsync(order.Id, amount, lineCount).ConfigureAwait(false);
|
|
735
|
+
return amount;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/// <summary>导出对账明细。</summary>
|
|
739
|
+
public async Task ExportAsync(string path, IReadOnlyList<Order> orders)
|
|
740
|
+
{
|
|
741
|
+
using var writer = new StreamWriter(path);
|
|
742
|
+
foreach (var order in orders)
|
|
743
|
+
{
|
|
744
|
+
var amount = order.AmountCents / 100m;
|
|
745
|
+
await writer.WriteLineAsync(
|
|
746
|
+
$"{order.No},{amount.ToString("F2", CultureInfo.InvariantCulture)}")
|
|
747
|
+
.ConfigureAwait(false);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/// <summary>重试包装:原样重抛,不破坏堆栈。</summary>
|
|
752
|
+
public async Task<T> WithRetryAsync<T>(Func<Task<T>> action, int times)
|
|
753
|
+
{
|
|
754
|
+
for (var i = 0; i < times; i++)
|
|
755
|
+
{
|
|
756
|
+
try
|
|
757
|
+
{
|
|
758
|
+
return await action().ConfigureAwait(false);
|
|
759
|
+
}
|
|
760
|
+
catch (TimeoutException ex) when (i < times - 1)
|
|
761
|
+
{
|
|
762
|
+
_log.Warn("第 {0} 次超时,准备重试", i + 1, ex);
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
throw new InvalidOperationException("重试次数已用尽");
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private DateTime _lastSettledAt;
|
|
771
|
+
private readonly ILogger _log;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
'''
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
def _write(dirpath, name, text):
|
|
778
|
+
p = os.path.join(dirpath, name)
|
|
779
|
+
os.makedirs(os.path.dirname(p), exist_ok=True)
|
|
780
|
+
with open(p, 'w', encoding='utf-8') as f:
|
|
781
|
+
f.write(text)
|
|
782
|
+
return p
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _rules_hit(paths, **opts):
|
|
786
|
+
issues, _, _ = C.check_files(paths, **opts)
|
|
787
|
+
return {i.rule for i in issues}, issues
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
def _defect_uncovered(tmp):
|
|
791
|
+
"""跨文件:有测试文件,但某个公共函数在测试里从没出现过。"""
|
|
792
|
+
a = _write(tmp, 'svc.js',
|
|
793
|
+
'/** 结算。 */\nexport function settleInvoice(rows, rate) {\n let total = 0;\n'
|
|
794
|
+
' for (const row of rows) {\n if (!row) continue;\n'
|
|
795
|
+
' total += row.value * rate;\n }\n return total;\n}\n')
|
|
796
|
+
b = _write(tmp, 'other.test.js',
|
|
797
|
+
"import { somethingElse } from './other.js';\n"
|
|
798
|
+
"it('works', () => { expect(somethingElse(1)).toBe(1); });\n")
|
|
799
|
+
return [a, b]
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def _defect_drift(tmp):
|
|
803
|
+
"""跨文件:同名公共函数在两处参数个数不一致。"""
|
|
804
|
+
a = _write(tmp, 'x1.js', '/** 建单。 */\nexport function buildOrder(a, b) {\n return a + b;\n}\n')
|
|
805
|
+
b = _write(tmp, 'x2.js', '/** 建单。 */\nexport function buildOrder(a, b, c) {\n return a + b + c;\n}\n')
|
|
806
|
+
return [a, b]
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
SPECIAL = {'test.uncovered': _defect_uncovered, 'consistency.signature-drift': _defect_drift}
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def declared_rules():
|
|
813
|
+
"""所有已声明的规则名——直接复用 code_check.list_rules(),不自己再实现一遍。
|
|
814
|
+
|
|
815
|
+
用来断言**每条规则都配了反向样本**:新加一条规则却忘了配样本时,
|
|
816
|
+
那条规则从此没有任何保护,而压测台照样全绿。
|
|
817
|
+
"""
|
|
818
|
+
return {rule for rules in C.list_rules().values() for rule, _level in rules}
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def run_reverse():
|
|
822
|
+
"""反向:每条规则的缺陷样本都必须让那条规则响,且每条规则都要有样本。"""
|
|
823
|
+
missed, ok = [], 0
|
|
824
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
825
|
+
for rule, sample in DEFECTS.items():
|
|
826
|
+
sub = os.path.join(tmp, rule.replace('.', '_').replace('@', '_at_'))
|
|
827
|
+
os.makedirs(sub, exist_ok=True)
|
|
828
|
+
if sample is None:
|
|
829
|
+
paths = SPECIAL[rule](sub)
|
|
830
|
+
else:
|
|
831
|
+
name, src = sample
|
|
832
|
+
paths = [_write(sub, name, src)]
|
|
833
|
+
hit, issues = _rules_hit(paths)
|
|
834
|
+
if rule.split('@')[0] in hit:
|
|
835
|
+
ok += 1
|
|
836
|
+
else:
|
|
837
|
+
missed.append((rule, sorted(hit)))
|
|
838
|
+
return ok, missed
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def run_clean():
|
|
842
|
+
"""反误报:规矩写的代码必修项必须为零。"""
|
|
843
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
844
|
+
paths = [_write(tmp, n, s) for n, s in CLEAN.items()]
|
|
845
|
+
issues, _, stats = C.check_files(paths)
|
|
846
|
+
errs = [i for i in issues if i.level == 'error']
|
|
847
|
+
degraded = stats['degraded']
|
|
848
|
+
return errs, degraded, issues
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
# ══ URL 不是本机路径:hardcoded-path 是「建议」级,反误报样本只数必修管不到它,单独守 ══
|
|
852
|
+
URL_GUARD = {
|
|
853
|
+
# 文件名: (源码, 该不该报 hardcoded-path)
|
|
854
|
+
'urls.js': ('export const NS = "http://www.w3.org/2000/svg";\n'
|
|
855
|
+
'export const API = "https://api.example.com/home/data";\n'
|
|
856
|
+
'export const ICON = "data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\'%3E%3C/svg%3E";\n'
|
|
857
|
+
'export const CDN = "https://registry.npmmirror.com/tmp/pkg";\n', False),
|
|
858
|
+
'winpath.js': ('export function load() {\n return read("C:\\\\Users\\\\bob\\\\data\\\\cfg.json");\n}\n', True),
|
|
859
|
+
'fileurl.js': ('export const P = "file:///Users/alice/work/data.json";\n', True),
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def run_url_guard():
|
|
864
|
+
"""URL 与命名空间不许被当成本机路径;真正的本机路径(含 file:// 里的)照样要报。"""
|
|
865
|
+
bad = []
|
|
866
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
867
|
+
for name, (src, should) in URL_GUARD.items():
|
|
868
|
+
p = _write(tmp, 'src/' + name, src)
|
|
869
|
+
issues, _, _ = C.check_files([p])
|
|
870
|
+
hit = any(i.rule == 'structure.hardcoded-path' for i in issues)
|
|
871
|
+
if hit != should:
|
|
872
|
+
bad.append((name, '漏报了真正的本机路径' if should else '把 URL 当成了本机路径'))
|
|
873
|
+
return bad
|
|
874
|
+
|
|
875
|
+
|
|
876
|
+
# ══ 正向跑批:随机代码不许把引擎跑崩 ═══════════════════════════════════════
|
|
877
|
+
_NAMES = ['load', 'save', 'build', 'parse', 'render', 'sync', 'merge', 'flush', 'emit', 'wrap']
|
|
878
|
+
_TEMPLATES = [
|
|
879
|
+
'function {n}{i}({a}) {{\n if ({a}) {{ return {a} * {k}; }}\n return null;\n}}\n',
|
|
880
|
+
'const {n}{i} = ({a}) => {a} + {k};\n',
|
|
881
|
+
'class C{i} {{\n /** 做点事。 */\n {n}({a}) {{ return {a}; }}\n}}\n',
|
|
882
|
+
'async function {n}{i}({a}) {{\n try {{ return await go({a}); }} catch (e) {{ log(e); return null; }}\n}}\n',
|
|
883
|
+
]
|
|
884
|
+
_JAVA_TEMPLATES = [
|
|
885
|
+
'public class C{i} {{\n /** 做点事。 */\n public String {n}(String {a}) {{\n'
|
|
886
|
+
' if ({a} == null) {{ return null; }}\n return {a} + {k};\n }}\n}}\n',
|
|
887
|
+
'public class C{i} {{\n private final Repo repo;\n\n public C{i}(Repo repo) {{ this.repo = repo; }}\n\n'
|
|
888
|
+
' /** 查询。 */\n public List<String> {n}(Long {a}) {{\n'
|
|
889
|
+
' try {{\n return repo.find({a});\n'
|
|
890
|
+
' }} catch (DataAccessException e) {{\n log.warn("查询失败", e);\n return List.of();\n }}\n }}\n}}\n',
|
|
891
|
+
'@Service\npublic class C{i} {{\n /** 结算。 */\n'
|
|
892
|
+
' @Transactional(rollbackFor = Exception.class)\n'
|
|
893
|
+
' public int {n}(int {a}) {{\n return {a} * {k};\n }}\n}}\n',
|
|
894
|
+
]
|
|
895
|
+
_CS_TEMPLATES = [
|
|
896
|
+
'using System;\nusing System.Threading.Tasks;\n\nnamespace Demo {{\n'
|
|
897
|
+
' /// <summary>做点事。</summary>\n public sealed class C{i} {{\n'
|
|
898
|
+
' private readonly IRepo _repo;\n\n'
|
|
899
|
+
' public C{i}(IRepo repo) {{ _repo = repo; }}\n\n'
|
|
900
|
+
' /// <summary>按 id 取。</summary>\n'
|
|
901
|
+
' public async Task<decimal> {n}Async(long {a}) {{\n'
|
|
902
|
+
' var row = await _repo.GetAsync({a}).ConfigureAwait(false);\n'
|
|
903
|
+
' return row?.Amount ?? 0m;\n }}\n }}\n}}\n',
|
|
904
|
+
'namespace Demo {{\n /// <summary>值对象。</summary>\n'
|
|
905
|
+
' public sealed class C{i} {{\n private readonly object _gate = new object();\n\n'
|
|
906
|
+
' /// <summary>累加。</summary>\n public decimal {n}(decimal {a}) {{\n'
|
|
907
|
+
' lock (_gate) {{ return {a} * {k}m; }}\n }}\n }}\n}}\n',
|
|
908
|
+
]
|
|
909
|
+
_VUE_TEMPLATES = [
|
|
910
|
+
'<script setup lang="ts">\nimport {{ computed, ref }} from "vue"\n\n'
|
|
911
|
+
'const props = defineProps<{{ {a}: string }}>()\nconst n = ref({k})\n'
|
|
912
|
+
'// 过滤放 computed,模板里写 v-if 每次渲染都会重算\n'
|
|
913
|
+
'const shown = computed(() => props.{a}.length > n.value)\n</script>\n\n'
|
|
914
|
+
'<template>\n <div v-if="shown">{{{{ props.{a} }}}}</div>\n</template>\n',
|
|
915
|
+
'<script setup>\nconst rows = []\n</script>\n\n'
|
|
916
|
+
'<template>\n <li v-for="row in rows" :key="row.id">{{{{ row.{a} }}}}</li>\n</template>\n',
|
|
917
|
+
]
|
|
918
|
+
_PY_TEMPLATES = [
|
|
919
|
+
'def {n}_{i}({a}):\n """做点事。"""\n if {a}:\n return {a} * {k}\n return None\n\n',
|
|
920
|
+
'class C{i}:\n """类。"""\n\n def {n}(self, {a}):\n return {a}\n\n',
|
|
921
|
+
]
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def _rand_source(rng, lang):
|
|
925
|
+
tpls = {'py': _PY_TEMPLATES, 'java': _JAVA_TEMPLATES, 'vue': _VUE_TEMPLATES,
|
|
926
|
+
'csharp': _CS_TEMPLATES}.get(lang, _TEMPLATES)
|
|
927
|
+
parts = []
|
|
928
|
+
# Java 一个文件只放一个类:多个 public class 同文件不合法,
|
|
929
|
+
# 生成出来的东西必须是「真实项目里可能出现的形状」,否则压测的是一个不存在的场景。
|
|
930
|
+
count = 1 if lang in ('java', 'vue', 'csharp') else rng.randint(1, 8)
|
|
931
|
+
for i in range(count):
|
|
932
|
+
t = rng.choice(tpls)
|
|
933
|
+
parts.append(t.format(n=rng.choice(_NAMES), i=i, a=rng.choice(['x', 'value', 'rows', 'ctx']),
|
|
934
|
+
k=rng.randint(1, 99)))
|
|
935
|
+
return ''.join(parts)
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def run_forward(n, seed=20260911):
|
|
939
|
+
"""正向:随机生成 n 份代码,断言引擎不崩、行号不越界、报告能渲染。"""
|
|
940
|
+
rng = random.Random(seed)
|
|
941
|
+
crashed, bad_line = [], []
|
|
942
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
943
|
+
for i in range(n):
|
|
944
|
+
lang = rng.choice(['js', 'py', 'java', 'ts', 'vue', 'csharp'])
|
|
945
|
+
ext = {'js': '.js', 'ts': '.ts', 'py': '.py', 'java': '.java',
|
|
946
|
+
'vue': '.vue', 'csharp': '.cs'}[lang]
|
|
947
|
+
src = _rand_source(rng, lang if lang in ('py', 'java', 'vue', 'csharp') else 'js')
|
|
948
|
+
name = {'java': f'C{i}.java', 'vue': f'V{i}.vue',
|
|
949
|
+
'csharp': f'C{i}.cs'}.get(lang, f'g{i}{ext}')
|
|
950
|
+
src = src.replace('class C0', f'class C{i}').replace('C0(', f'C{i}(') if lang == 'java' else src
|
|
951
|
+
p = _write(tmp, name, src)
|
|
952
|
+
try:
|
|
953
|
+
issues, lenses, stats = C.check_files([p])
|
|
954
|
+
C.render_report(issues, stats, tmp)
|
|
955
|
+
total = len(src.split('\n'))
|
|
956
|
+
for it in issues:
|
|
957
|
+
if not (1 <= it.line <= total):
|
|
958
|
+
bad_line.append((p, it.rule, it.line, total))
|
|
959
|
+
except Exception as e: # noqa: BLE001 压测就是要抓住任何异常
|
|
960
|
+
crashed.append((p, f'{type(e).__name__}: {e}'))
|
|
961
|
+
finally:
|
|
962
|
+
try:
|
|
963
|
+
os.unlink(p)
|
|
964
|
+
except OSError:
|
|
965
|
+
pass # 临时文件清不掉不影响压测结论,故意忽略(目录整体会被回收)
|
|
966
|
+
return crashed, bad_line
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
def run_fix_loop():
|
|
970
|
+
"""修复闭环:造缺陷 → 自动修 → 复检,断言那条规则真的消失且文件仍可解析。
|
|
971
|
+
|
|
972
|
+
还要断言**不该自动修的没被碰**——loose-eq / self-assign 会改变行为,
|
|
973
|
+
它们必须原样留着报给人。一个偷偷把 `==` 改成 `===` 的修复器,
|
|
974
|
+
会在某次发布后制造一个没人能复现的线上故障。
|
|
975
|
+
"""
|
|
976
|
+
bad = []
|
|
977
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
978
|
+
for rule in sorted(C.FIXABLE):
|
|
979
|
+
sample = DEFECTS.get(rule)
|
|
980
|
+
if sample is None:
|
|
981
|
+
continue
|
|
982
|
+
name, src = sample
|
|
983
|
+
sub = os.path.join(tmp, 'fix_' + rule.replace('.', '_'))
|
|
984
|
+
os.makedirs(sub, exist_ok=True)
|
|
985
|
+
path = _write(sub, name, src)
|
|
986
|
+
before, _, _ = C.check_files([path])
|
|
987
|
+
if rule not in {i.rule for i in before}:
|
|
988
|
+
bad.append((rule, '缺陷样本本身就没触发该规则'))
|
|
989
|
+
continue
|
|
990
|
+
import code_fix as F
|
|
991
|
+
res = F.apply_fixes(path, write=True)
|
|
992
|
+
if not res.get('written'):
|
|
993
|
+
bad.append((rule, f'没有落盘:{res.get("aborted") or "无可修编辑"}'))
|
|
994
|
+
continue
|
|
995
|
+
after, lenses, _ = C.check_files([path])
|
|
996
|
+
if rule in {i.rule for i in after}:
|
|
997
|
+
bad.append((rule, '修完之后这条规则还在响'))
|
|
998
|
+
if lenses and not lenses[0].parse_ok:
|
|
999
|
+
bad.append((rule, '修完之后文件解析不了了'))
|
|
1000
|
+
# 反向:行为会变的两条,必须**不被**自动修
|
|
1001
|
+
for rule in ('logic.loose-eq', 'logic.self-assign'):
|
|
1002
|
+
name, src = DEFECTS[rule]
|
|
1003
|
+
sub = os.path.join(tmp, 'keep_' + rule.replace('.', '_'))
|
|
1004
|
+
os.makedirs(sub, exist_ok=True)
|
|
1005
|
+
path = _write(sub, name, src)
|
|
1006
|
+
import code_fix as F
|
|
1007
|
+
F.apply_fixes(path, write=True)
|
|
1008
|
+
with open(path, encoding='utf-8') as f:
|
|
1009
|
+
now = f.read()
|
|
1010
|
+
if now != src:
|
|
1011
|
+
bad.append((rule, '被自动改了!这条会改变行为,只许报给人'))
|
|
1012
|
+
return bad
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def main(argv):
|
|
1016
|
+
quick = '--quick' in argv
|
|
1017
|
+
nums = [a for a in argv if a.isdigit()]
|
|
1018
|
+
n = int(nums[0]) if nums else 300
|
|
1019
|
+
print('══ 代码体检引擎 压测台 ' + '═' * 46)
|
|
1020
|
+
|
|
1021
|
+
ok, missed = run_reverse()
|
|
1022
|
+
declared = declared_rules()
|
|
1023
|
+
covered = {k.split('@')[0] for k in DEFECTS}
|
|
1024
|
+
orphan = sorted(declared - covered)
|
|
1025
|
+
print(f'\n【反向造缺陷】{ok}/{len(DEFECTS)} 个样本触发了自己那条规则'
|
|
1026
|
+
f'(覆盖 {len(covered)}/{len(declared)} 条规则)')
|
|
1027
|
+
for rule, hit in missed:
|
|
1028
|
+
print(f' ✗ {rule} 没有响!该样本实际命中:{hit or "(什么都没报)"}')
|
|
1029
|
+
for rule in orphan:
|
|
1030
|
+
print(f' ✗ {rule} 有规则但没有反向样本——它从此不受任何保护')
|
|
1031
|
+
|
|
1032
|
+
errs, degraded, all_issues = run_clean()
|
|
1033
|
+
print(f'\n【反误报】合规样本:必修 {len(errs)} 条(应为 0)'
|
|
1034
|
+
f',全部提示 {len(all_issues)} 条')
|
|
1035
|
+
for e in errs:
|
|
1036
|
+
print(f' ✗ 误报 [{e.rule}] {os.path.basename(str(e.path))}:{e.line} {e.msg}')
|
|
1037
|
+
for p, why in degraded:
|
|
1038
|
+
print(f' ✗ 合规样本解析失败:{os.path.basename(str(p))} {why}')
|
|
1039
|
+
|
|
1040
|
+
url_bad = run_url_guard()
|
|
1041
|
+
print(f'\n【URL 不是本机路径】{len(URL_GUARD) - len(url_bad)}/{len(URL_GUARD)} 判对')
|
|
1042
|
+
for name, why in url_bad:
|
|
1043
|
+
print(f' ✗ {name}:{why}')
|
|
1044
|
+
|
|
1045
|
+
fix_bad = run_fix_loop()
|
|
1046
|
+
print(f'\n【修复闭环】可自动修 {len(sorted(C.FIXABLE))} 类:'
|
|
1047
|
+
+ ('全部「修完复检不再响、文件仍可解析」,且不该修的两条原样保留'
|
|
1048
|
+
if not fix_bad else f'{len(fix_bad)} 处不合格'))
|
|
1049
|
+
for rule, why in fix_bad:
|
|
1050
|
+
print(f' ✗ {rule}:{why}')
|
|
1051
|
+
|
|
1052
|
+
crashed, bad_line = ([], [])
|
|
1053
|
+
if not quick:
|
|
1054
|
+
crashed, bad_line = run_forward(n)
|
|
1055
|
+
print(f'\n【正向跑批】{n} 份随机代码:崩溃 {len(crashed)} · 行号越界 {len(bad_line)}')
|
|
1056
|
+
for p, why in crashed[:5]:
|
|
1057
|
+
print(f' ✗ {os.path.basename(p)} {why}')
|
|
1058
|
+
for p, rule, ln, total in bad_line[:5]:
|
|
1059
|
+
print(f' ✗ {os.path.basename(p)} [{rule}] 报在第 {ln} 行,文件只有 {total} 行')
|
|
1060
|
+
|
|
1061
|
+
bad = (len(missed) + len(errs) + len(degraded) + len(crashed)
|
|
1062
|
+
+ len(bad_line) + len(fix_bad) + len(orphan) + len(url_bad))
|
|
1063
|
+
print('\n' + '═' * 68)
|
|
1064
|
+
if bad == 0:
|
|
1065
|
+
print('✅ 全部通过:每条规则都会响,合规代码零必修,随机输入不崩。')
|
|
1066
|
+
else:
|
|
1067
|
+
print(f'❌ {bad} 处问题——规则不响或乱响,两样都会让这个体检器失去意义。')
|
|
1068
|
+
return 1 if bad else 0
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
if __name__ == '__main__':
|
|
1072
|
+
sys.exit(main(sys.argv[1:]))
|