@huicai/cli 2026.814.0 → 2026.1021.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 +139 -8
- package/bin/huicai.js +155 -74
- package/bin/update.js +97 -0
- 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/SKILL.md +2 -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/SKILL.md +9 -1
- package/skills/eng-develop/references/dod-checklist.md +3 -0
- package/skills/eng-develop/references/engineering-workflow.md +37 -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 -1
- 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 -1
- 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 -1
- 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/engineering.js +1 -0
- package/src/errkind.js +1 -1
- 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/ide.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 -1
- 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 -1
- 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 -1
- package/src/visual.js +1 -1
- 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
package/src/weekly.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{parseRange as n,cnRange as r,weeklyDefaults as o,ymd as s}from"./worklog.js";const i=[{key:"personal",title:"个人层面核心进展",required:!0},{key:"team",title:"团队层面核心成果",required:!0},{key:"issues",title:"存在问题及分析",required:!0},{key:"plan",title:"下周工作计划",required:!0}],c="需上级决策/支持事项",l=[["problem","具体问题"],["cause","根因分析"],["impact","影响预判"]],u=["有效","持续","不断","进一步","全面","深入","积极","大力","扎实","稳步","显著","切实","高度重视"],p=[/X{2,}/,/[X×]\s*月\s*[X×]\s*日/,/(?待填)?/,/\bTODO\b/i,/_{3,}/,/\{\{[^}]*\}\}/],a=/^(参加|参与|出席|列席|开了?|组织了?|配合|协助|跟进|对接)了?[^,。;]{0,12}(会议|会|评审|沟通|讨论)/,d=[["本周完成需求",/(?:完成|交付|上线)(?:了)?\s*(\d+)\s*(?:条|个)需求/g],["本周解决缺陷",/(?:解决|修复|修掉|关闭)(?:了)?\s*(\d+)\s*(?:条|个)(?:缺陷|bug)/gi],["提交",/(\d+)\s*次提交|提交\s*(\d+)\s*次/g],["需求",/(\d+)\s*(?:条|个)需求|需求\s*(\d+)\s*(?:条|个)/g],["缺陷",/(\d+)\s*(?:条|个)(?:缺陷|bug)|(?:缺陷|bug)\s*(\d+)\s*(?:条|个)/gi],["活跃仓库",/(\d+)\s*个(?:代码)?仓库/g],["文档",/(\d+)\s*(?:份|个)文档/g]];export function parseAnchors(e){const t=String(e||"").match(/【对账锚点】(.*)/);if(!t)return null;const n={};for(const e of t[1].split(/\s+/)){const t=e.split("=");2===t.length&&/^\d+$/.test(t[1])&&(n[t[0]]=Number(t[1]))}return Object.keys(n).length?n:null}const h=/[0-90-9]/;function f(e){if(null==e)return null;if("string"==typeof e){const t=e.trim();return t?{lead:"",text:t}:null}const t=String(e.text??e.content??"").trim(),n=String(e.lead??e.title??"").trim();return t||n?t?{lead:n,text:t}:{lead:"",text:n}:null}function g(e){return(Array.isArray(e)?e:[]).map(f).filter(Boolean)}function m(e,t){return`${e}、${t.lead?`**${t.lead}**${t.text}`:t.text}`}function $(e){if("string"==typeof e.period&&e.period.trim())return{text:e.period.trim(),range:null};const t=e.period&&"object"==typeof e.period?e.period:e,o=n({since:t.since,until:t.until,days:t.days,week:t.week});return{text:r(o),range:o}}export function renderWeekly(e={}){const t=$(e),n=[];n.push(`汇报周期:${t.text}`),n.push(`汇报人:${String(e.reporter||"").trim()||"XXX"}`);const r=String(e.conclusion||"").trim();r&&n.push(`核心结论:${r}`),n.push("");for(const t of i){if(n.push(`【${t.title}】`),"issues"===t.key){const t=e.issues;if(t&&!Array.isArray(t)&&"object"==typeof t){let e=0;for(const[r,o]of l){const s=g(Array.isArray(t[r])?t[r]:t[r]?[t[r]]:[]);if(s.length)for(const t of s){e++;const r=t.lead?`**${t.lead}**${t.text}`:t.text;n.push(`${e}、${o}:${r}`)}}e||(n.push("1、具体问题:"),n.push("2、根因分析:"))}else{const e=g(t);e.length?e.forEach((e,t)=>n.push(m(t+1,e))):(n.push("1、具体问题:"),n.push("2、根因分析:"))}}else{const r=g(e[t.key]);r.length?r.forEach((e,t)=>n.push(m(t+1,e))):(n.push("1、"),n.push("2、"))}n.push(""),n.push("")}const o=g(e.asks);return o.length&&(n.push(`【${c}】`),o.forEach((e,t)=>n.push(m(t+1,e))),n.push("")),n.join("\n").replace(/\n{3,}$/,"\n")+"\n"}export function toWordMarkdown(e){return String(e||"").replace(/^【(.+?)】\s*$/gm,"## $1")}export function parseWeekly(e){const t=String(e||"").split(/\r?\n/),n={period:"",reporter:"",conclusion:""},r=new Map;let o=null;for(const e of t){const t=e.trim(),s=t.match(/^【(.+?)】\s*$/);if(s){o=s[1].trim(),r.has(o)||r.set(o,[]);continue}if(!o){const e=t.match(/^汇报周期[::](.*)$/),r=t.match(/^汇报人[::](.*)$/),o=t.match(/^核心结论[::](.*)$/);e?n.period=e[1].trim():r?n.reporter=r[1].trim():o&&(n.conclusion=o[1].trim());continue}const i=t.match(/^(\d+)\s*[、..))]\s*(.*)$/);if(i)r.get(o).push({no:Number(i[1]),text:i[2].trim(),raw:t});else if(t&&r.get(o).length){const e=r.get(o)[r.get(o).length-1];e.text+=t,e.raw+=t}}return{head:n,secs:r}}const x=e=>String(e||"").replace(/\*\*/g,"").trim();function y(e,t){const n=e=>{const t=x(e).replace(/[\s,。、;:,.;:]/g,""),n=new Set;for(let e=0;e+1<t.length;e++)n.add(t.slice(e,e+2));return n},r=n(e),o=n(t);if(r.size<4||o.size<4)return 0;let s=0;for(const e of r)o.has(e)&&s++;return s/Math.min(r.size,o.size)}export function checkWeekly(e,{range:t=null,evidence:n="",prev:r=""}={}){const{head:o,secs:c}=parseWeekly(e),f=[],g=(e,t,n,r)=>f.push({level:e,where:t,what:n,how:r});if(o.period){const e=o.period.match(/(\d{4})年(\d{1,2})月(\d{1,2})日\s*[-—~至]\s*(\d{4})年(\d{1,2})月(\d{1,2})日/);if(e){const n=new Date(+e[1],+e[2]-1,+e[3]),r=new Date(+e[4],+e[5]-1,+e[6]),o=Math.round((r-n)/864e5)+1;if(o<=0?g("must","抬头","汇报周期起止反了",`把 ${s(n)} 与 ${s(r)} 对调`):o>31&&g("should","抬头",`汇报周期跨度 ${o} 天,不像一份周报`,"确认是不是月报/季度报,或起止日期写错了"),t){const e=s(t.since),o=s(t.until);s(n)===e&&s(r)===o||g("must","抬头",`汇报周期与取数区间不一致:正文写 ${s(n)}~${s(r)},实际取的是 ${e}~${o}`,"两者必须一致,否则周报里的数字对应的不是它声称的那一周")}}else g("must","抬头",`汇报周期格式不对:「${o.period}」`,"写成「2026年08月28日-2026年09月03日」(四位年、两位月日、中间一个短横)")}else g("must","抬头","缺「汇报周期」行","补一行「汇报周期:2026年08月28日-2026年09月03日」");o.reporter?/^X+$/i.test(o.reporter.replace(/\s/g,""))&&g("must","抬头","汇报人还是占位符","填真实姓名"):g("must","抬头","缺「汇报人」行","补一行「汇报人:<部门> <姓名>」");const m=[...c.keys()];for(const e of i)c.has(e.title)||g("must",`【${e.title}】`,"模板要求的小节缺失",`补上【${e.title}】小节`);const $=i.map(e=>e.title).filter(e=>m.includes(e)),k=m.filter(e=>$.includes(e));$.join("|")!==k.join("|")&&g("should","整体",`小节顺序与模板不一致:${k.join(" → ")}`,`按模板顺序排:${$.join(" → ")}`);for(const e of i){const t=c.get(e.title);if(!t)continue;const n=`【${e.title}】`,r=t.filter(e=>x(e.text).length>0);if(!r.length){g("must",n,"整节是空的(只有编号没有内容)","这一节必须写;确实没有内容也要写清「本周无」并说明原因");continue}const o=t.map(e=>e.no);for(let e=0;e<o.length;e++)if(o[e]!==e+1){g("should",n,`编号不连续:第 ${e+1} 条写的是「${o[e]}、」`,"改成 1、2、3… 连续编号");break}for(const e of r)for(const t of p)if(t.test(x(e.text))){g("must",`${n} 第 ${e.no} 条`,`占位符没填:「${x(e.text).slice(0,40)}」`,"填成真实内容——占位符发到领导那儿是硬伤");break}const s=r.filter(e=>h.test(x(e.text)));if(s.length){if(s.length<Math.ceil(.6*r.length)){g("should",n,`第 ${r.filter(e=>!h.test(x(e.text))).map(e=>e.no).join("、")} 条没有任何数字`,"给这几条补上量化结果(做了多少、快了多少、还剩多少)")}}else g("must",n,"整节没有一个数字","每条都要有可被追问的锚点:完成率/条数/耗时/日期/金额。没有数字的进展等于没汇报");for(const e of r){const t=x(e.text).length;t<12?g("should",`${n} 第 ${e.no} 条`,`只有 ${t} 个字,等于没写:「${x(e.text)}」`,"补上「做了什么 + 到什么程度 + 结果如何」"):t>220&&g("note",`${n} 第 ${e.no} 条`,`${t} 个字,读者抓不住重点`,"拆成两条,或把过程描述压掉只留结论与数字")}const i=[];for(const e of r){const t=x(e.text),n=u.filter(e=>t.includes(e));n.length>=2&&i.push(`第 ${e.no} 条(${n.join("/")})`)}i.length&&g("should",n,`空洞副词堆砌:${i.join("、")}`,"把「有效提升」换成提升了多少、「持续推进」换成推进到哪一步");for(let e=0;e<r.length;e++)for(let t=e+1;t<r.length;t++)y(r[e].text,r[t].text)>=.7&&g("should",n,`第 ${r[e].no} 条与第 ${r[t].no} 条说的是同一件事`,"合并成一条,或把两者的差别写出来");const l=r.filter(e=>a.test(x(e.text))&&!h.test(x(e.text))).map(e=>e.no);l.length&&g("note",n,`第 ${l.join("、")} 条是流水账(只说参加了什么,没说结果)`,"写清这个会议/评审得出了什么结论、推动了哪件事往前走")}const w=c.get("存在问题及分析");if(w&&w.length){const e=w.map(e=>x(e.text)).join("\n");for(const[,t]of l.slice(0,2))e.includes(t)||g("must","【存在问题及分析】",`缺「${t}」`,`模板要求这一节写清「具体问题:…」和「根因分析:…」,补上「${t}:」这一条`);const t=/^(暂无|无|没有|不涉及|暂时没有|暂无异常)(问题|风险|异常)?[。.,,;;]?$/,n=w.map(e=>x(e.text).replace(/^(具体问题|根因分析|影响预判)\s*[::]/,"").trim()).filter(e=>t.test(e));n.length&&g("should","【存在问题及分析】",`有 ${n.length} 条写成「${n[0]}」`,"一周没有任何问题几乎不可能。真没有阻塞也要写「当前风险点是什么、为什么可控」——藏住的问题会在月报里爆炸")}const v=c.get("下周工作计划");if(v&&v.length){v.filter(e=>/\d{1,2}\s*月\s*\d{1,2}\s*日|\d{1,2}[-/]\d{1,2}|周[一二三四五六日天]|前完成|之前/.test(x(e.text))).length||g("should","【下周工作计划】","没有一条带时间点","每条给出「谁在几号前交什么」,否则下周复盘时无法判定是否达成")}const S=parseAnchors(n);if(S){let t=x(e);for(const[e,n]of d){n.lastIndex=0;const r=[];let o;for(;o=n.exec(t);)r.push({text:o[0],at:o.index,n:Number(o.slice(1).find(e=>void 0!==e))});for(const e of r)t=t.slice(0,e.at)+" ".repeat(e.text.length)+t.slice(e.at+e.text.length);if(e in S)for(const t of r)Number.isFinite(t.n)&&t.n!==S[e]&&g("must","正文",`「${t.text.trim()}」与取证对不上:取证包里${e}是 ${S[e]}`,`改成 ${S[e]},或说清这个 ${t.n} 是从哪儿来的(不在本周期取证范围内的就别写进本周报)`)}}else n&&g("note","整体","取证包里没有【对账锚点】行,本次跳过了数字对账","用work_evidence取证(它会带上锚点),数字才对得了账");if(r){const t=parseWeekly(r).secs.get("下周工作计划")||[],n=x(String(e).replace(/\*\*[^*]+\*\*/g,"")),o=[];for(const e of t){const t=x(String(e.text).replace(/^\*\*[^*]+\*\*/,""));if(!t)continue;const r=(t.match(/[\u4e00-\u9fa5]+/g)||[]).join(""),s=new Set;for(let e=0;e+4<=r.length;e++)s.add(r.slice(e,e+4));let i=0;for(const e of s)if(n.includes(e)&&++i>=2)break;s.size&&i<2&&o.push(t.slice(0,40))}o.length&&g("should","整体",`上周计划的 ${o.length} 项在本周报里找不到任何呼应:${o.map(e=>`「${e}」`).join("、")}`,"每一项都要有交代——做完了写进进展,没做完写进【存在问题及分析】或顺延到下周计划并说明原因。上周承诺的事这周只字不提,是周报最伤信用的一种写法")}return f}export function formatCheck(e){const t=e.filter(e=>"must"===e.level),n=e.filter(e=>"should"===e.level),r=e.filter(e=>"note"===e.level),o=[];o.push(`【周报体检】必修 ${t.length} 条 · 建议 ${n.length} 条 · 提示 ${r.length} 条`);const s=(e,t)=>{t.length&&(o.push(`── ${e} ──`),t.forEach((e,t)=>{o.push(`${t+1}. ${e.where}|${e.what}`),o.push(` 改法:${e.how}`)}))};return s("必修(不改就别发)",t),s("建议",n),s("提示",r),e.length?t.length||o.push("必修为 0,格式上可以发了。建议项按需处理。"):o.push("未发现模板层面的问题。注意:体检只查得了结构与措辞,**数字对不对它无从判断**——请自己核一遍来源。"),o.join("\n")}export function makeWeeklyTools({cwd:n}){return[{readOnly:!1,spec:{type:"function",function:{name:"weekly_report",description:'按固定模板渲染周报,并**出片即体检**(也可单独体检别人写的周报)。\n模板骨架固定:汇报周期 / 汇报人 →【个人层面核心进展】【团队层面核心成果】【存在问题及分析】【下周工作计划】,给了asks才多出【需上级决策/支持事项】。每条支持「加粗引导词 + 正文」的写法(lead=「核心事项推进」,text=「完成…」),这是范本的样子,也便于领导扫读。\n**你只管每条写什么,骨架与编号交给它**——不要自己拼模板,小节名、编号符号、周期写法每周变一点,读的人每次都要重新找落点。\n体检查这几类:占位符残留、整节空白、编号断号、整节无数字锚点、条目过短/过长、空洞副词堆砌、同一件事写两遍、流水账、下周计划缺时间点、「存在问题」写成「暂无」、汇报周期与实际取数区间不符。判定分必修/建议/提示,**必修为 0 才算能发**。\n注意体检**不判断数字真假**——它只保证你写了可被追问的数字,对不对要自己核。\n要Word版:先用format:"word" 渲染,再把正文交给create_doc的spec.markdown。(直接拿md版去出Word会得到一份没有任何标题、目录空白的文档。)',parameters:{type:"object",properties:{action:{type:"string",description:"render(默认,渲染并体检)| check(只体检已有周报)"},path:{type:"string",description:"render:输出文件(默认 周报-<结束日>.md);check:要体检的周报文件"},text:{type:"string",description:"check:直接给周报全文(与path二选一)"},evidence:{type:"string",description:"`work_evidence` 的完整输出。给了就用它末尾的【对账锚点】回头核对正文里的原始计数(提交次数/需求条数/缺陷解决数/文档份数)——写了个取证里没有的数字会当场判必修。**每次成稿都该带上它**,这是本工具唯一能机检数字真伪的地方。"},format:{type:"string",description:"md(默认)= 模板原样(【小节】+ 1、),可直接发微信/邮件;word = 小节转成 `## 标题`,专供交给create_doc出Word——不转的话生成的docx一个标题都没有,目录域和导航窗格全是空的。"},prev:{type:"string",description:"上一期周报(文件路径或全文)。给了就检查「上周计划的事这周有没有交代」——上周承诺、这周只字不提,是周报最伤信用的写法,也最容易被漏掉。"},spec:{type:"object",additionalProperties:!0,description:'周报内容。字段:period{since,until} 或 {days} 或 {week:"this"|"last"} 或直接给中文串;reporter汇报人;conclusion核心结论(选填,一段话讲清整体完成情况与风险判断);personal[] 个人层面核心进展;team[] 团队层面核心成果;issues{problem[],cause[],impact[]}(模板要求「具体问题」「根因分析」都要有;impact选填)或直接给数组;plan[] 下周工作计划(每条要带时间点);asks[] 需上级决策/支持事项(选填)。\n每条可以是字符串,也可以是 {lead:"核心事项推进", text:"完成…"}。'}},required:[]}}},run:async r=>{const i=String(r?.action||"render").trim();let c=String(r?.prev||"");if(c&&!/\n/.test(c)&&c.length<400){const r=t.resolve(n,c);c=e.existsSync(r)?e.readFileSync(r,"utf8"):""}const l=String(r?.evidence||"");if("check"===i){let o=String(r?.text||"");if(!o&&r?.path){const s=t.resolve(n,String(r.path));if(!e.existsSync(s))return`找不到文件:${r.path}`;o=e.readFileSync(s,"utf8")}return o.trim()?formatCheck(checkWeekly(o,{evidence:l,prev:c})):"check需要text(周报全文)或path(周报文件)。"}const u=r?.spec&&"object"==typeof r.spec?{...r.spec}:{};let p;u.reporter||(u.reporter=o(n).reporter||"");let a=null;try{a=$(u).range,p=renderWeekly(u)}catch(e){return"渲染失败:"+e.message}const d=checkWeekly(p,{range:a,evidence:l,prev:c}),h="word"===String(r?.format||"md").trim(),f=h?toWordMarkdown(p):p,g=s(a?a.until:new Date);let m="",x="";try{const o=function(n,r,o){const s=String(r||"").trim()||o,i=t.resolve(n,s);if(i!==n&&!i.startsWith(n+t.sep))throw new Error("输出路径越出工作目录:"+s);return e.mkdirSync(t.dirname(i),{recursive:!0}),i}(n,r?.path,`周报-${g}.md`);e.writeFileSync(o,f,"utf8"),m=t.relative(n,o)||t.basename(o)}catch(e){m="",x=e.message}const y=d.filter(e=>"must"===e.level).length;return[m?`已生成:${m}`:`(未落盘${x?":"+x:",仅返回正文"})`,"",h?"───── 正文(Word版:小节已转成 ## 标题)─────":"───── 正文 ─────",f.trimEnd(),"───────────────","",formatCheck(d),"",l?"数字对账:正文里的原始计数已与取证包核对过;推算值(完成率、耗时、百分比)工具判不了,请自己核算法。":"数字对账:本次**没带evidence,数字没有对过账**——把work_evidence的完整输出传进evidence重跑一次,否则「本周 23 次提交」这类与取证矛盾的数字没人拦得住。",y?`下一步:先把上面 ${y} 条必修改掉,再调一次本工具重渲染;必修没清零不要发给领导。`:h?"下一步:把上面的正文交给create_doc的spec.markdown出Word。注意条目编号会被Word的列表样式渲染成「1.」而不是模板的「1、」——那是Word惯例,但要让用户知道两份文件在这里不一致。":'下一步:确认无误后即可发出。要Word版请改用format:"word" 再渲染一次。'].join("\n")}}]}
|
package/src/workflow.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import r from"node:crypto";import{Worker as n}from"node:worker_threads";import{CONFIG_DIR as o}from"./config.js";export const MAX_AGENTS_PER_RUN=200;const a=36e5;export class WorkflowUsageError extends Error{constructor(e){super(e),this.name="WorkflowUsageError"}}export const RUNS_DIR=t.join(o,"workflows");export function pruneRuns(r=Date.now()){let n;try{n=e.readdirSync(RUNS_DIR)}catch{return 0}const o=[];for(const r of n)if(r.startsWith("wf-"))try{o.push({n:r,t:e.statSync(t.join(RUNS_DIR,r)).mtimeMs})}catch{}o.sort((e,t)=>t.t-e.t);const a=r-12096e5;let s=0;return o.forEach((r,n)=>{if(!(n<30&&r.t>=a))try{e.rmSync(t.join(RUNS_DIR,r.n),{recursive:!0,force:!0}),s++}catch{}}),s}function s(r,{script:n,meta:o,args:a}){const s=t.join(RUNS_DIR,r);try{!function(t){e.mkdirSync(t,{recursive:!0,mode:448});try{e.chmodSync(t,448)}catch{}}(s),e.writeFileSync(t.join(s,"script.js"),String(n),{mode:384}),e.writeFileSync(t.join(s,"meta.json"),JSON.stringify({runId:r,meta:o,args:a,startedAt:(new Date).toISOString()},null,2),{mode:384})}catch{return{dir:s,append:()=>{},finish:()=>{},ok:!1}}const i=t.join(s,"journal.jsonl");return{dir:s,ok:!0,append(t){try{e.appendFileSync(i,JSON.stringify(t)+"\n",{mode:384})}catch{}},finish(r){try{const n=t.join(s,"meta.json"),o=JSON.parse(e.readFileSync(n,"utf8"));e.writeFileSync(n,JSON.stringify({...o,...r,finishedAt:(new Date).toISOString()},null,2),{mode:384})}catch{}}}}export function loadResume(r){const n=new Map;if(!r)return n;let o;try{o=e.readFileSync(t.join(RUNS_DIR,String(r),"journal.jsonl"),"utf8").split("\n")}catch{return n}for(const e of o){if(!e.trim())continue;let t;try{t=JSON.parse(e)}catch{continue}if("agent"!==t.type||!t.ok||!t.key)continue;const r=n.get(t.key)||[];r.push(t.result),n.set(t.key,r)}return n}export function validateMeta(e){if(!e||"object"!=typeof e||Array.isArray(e))throw new WorkflowUsageError("meta必须是一个对象,至少包含name与description。");const t=String(e.name||"").trim(),r=String(e.description||"").trim();if(!t)throw new WorkflowUsageError("meta.name不能为空(用于进度显示与运行记录)。");if(!r)throw new WorkflowUsageError("meta.description不能为空(一句话说明这个工作流做什么)。");const n=[];if(void 0!==e.phases){if(!Array.isArray(e.phases))throw new WorkflowUsageError("meta.phases必须是数组。");for(const t of e.phases){if(!t||"object"!=typeof t||!String(t.title||"").trim())throw new WorkflowUsageError("meta.phases的每一项都必须是 { title, detail? } 形式且title非空。");n.push({title:String(t.title).trim(),detail:String(t.detail||"")})}}return{name:t,description:r,phases:n}}export function schemaShapeError(e){if(!e||"object"!=typeof e||Array.isArray(e))return"agent() 的schema必须是一个JSON Schema对象。";if("string"==typeof e.type||e.properties||e.items||Array.isArray(e.enum)||e.oneOf||e.anyOf||e.allOf||e.$ref)return"";return`agent() 的schema不是合法的JSON Schema(收到的键:${Object.keys(e).slice(0,5).join("、")||"空对象"})。正确形状形如 { type: "object", properties: { port: { type: "integer" } }, required: ["port"] }。写成 { port: "number" } 这类简写不会报错,但会让结构化校验彻底失效——子智能体报回什么都算通过。`}export function validateAgainstSchema(e,t,r="结果"){const n=[];if(!t||"object"!=typeof t)return n;const o=t.type,a=e=>Array.isArray(e)?"array":null===e?"null":typeof e;if(o&&a(e)!==o&&("integer"!==o||!Number.isInteger(e)))return n.push(`${r}应为${o},实际为${a(e)}`),n;Array.isArray(t.enum)&&!t.enum.includes(e)&&n.push(`${r} 应为 ${JSON.stringify(t.enum)} 之一`);if("object"===o||!o&&(t.properties||t.required)){if(null===e||"object"!=typeof e||Array.isArray(e))return n.push(`${r}应为object,实际为${a(e)}`),n;for(const o of t.required||[])void 0===e[o]&&n.push(`${r} 缺少必填字段${o}`);for(const[o,a]of Object.entries(t.properties||{}))void 0!==e[o]&&n.push(...validateAgainstSchema(e[o],a,`${r}.${o}`))}else"array"===o&&t.items&&e.forEach((e,o)=>n.push(...validateAgainstSchema(e,t.items,`${r}[${o}]`)));return n}export function runWorkflow({script:e,meta:t,args:o,resumeFrom:i,dispatchAgent:l,onEvent:c,signal:p}={}){const u="wf-"+Date.now().toString(36)+"-"+r.randomBytes(3).toString("hex"),m=loadResume(i),g=new Map,d=s(u,{script:e,meta:t,args:o});try{pruneRuns()}catch{}return new Promise(t=>{let s=null,i=!1,h=0,f=0,y=0,k=0,w=0;const S=[],v=[],_=Date.now(),b=e=>{try{c?.(e)}catch{}},j=e=>{if(i)return;i=!0,clearInterval(x),clearTimeout($),p?.removeEventListener?.("abort",D);try{s?.terminate()}catch{}const r={ok:e.ok,kind:e.kind||(e.ok?"done":"error"),agents:h,cached:f,phases:S.slice(),elapsedMs:Date.now()-_,error:e.error||""};d.finish(r),t({...e,runId:u,dir:d.dir,agents:h,cached:f,phases:S.slice(),elapsedMs:r.elapsedMs})},x=setInterval(()=>{Date.now()-_-w-(y>0?Date.now()-k:0)>2e4&&j({ok:!1,kind:"compute-timeout",logs:v.slice(),value:null,error:`脚本自身运算超过 ${Math.round(20)} 秒(不含等待子智能体)。工作流脚本只该做编排,重活交给agent()。`})},250),$=setTimeout(()=>{j({ok:!1,kind:"wall-timeout",logs:v.slice(),value:null,error:`工作流总耗时超过 ${Math.round(60)} 分钟,已中止。`})},a),D=()=>j({ok:!1,kind:"abort",logs:v.slice(),value:null,error:"已中断"});if(p){if(p.aborted)return D();p.addEventListener("abort",D,{once:!0})}const A=(e,t)=>{if(!i)try{s.postMessage({reply:e,...t})}catch{}},M=e=>j({ok:!1,kind:"usage",logs:v.slice(),value:null,error:e}),R=async(e,t)=>{const n="string"==typeof t?.prompt?t.prompt.trim():"";if(!n)return M("agent() 的第一个参数必须是非空字符串(子任务目标)。");const o=new Set(["prompt","label","phase","schema","context","agentType"]),a=Object.keys(t).filter(e=>!o.has(e));if(a.length)return M(`agent() 收到不认识的选项:${a.join("、")}。可用选项为label / phase / schema / context / agentType。`);if(void 0!==t.schema){const e=schemaShapeError(t.schema);if(e)return M(e)}if(h>=200)return M("本次工作流的子智能体数量已达上限 200,已中止。请缩小扇出规模或分几次跑。");const s=function(e){const t=r.createHash("sha256");return t.update(String(e.prompt||"")),t.update("\0"+String(e.label||"")),t.update("\0"+String(e.agentType||"")),t.update("\0"+JSON.stringify(e.schema||null)),t.digest("hex").slice(0,32)}(t),i=m.get(s),c=g.get(s)||0;if(i&&c<i.length)return g.set(s,c+1),h++,f++,d.append({type:"agent",key:s,ok:!0,cached:!0,phase:t.phase||"",label:t.label||"",result:i[c]}),b({type:"agent-end",cached:!0,label:t.label||n.slice(0,40)}),A(e,{ok:!0,result:i[c]});h++;const p=String(t.label||n.split("\n")[0]).slice(0,60);0===y&&(k=Date.now()),y++,b({type:"agent-start",label:p,phase:t.phase||""});const u=Date.now();let S;try{S=await l({...t,prompt:n,label:p})}catch(e){S={ok:!1,error:e.message}}if(y--,0===y&&(w+=Date.now()-k),d.append({type:"agent",key:s,ok:!!S.ok,phase:t.phase||"",label:p,ms:Date.now()-u,result:S.ok?S.result:void 0,error:S.ok?void 0:S.error||""}),b({type:"agent-end",label:p,ok:!!S.ok,ms:Date.now()-u}),!S.ok&&S.usage)return M(S.error||"agent() 用法错误");A(e,{ok:!0,result:S.ok?S.result:null})};try{s=new n("\nconst { parentPort, workerData } = require('node:worker_threads');\nconst vm = require('node:vm');\n\nconst pending = new Map();\nlet nextId = 0;\nfunction host(kind, payload) {\n return new Promise((resolve, reject) => {\n const id = ++nextId;\n pending.set(id, { resolve, reject });\n parentPort.postMessage({ call: id, kind, payload });\n });\n}\nparentPort.on('message', (m) => {\n if (!m || typeof m.reply !== 'number') return;\n const p = pending.get(m.reply);\n if (!p) return; // 未知/重复回包:忽略\n pending.delete(m.reply);\n if (m.ok) p.resolve(m.result);\n else p.reject(new Error(m.error || '调用失败'));\n});\n// 注意:这里只处理「子任务失败」这一种拒绝。钩子误用不会走到这里——\n// 宿主根本不回包,而是直接终止整个worker。理由见主线程handleAgent的注释。\n\n// 全新realm:这里根本不存在require / process / fs / setTimeout —— 「只能协调」由此成立\nconst ctx = vm.createContext({ __host: host, __args: workerData.args, __logs: [] });\n\n// 在**上下文内部**构造钩子:这样agent.constructor指向的是本realm的Function,\n// 而不是宿主realm的——顺手关掉最直白的那条摸回宿主的路。\nvm.runInContext(`\n const __h = __host;\n // 时钟与随机数:脚本不确定就无法复现,续跑也就无从谈起\n const __deny = (what) => () => { throw new Error('工作流脚本里禁用 ' + what + ':脚本必须可复现,否则断点续跑时提示词对不上、缓存永远命不中。需要时间戳/随机数请由调用方经args传入。'); };\n Date.now = __deny('Date.now()');\n Math.random = __deny('Math.random()');\n const __RealDate = Date;\n Date = new Proxy(__RealDate, {\n construct(T, a) { if (!a.length) __deny('new Date()')(); return Reflect.construct(T, a); },\n });\n\n var args = __args;\n var agent = (prompt, opts) => __h('agent', { prompt, ...(opts || {}) });\n var phase = (title) => { __h('phase', { title }); };\n var log = (message) => { __logs.push(String(message)); __h('log', { message: String(message) }); };\n\n // pipeline:每个条目独立穿过所有阶段,**阶段之间没有栅栏**。\n // 这是默认首选:加了栅栏,快的条目就得白等慢的,整条流水的耗时从「最慢的那条链」\n // 劣化成「每一阶段最慢者之和」。某一条目在某阶段抛错 → 该条目变null并跳过其余阶段,\n // 不影响别的条目;但钩子误用(__fatal)必须整体炸掉。\n var pipeline = (items, ...stages) => Promise.all((items || []).map(async (item, i) => {\n let cur = item;\n for (const st of stages) {\n try { cur = await st(cur, item, i); } catch { return null; }\n }\n return cur;\n }));\n\n // parallel:栅栏。仅当确实需要「全部结果一起看」(去重、汇总、早退判断)时才用。\n var parallel = (thunks) => Promise.all((thunks || []).map(async (t) => {\n try { return await t(); } catch { return null; }\n }));\n`, ctx);\n\n(async () => {\n let fn;\n try {\n fn = vm.runInContext('(async function () {' + workerData.code + '\\n})', ctx, { filename: 'workflow-script.js' });\n } catch (e) {\n parentPort.postMessage({ done: true, error: '脚本语法错误:' + e.message, syntax: true });\n return;\n }\n try {\n const value = await fn();\n parentPort.postMessage({ done: true, value: value === undefined ? null : value, logs: ctx.__logs });\n } catch (e) {\n parentPort.postMessage({ done: true, error: (e && e.message) ? e.message : String(e), logs: ctx.__logs });\n }\n})();\n",{eval:!0,workerData:{code:String(e||""),args:void 0===o?null:o},env:{},resourceLimits:{maxOldGenerationSizeMb:256},stdout:!0,stderr:!0})}catch(e){return j({ok:!1,kind:"exception",logs:[],value:null,error:"无法启动工作流运行时:"+e.message})}s.on("message",e=>{if(!i&&e&&"object"==typeof e){if(e.done)return e.error?j({ok:!1,kind:e.syntax?"syntax":"exception",logs:v.slice(),value:null,error:e.error}):j({ok:!0,logs:v.slice(),value:e.value??null});if("number"==typeof e.call)if("agent"!==e.kind){if("phase"===e.kind){const t=String(e.payload?.title||"").trim();return t&&!S.includes(t)&&S.push(t),t&&(d.append({type:"phase",title:t}),b({type:"phase",title:t})),A(e.call,{ok:!0,result:null})}if("log"===e.kind){const t=String(e.payload?.message||"");return v.push(t),d.append({type:"log",message:t}),b({type:"log",message:t}),A(e.call,{ok:!0,result:null})}M(`未知的脚本钩子:${e.kind}`)}else R(e.call,e.payload&&"object"==typeof e.payload?e.payload:{})}}),s.on("error",e=>j({ok:!1,kind:"exception",logs:v.slice(),value:null,error:e.message})),s.on("exit",e=>{i||j({ok:!1,kind:"worker-exit",logs:v.slice(),value:null,error:`工作流运行时意外退出(code=${e}),可能是内存超限。`})})})}export function makeWorkflowTool({config:e,cwd:t,extraTools:r=[],depth:n=0,onSub:o=null}={}){return{readOnly:!0,timeoutMs:0,spec:{type:"function",function:{name:"workflow",description:'交出一段JavaScript脚本来编排**子智能体**:由脚本决定扇出多少、怎么串成流水、什么时候收敛。适合固定形状表达不了的拓扑——扇出→各自多阶段流水→跨条目去重→对抗式验证→综合,或「一直找到连续两轮没有新发现为止」这类收敛循环。\n脚本里可用:\n· agent(prompt, { label?, phase?, schema?, context?, agentType? }) —— 起一个子智能体并拿回结论。只有这五个选项,传别的会**当场终止整个工作流**。schema必须是完整JSON Schema (形如 { type: "object", properties: { port: { type: "integer" } }, required: ["port"] }),写成 { port: "number" } 这种简写会让校验彻底失效。子任务失败时agent() 返回null(不抛异常),用 .filter(Boolean) 过滤即可;pipeline(items, ...stages) 让每个条目独立穿过所有阶段(阶段之间无栅栏,默认首选);parallel(thunks) 并发并等全部完成(栅栏,仅当确实要一起看全部结果时用);phase(title) / log(msg) 报告进度;args是你传入的输入。\n脚本只能协调、不能干活:没有文件系统、网络、定时器,也禁用Date.now与Math.random(脚本必须可复现,否则断点续跑时对不上)。要读写文件或跑命令,交给agent() 里的子智能体,或改用run_program。\n每次运行都会留下记录,中断后可用resume_from续跑:已完成的agent() 直接复用,不重跑。',parameters:{type:"object",properties:{script:{type:"string",description:"脚本体(异步函数体;可用顶层await与return)"},meta:{type:"object",description:"身份块(纯JSON,不从脚本里解析):{ name, description, phases?: [{title, detail?}] }",properties:{name:{type:"string"},description:{type:"string"},phases:{type:"array",items:{type:"object"}}},required:["name","description"]},args:{description:"可选:传给脚本的输入,脚本里以args逐字可见"},resume_from:{type:"string",description:"可选:上次运行的runId,复用其已完成的agent() 结果续跑"}},required:["script","meta"]}}},runWithEnv:async(a,s,i)=>{let l;try{l=validateMeta(a?.meta)}catch(e){return`【工作流未启动】${e.message}`}const c=String(a?.script||"").trim();if(!c)return"【工作流未启动】script不能为空。";const{makeWorkflowDispatcher:p}=await import("./subagent.js"),u=p({config:e,cwd:t,extraTools:r,depth:n,onSub:o}),m=s?.info||(()=>{});m(`工作流「${l.name}」开始:${l.description}`);return formatResult(await runWorkflow({script:c,meta:l,args:a?.args,resumeFrom:a?.resume_from,dispatchAgent:u,signal:i,onEvent:e=>{"phase"===e.type?m(`工作流·阶段:${e.title}`):"log"===e.type?m(`工作流:${e.message}`):"agent-end"===e.type&&e.cached&&m(`工作流·复用上次结果:${e.label}`)}}),l)}}}export function formatResult(e,t){const r=Math.round((e.elapsedMs||0)/1e3),n=`【工作流${e.ok?"完成":"失败"}】${t?.name||""} 子智能体 ${e.agents} 个${e.cached?`(其中 ${e.cached} 个复用上次结果)`:""} 耗时 ${r} 秒 runId=${e.runId}`;if(!e.ok){const t={usage:"这是**脚本用法错误**,不是某个子任务失败——修正脚本后重跑。",syntax:"脚本没能通过语法解析,一个子智能体都没起。","compute-timeout":"脚本自身算得太久:工作流脚本只该做编排。","wall-timeout":"整体超时:缩小扇出规模,或分几次跑。",abort:"已被用户中断。"}[e.kind]||"",r="usage"===e.kind||"syntax"===e.kind?"":`\n已完成的部分可续跑:再次调用时传resume_from="${e.runId}",已跑完的子智能体不会重跑。`;return`${n}\n${e.error}${t?"\n"+t:""}${r}`}const o=[n];return e.phases?.length&&o.push("阶段:"+e.phases.join(" → ")),e.logs?.length&&o.push("过程:\n"+e.logs.join("\n")),null!==e.value&&void 0!==e.value?o.push("结果:\n"+("string"==typeof e.value?e.value:JSON.stringify(e.value,null,2))):e.logs?.length||o.push("(脚本没有return也没有log任何内容——结果不会自动回传,记得把要的东西return出来。)"),o.join("\n")}
|
package/src/worklog.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"node:fs";import e from"node:os";import n from"node:path";import{execFile as s}from"node:child_process";import{promisify as o}from"node:util";import{classifyCommit as i}from"./archaeology.js";const r=o(s);export const GIT_CONCURRENCY=Math.max(2,Math.min(8,e.cpus().length));export async function mapLimit(t,e,n){const s=new Array(t.length);let o=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(e,t.length))},async()=>{for(;;){const e=o++;if(e>=t.length)return;s[e]=await n(t[e],e)}})),s}const c=[["test",/\b(test|spec|e2e)\b|测试|用例|压测|自测/i],["docs",/\b(docs?|readme|changelog|comment)\b|文档|注释|说明书|readme/i],["perf",/\b(perf|optimiz|improve|enhance|speed[\s-]?up)\b|优化|完善|增强|提速|性能|调优/i],["chore",/\b(chore|build|deps|dependency|bump|release|ci)\b|同步|升级|依赖|版本号|构建|打包|配置调整/i],["style",/\b(style|format|lint|prettier)\b|格式化|排版调整|代码风格/i],["feat",/迁入|迁移|接入|对接|打通|开发完成|新建|搭建/]];export function commitLead(t){return(e=t,String(e||"").replace(/[\w.-]*\/[\w./-]*/g," ")).split(/[::]/)[0].slice(0,50);var e}function a(t){const e=i(t);if("other"!==e)return e;const n=commitLead(t);for(const[t,e]of c)if(e.test(n))return t;return"other"}const u=new Set(["node_modules",".git",".svn",".hg","dist","build","out","target","bin","obj","__pycache__",".venv","venv","env",".tox",".mypy_cache",".pytest_cache",".ruff_cache",".gradle",".m2",".cache",".next",".nuxt",".turbo",".parcel-cache","coverage","vendor","Pods","Carthage","DerivedData",".idea",".vscode-test","Library","Applications","System",".Trash",".npm",".pnpm-store",".docker"]),l=/(^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock|poetry\.lock|Cargo\.lock|composer\.lock|Gemfile\.lock|go\.sum)$|(^|\/)(dist|build|out|target|vendor|node_modules|__generated__|generated)\/|\.min\.(js|css)$|\.(map|lock)$/i,p=[["文档",/\.(docx?|wps|md|markdown|txt|rtf|odt|ofd|pdf|tex)$/i],["表格",/\.(xlsx?|xlsm|csv|et|ods|numbers)$/i],["演示",/\.(pptx?|dps|odp|key)$/i],["图表",/\.(drawio|vsdx?|xmind|mmd|puml)$/i]];export function ymd(t){const e=t=>String(t).padStart(2,"0");return`${t.getFullYear()}-${e(t.getMonth()+1)}-${e(t.getDate())}`}function h(t){const e=String(t).trim().match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);if(!e)throw new Error("日期格式应为YYYY-MM-DD:"+t);const[n,s,o]=[Number(e[1]),Number(e[2]),Number(e[3])],i=new Date(n,s-1,o);if(i.getFullYear()!==n||i.getMonth()!==s-1||i.getDate()!==o)throw new Error(`日期不存在:${t}(不要靠JS的自动进位,请给真实存在的日期)`);return i}function f(t,e){const n=new Date(t.getTime());return n.setDate(n.getDate()+e),n}export function parseRange({since:t,until:e,days:n,week:s}={}){const o=h(ymd(new Date));if(t){const n=h(t),s=e?h(e):o;if(s<n)throw new Error(`区间反了:${ymd(n)} 晚于 ${ymd(s)}`);return{since:n,until:s,label:`${ymd(n)} ~ ${ymd(s)}`,source:"显式区间"}}if("this"===s||"last"===s){const t=(o.getDay()+6)%7,e=f(o,-t-("last"===s?7:0)),n=f(e,6);return{since:e,until:n,label:`${ymd(e)} ~ ${ymd(n)}`,source:"this"===s?"本自然周(周一至周日)":"上一自然周(周一至周日)"}}const i=Number(n),r=Math.min(120,Number.isFinite(i)&&i>0?Math.round(i):7),c=f(o,-(r-1));return{since:c,until:o,label:`${ymd(c)} ~ ${ymd(o)}`,source:`最近 ${r} 天(含今天)`}}export function cnRange(t){const e=t=>`${t.getFullYear()}年${String(t.getMonth()+1).padStart(2,"0")}月${String(t.getDate()).padStart(2,"0")}日`;return`${e(t.since)}-${e(t.until)}`}export function expandPath(t,s=process.cwd()){let o=String(t||"").trim();return o?("~"===o?o=e.homedir():(o.startsWith("~/")||o.startsWith("~\\"))&&(o=n.join(e.homedir(),o.slice(2))),n.resolve(s,o)):""}export function discoverRepos(e,{maxDepth:s=4}={}){const o=[],i=[];let r=0;const c=(e,i)=>{if(o.length>=300||r>=6e4)return;if(r++,function(e){try{return t.existsSync(n.join(e,".git"))}catch{return!1}}(e))return void o.push(e);if(i>=s)return;let a=[];try{a=t.readdirSync(e,{withFileTypes:!0})}catch{return}for(const t of a)(t.isDirectory()||t.isSymbolicLink())&&(t.name.startsWith(".")&&"."!==t.name||u.has(t.name)||t.isSymbolicLink()||c(n.join(e,t.name),i+1))};for(const n of e)t.existsSync(n)?c(n,0):i.push(n);const a=new Map;for(const e of o){let n=e;try{n=t.realpathSync(e)}catch{}a.has(n)||a.set(n,e)}return{repos:[...a.values()].sort(),missingRoots:i,truncated:o.length>=300}}async function d(t,e,n){try{return await async function(t,e,{maxBuffer:n=67108864}={}){const{stdout:s}=await r("git",e,{cwd:t,encoding:"utf8",maxBuffer:n,windowsHide:!0});return s}(t,e,n)}catch{return""}}export async function buildIdentities({configured:t=[],repos:e=[]}={}){const n=new Set;for(const e of t){const t=String(e||"").trim().toLowerCase();t&&n.add(t)}const s=await mapLimit(e,GIT_CONCURRENCY,t=>d(t,["config","--get-regexp","^user\\.(name|email)$"]));for(const t of s)for(const e of String(t).split("\n")){const t=e.indexOf(" ");if(t<0)continue;const s=e.slice(t+1).trim().toLowerCase();s&&n.add(s)}return n}const m=(t,e,n)=>t.has(String(e||"").trim().toLowerCase())||t.has(String(n||"").trim().toLowerCase());export function normalizePath(t){let e=function(t){if(!(t.startsWith('"')&&t.endsWith('"')&&t.length>=2))return t;const e=t.slice(1,-1),n=[];for(let t=0;t<e.length;t++){if("\\"!==e[t]){n.push(e.charCodeAt(t));continue}const s=e.slice(t+1,t+4);if(/^[0-7]{3}$/.test(s)){n.push(parseInt(s,8)),t+=3;continue}const o={n:10,t:9,r:13,'"':34,"\\":92},i=e[t+1];i in o?(n.push(o[i]),t+=1):n.push(e.charCodeAt(t))}try{return Buffer.from(n).toString("utf8")}catch{return t}}(String(t||"").trim());e=e.replace(/\{([^{}]*) => ([^{}]*)\}/g,"$2");const n=e.indexOf(" => ");return n>=0&&(e=e.slice(n+4)),e.replace(/\/{2,}/g,"/").trim()}export async function collectRepoCommits(t,{since:e,until:s,identities:o}){const i=ymd(f(e,-90)),r=await d(t,["-c","core.quotepath=false","log","--all","--no-merges",`--since=${i}`,"-n4000","--date=format:%Y-%m-%d %H:%M","--pretty=format:%x00%H%x1f%h%x1f%ad%x1f%cd%x1f%an%x1f%ae%x1f%s","--numstat"]),c=ymd(e),u=ymd(s),p=[],h=[],g=new Map;for(const t of r.split("\0")){if(!t.trim())continue;const e=t.indexOf("\n"),n=(e<0?t:t.slice(0,e)).split("");if(n.length<7)continue;const[s,i,r,f,d,$,y]=n,k=r.slice(0,10),b=f.slice(0,10);if(!m(o,d,$)){if(k>=c&&k<=u){const t=`${d} <${$}>`;g.set(t,(g.get(t)||0)+1)}continue}const x=k>=c&&k<=u;if(!x&&!(!x&&k<c&&b>=c&&b<=u))continue;let w=0,S=0,j=0,D=0,v=0;const _=[];if(e>=0)for(const n of t.slice(e+1).split("\n")){if(!n.trim())continue;const t=n.split("\t");if(t.length<3)continue;const e=normalizePath(t.slice(2).join("\t"));if(!e)continue;if(_.push(e),"-"===t[0]||"-"===t[1]){j++;continue}const s=Number(t[0])||0,o=Number(t[1])||0;w+=s,S+=o,l.test(e)&&(D+=s,v+=o)}const I={hash:i,date:k,time:r,commitDate:b,subject:y,kind:a(y),ins:w,del:S,binary:j,noiseIns:D,noiseDel:v,files:_,bulk:w+S>=2e3||_.length>=40};(x?p:h).push(I)}return p.sort((t,e)=>t.time<e.time?1:-1),h.sort((t,e)=>t.time<e.time?1:-1),{dir:t,name:n.basename(t),branch:(await d(t,["rev-parse","--abbrev-ref","HEAD"])).trim(),commits:p,landed:h,otherAuthors:[...g.entries()].sort((t,e)=>e[1]-t[1])}}export function summarize(t){const e={n:t.length,ins:0,del:0,binary:0,noiseIns:0,noiseDel:0,bulk:[],kinds:{},files:new Set,dirs:new Map};for(const n of t){e.ins+=n.ins,e.del+=n.del,e.binary+=n.binary,e.noiseIns+=n.noiseIns,e.noiseDel+=n.noiseDel,e.kinds[n.kind]=(e.kinds[n.kind]||0)+1,n.bulk&&e.bulk.push(n);for(const t of n.files){e.files.add(t);const n=t.includes("/")?t.slice(0,t.indexOf("/"))+"/":"(根目录)";e.dirs.set(n,(e.dirs.get(n)||0)+1)}}e.netIns=e.ins-e.noiseIns,e.netDel=e.del-e.noiseDel;const n=new Set(e.bulk);e.exBulkIns=0,e.exBulkDel=0,e.exBulkN=0;for(const s of t)n.has(s)||(e.exBulkN++,e.exBulkIns+=s.ins-s.noiseIns,e.exBulkDel+=s.del-s.noiseDel);return e}const g={feat:"新功能",fix:"修复",refactor:"重构",revert:"回滚",hotfix:"紧急修复",merge:"合并",perf:"优化",chore:"同步/构建",docs:"文档",test:"测试",style:"格式",other:"其它"};function $(t,e){return[...t.entries()].sort((t,e)=>e[1]-t[1]).slice(0,e)}export function formatGitReport(t,e,n){const s=[];s.push(`【本机Git工作记录】${t.label}(${t.source},按**作者提交时间**切窗)`),s.push(`身份:${n.identities.length?n.identities.join("、"):"(未识别到任何身份)"}`),s.push(`扫描根目录:${n.roots.join("、")||"(无)"}`);const o=e.filter(t=>t.commits.length||t.landed.length);if(s.push(`发现仓库 ${n.repoCount} 个,其中 ${o.length} 个在本周期内有你的提交`),n.missingRoots.length&&s.push(`⚠ 这些根目录不存在,已跳过:${n.missingRoots.join("、")}`),n.truncated&&s.push("⚠ 仓库数达到上限 300,可能没扫全——请把roots收窄到真正的项目目录"),s.push(""),!o.length){s.push("本周期内没有找到属于你的提交。三种可能,请逐一排除:"),s.push(" ① 根目录没设对——现在扫的是上面那几个路径;"),s.push(" ② 身份没配全——下面「其它作者」里如果有你自己的马甲,把它加进identities重跑;"),s.push(" ③ 确实没提交(活干在别处,用work_scan或tapd_fetch看)。");const t=new Map;for(const n of e)for(const[e,s]of n.otherAuthors)t.set(e,(t.get(e)||0)+s);return t.size&&s.push(" 本周期内出现过的其它作者:"+$(t,12).map(([t,e])=>`${t}×${e}`).join("、")),s.join("\n")}let i=0,r=0,c=0,a=0,u=0,l=0;const p={};for(const t of o){const e=summarize(t.commits);i+=e.n,r+=e.netIns,c+=e.netDel,a+=e.exBulkIns,u+=e.exBulkDel,l+=e.bulk.length;for(const[t,n]of Object.entries(e.kinds))p[t]=(p[t]||0)+n;if(s.push(`■ ${t.name}${t.branch?`(当前分支 ${t.branch})`:""} ${t.dir}`),e.n){s.push(` 本周期 ${e.n} 次提交 · 净 +${e.netIns}/-${e.netDel} 行 · 触及 ${e.files.size} 个文件`+(e.binary?` · 二进制文件 ${e.binary} 个(行数无从统计)`:"")),e.bulk.length&&s.push(` 其中 ${e.bulk.length} 次是超大提交;**剔除后**只剩 ${e.exBulkN} 次提交 · 净 +${e.exBulkIns}/-${e.exBulkDel} 行——两个口径都在这儿,写进周报的那个要说得清是怎么算的`),e.noiseIns+e.noiseDel>0&&s.push(` (另有 +${e.noiseIns}/-${e.noiseDel} 行来自锁文件/构建产物/生成代码,已从上面的净额里剔除——若本周期的工作本身就是升级依赖,这部分要自己加回去说明)`),s.push(" 类型分布:"+Object.entries(e.kinds).sort((t,e)=>e[1]-t[1]).map(([t,e])=>`${g[t]||t} ${e}`).join(" · ")),s.push(" 主要触及:"+$(e.dirs,6).map(([t,e])=>`${t}(${e})`).join("、"));for(const e of t.commits)s.push(` ${e.date} ${e.hash} [${g[e.kind]||e.kind}] ${e.subject} +${e.ins}/-${e.del},${e.files.length} 文件${e.bulk?" ⚠超大":""}`);e.bulk.length&&(s.push(` ⚠ ${e.bulk.length} 次超大提交(≥2000 行或 ≥40 文件):`+e.bulk.map(t=>`${t.hash}「${t.subject}」`).join("、")),s.push(" 超大提交常是格式化、代码生成或副本同步。写进周报前先确认它代表真实工作量,否则「本周产出N行」会在第一次追问时塌台。"))}t.landed.length&&(s.push(` ↳ 另有 ${t.landed.length} 次提交是**更早写的、本周期才合入/整理**(rebase/cherry-pick):`+t.landed.slice(0,8).map(t=>`${t.hash}「${t.subject}」(写于 ${t.date})`).join("、")),s.push(" 这些算不算本周工作由你判断:合入与冲突处理是本周做的,代码不是。")),s.push("")}s.push(`【合计】${o.length} 个仓库 · ${i} 次提交 · 净 +${r}/-${c} 行 · `+Object.entries(p).sort((t,e)=>e[1]-t[1]).map(([t,e])=>`${g[t]||t} ${e}`).join(" · ")),l&&s.push(` 剔除 ${l} 次超大提交后:${i-l} 次提交 · 净 +${a}/-${u} 行。两个数差得越远,越说明本周期的行数不代表手写工作量——这种时候周报里干脆别写行数,写交付了什么。`);const h=new Map;for(const t of e)for(const[e,n]of t.otherAuthors)h.set(e,(h.get(e)||0)+n);return h.size&&(s.push(`【同期其它作者】${$(h,12).map(([t,e])=>`${t}×${e}`).join("、")}`),s.push(" —— 若其中有你自己的另一套name/email,加进identities重跑,否则这部分工作量会一直漏。")),s.push(""),s.push("【写周报时的口径提醒】提交次数与行数是**过程量**,不是成果。周报要写的是「这些提交合起来交付了什么、达到了什么指标」——把提交主题按项目/功能归并成 3~5 件事,每件事给出可验证的结果,不要罗列commit。"),s.join("\n")}export async function trackedFiles(t){const e=new Set,s=await mapLimit(t,GIT_CONCURRENCY,t=>async function(t){const e=new Set,s=await d(t,["-c","core.quotepath=false","ls-files","-z"],{maxBuffer:134217728});if(!s)return e;for(const o of s.split("\0"))o&&e.add(n.join(t,o));return e}(t));for(const t of s)for(const n of t)e.add(n);return e}function y(t){for(const[e,n]of p)if(n.test(t))return e;return""}export function scanDocs(e,{since:s,until:o,maxDepth:i=5,limit:r=400,tracked:c=null}={}){const a=s.getTime(),l=f(o,1).getTime(),p=[];let h=0,d=!1,m=0;const g=(e,s)=>{if(p.length>=r||h>=6e4)return void(d=!0);h++;let o=[];try{o=t.readdirSync(e,{withFileTypes:!0})}catch{return}for(const h of o){if(p.length>=r)return void(d=!0);const o=n.join(e,h.name);if(h.isSymbolicLink())continue;if(h.isDirectory()){if(u.has(h.name)||h.name.startsWith("."))continue;s<i&&g(o,s+1);continue}if(!h.isFile())continue;if(h.name.startsWith("~$")||h.name.startsWith("."))continue;const f=y(h.name);if(!f)continue;let $;try{$=t.statSync(o)}catch{continue}const k=$.mtimeMs;k<a||k>=l||(c&&c.has(o)?m++:p.push({path:o,kind:f,mtime:new Date(k),size:$.size}))}};for(const n of e)t.existsSync(n)&&g(n,0);return p.sort((t,e)=>e.mtime-t.mtime),{hits:p,truncated:d,skippedTracked:m}}export function formatDocReport(t,e,{hits:s,truncated:o,skippedTracked:i=0},{repoDirs:r=[]}={}){const c=[];if(c.push(`【文档与其它工作痕迹】${t.label}(${t.source})`),c.push("口径:按文件**最后修改时间**筛出的文档/表格/演示稿。"),c.push("⚠ 这是**弱证据**——拷贝、解压、网盘同步、打开后另存都会刷新修改时间,「文件改过」不等于「这是你的工作成果」。逐条自己认一下再写进周报。"),c.push(`扫描根目录:${e.join("、")||"(无)"}`),i&&c.push(`已排除 ${i} 个被git跟踪的文件——那些走git_worklog(有作者有行数,证据强得多);这里只留版本库之外的东西:方案稿、评审材料、台账、汇报稿。`),!s.length)return c.push("本周期内没有找到版本库之外的文档改动。"+(i?"(仓库内的文档改动请看git_worklog。)":"")),c.join("\n");const a=t=>r.some(e=>t===e||t.startsWith(e+n.sep)),u=new Map;for(const t of s){const s=r.find(e=>t.path.startsWith(e+n.sep)),o=e.find(e=>t.path.startsWith(e+n.sep))||n.dirname(t.path),i=s||(()=>{const e=n.relative(o,t.path),s=e.split(n.sep)[0];return e.includes(n.sep)?n.join(o,s):o})();u.has(i)||u.set(i,[]),u.get(i).push(t)}c.push(`共 ${s.length} 个文件${o?"(已达上限,可能没列全)":""},分布在 ${u.size} 处:`),c.push("");for(const[t,e]of[...u.entries()].sort((t,e)=>e[1].length-t[1].length)){const s=e.reduce((t,e)=>(t[e.kind]=(t[e.kind]||0)+1,t),{});c.push(`■ ${t}${a(t)?"(git仓库内——正文改动请以git_worklog为准,这里只看文档件)":""}`),c.push(" "+Object.entries(s).map(([t,e])=>`${t} ${e}`).join(" · "));for(const s of e.slice(0,20)){const e=n.relative(t,s.path)||n.basename(s.path);c.push(` ${ymd(s.mtime)} ${String(s.mtime.getHours()).padStart(2,"0")}:${String(s.mtime.getMinutes()).padStart(2,"0")} [${s.kind}] ${e} ${(s.size/1024).toFixed(0)}KB`)}e.length>20&&c.push(` …… 另有 ${e.length-20} 个未列出`),c.push("")}return c.push("【怎么用】文档名往往就是工作事项本身(《X方案》《Y评审纪要》)。把它们与git提交、TAPD需求对照,能补上「写方案、做评审、对外汇报」这类不留代码痕迹的工作。"),c.join("\n")}export const WEEKLY_SETTINGS_FILE="weekly.json";function k(e){try{const n=JSON.parse(t.readFileSync(e,"utf8"));return n&&"object"==typeof n?n:{}}catch{return{}}}export function weeklySettingsPaths(t=process.cwd()){return[n.join(t,".huicai","weekly.json"),n.join(e.homedir(),".huicai","weekly.json")]}export function weeklySettings(t=process.cwd()){const[e,n]=weeklySettingsPaths(t),s={...k(n),...k(e)},o={...k(n).tapd||{},...k(e).tapd||{}};return Object.keys(o).length&&(s.tapd=o),s}export function weeklyDefaults(t=process.cwd()){const e=weeklySettings(t),s=String(process.env.HUICAI_WEEKLY_ROOTS||"").split(n.delimiter).filter(Boolean);return{roots:(s.length?s:Array.isArray(e.roots)?e.roots:[]).map(e=>expandPath(e,t)).filter(Boolean),identities:Array.isArray(e.identities)?e.identities.map(String):[],reporter:String(e.reporter||"").trim()}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"node:fs";import t from"node:path";import{BUNDLED_SKILLS_DIR as n}from"./skills.js";import{ensureRuntime as r,execFileCapture as i,runtimeStatus as s,venvPython as o}from"./pyruntime.js";export function engineDir(){return t.join(n,"xlsx-analysis","scripts")}const a="report业务报表(表头深色/斑马纹/冻结/筛选/千分位/打印一页宽,默认)/ plain素表(无底纹,便于再加工与二次透视)/ finance财务报表(会计格式、负数红括号、小计加粗上框线)";async function p(n,a,p,{timeoutMs:c=24e4,config:l={}}={}){s(l).usable||await r({log:()=>{},config:l});const u=o();if(!e.existsSync(u))throw new Error("托管Python未就绪(设置 → 智能 → 立即准备,或先调用一次run_python)");const f=await i(u,["-X","utf8",t.join(engineDir(),a),...p],{cwd:n,timeoutMs:c,env:{PYTHONIOENCODING:"utf-8",MPLBACKEND:"Agg"}});if(f.killed)throw new Error("执行超时被终止");return f.output||""}export function makeXlsxTools({cwd:n,config:r={}}){return[{readOnly:!1,spec:{type:"function",function:{name:"create_sheet",description:`生成Excel报表(.xlsx):经营报表、对账表、台账、预算表、明细汇总等。**你只给「哪张表放什么」,列宽与格式由引擎算**——不要用run_python手写openpyxl摆格式。\n引擎替你做对这几件最容易翻车的事:① **合计行一律写SUBTOTAL公式而不是算好的常量**——常量合计在你改明细后不会更新,而且不报错,这是Excel交付件最经典的事故;② 列宽按内容量(数字列不够宽会显示成 ####,打印出来一片井号);③ 编号/身份证类列强制文本格式,防前导零消失与科学计数法;④ 分组小计与总计都用SUBTOTAL(9,…),总计不会把小计再加一遍;⑤ 冻结首行 + 自动筛选 + 打印缩放到一页宽 + 跨页重复标题行。\n排版档位profile:${a}。\n出片后**自动跑表格体检**(数据/结构/公式/勾稽/呈现),报告随结果返回。`,parameters:{type:"object",properties:{path:{type:"string",description:"输出路径(相对工作目录,如 2026年8月经营报表.xlsx)"},spec:{type:"object",description:"整本工作簿的结构",additionalProperties:!0,properties:{profile:{type:"string",description:"report(默认)/ plain / finance,见工具说明"},theme:{type:"string",description:"配色navy(默认)/ green / mono / warm"},cover:{type:"object",additionalProperties:!0,description:"可选说明页(排在第一张):{title, fields:[[键,值],…], date}。正式交付的报表建议带上口径与数据来源,省掉一轮来回问"},sheets:{type:"array",description:"逐张工作表。每张 {name, columns, rows|records, ...}:name表名;title表格上方标题行;note口径说明(小字);columns [{key,header,format,width,align,wrap}],format取 amount金额千分位两位 / amount0 整数金额 / accounting会计负数红括号 / int整数 / percent百分比 / date日期 / month年月 / text强制文本(编号必须用它) / general;rows [[…]] 按列顺序给值,或records [{key:值}];total {label,cols} 合计行(写成SUBTOTAL公式);group_by按某列分组出小计;chart {type:bar|line|pie, cat, values[], title};conditional [{col,type:data_bar|color_scale|gt|lt,value,color}];validation [{col,list[]}] 下拉选项;freeze/autofilter布尔;print {landscape,fit_width,repeat_head,page}。",items:{type:"object",additionalProperties:!0}}},required:["sheets"]},spec_path:{type:"string",description:"可选:从JSON文件读spec(数据量大时用,省上下文)"}},required:["path"]}}},run:async i=>{let s=i?.spec;if(!s&&i?.spec_path&&(s=JSON.parse(e.readFileSync(t.resolve(n,String(i.spec_path)),"utf8"))),!s)return"create_sheet需要spec(或spec_path):至少给sheets一张表。";const o=function(n,r,i){const s=String(r||i||"").trim()||i,o=t.resolve(n,s);if(o!==n&&!o.startsWith(n+t.sep))throw new Error("输出路径越出工作目录:"+s);return e.mkdirSync(t.dirname(o),{recursive:!0}),o}(n,i?.path,"报表.xlsx"),a=t.join(t.dirname(o),`.sheet-spec-${Date.now()}.json`);try{e.writeFileSync(a,JSON.stringify(s),"utf8");const i=function(e){const t=String(e||"").match(/\{[\s\S]*\}\s*$/);if(!t)throw new Error("引擎输出异常:"+String(e||"").slice(-400));return JSON.parse(t[0])}(await p(n,"xlsx_build.py",[a,o],{config:r}));if(i.error)return`生成失败:${i.error}\n(字段见create_sheet参数说明;columns与rows的列顺序必须对齐)`;const c=t.relative(n,i.path)||t.basename(i.path),l=i.check?.stats,u=[`已生成 ${c}`+(l?`(${l.sheets} 表:${(l.sheet_names||[]).join("、")} / 共 ${l.rows} 行 / ${l.formulas} 个公式)`:"")];return i.warnings?.length&&u.push("【建表提示】\n"+i.warnings.map(e=>" · "+e).join("\n")),u.push(i.report||""),i.check?.counts?.error&&u.push("必修项未清零:按上面的提示改spec重新生成,或用xlsx_inspect(path, fix=true) 修可确定性修的那几类。"),u.filter(Boolean).join("\n")}finally{try{e.unlinkSync(a)}catch{}}}},{readOnly:!1,spec:{type:"function",function:{name:"xlsx_inspect",description:"对**任意** Excel工作簿做体检,逐条带位置与具体数字。生成表格后**必须**跑一遍(create_sheet会自动跑;自己写openpyxl/pandas导出的更要跑);拿到别人给的表要分析之前,也**先跑一遍再动手算**——读进来的东西不是你以为的东西,是数据分析出错误答案的头号原因。\n查六类:① 数据——数值被存成文本(SUM会得 0)、一列里数字与文本混杂、日期存成文本、编号列被存成数字(前导零消失/超 15 位丢精度)、首尾空白与不可见字符、完全重复行;② 结构——数据区内的合并单元格(排序报错、筛选漏行、透视读不到)、隐藏行列、「合计」行混在明细中(一汇总就翻倍)、重名列、空列名、隐藏工作表、默认表名;③ 公式——错误值 #REF!/#DIV/0! 等、一列里公式与手打常量混杂、**求和范围没覆盖到数据末行**、引用了不存在的工作表、外部工作簿链接、易失函数过多;④ 勾稽——**合计行是写死的常量而不是公式**、合计对不上明细之和、占比列合计不等于 100%;⑤ 呈现——列宽不足会显示 ####、同列数字格式不统一、金额列缺千分位、缺冻结窗格与筛选、打印会横跨多页、长表缺重复标题行;⑥ 元信息——隐藏表、默认表名。\nprofile=true再叠一层**数据体检**:推断每列真实类型、找唯一标识列、重复行、同物异名(「北京」与「北京 」会被当成两个值)、缺失率、极端值、断月。\n也接受 .csv:只跑数据体检那一层。\nfix=true:自动修可确定性修的(文本转数字、编号列转文本、清首尾空白、拆数据区合并并填充、**写死的合计改成SUBTOTAL公式**、补齐求和范围、列宽自适应、同列格式归一、金额补千分位、冻结首行与筛选、打印缩放一页宽与重复标题行),**另存「-已校正.xlsx」不覆盖原件**并回报复检结果;救不回来的会如实说(如编号已超 15 位精度丢失,只能让对方按文本重新导出)——不假装修好了。",parameters:{type:"object",properties:{path:{type:"string",description:"要体检的 .xlsx(或 .csv)路径"},fix:{type:"boolean",description:"是否顺手自动修复(默认false,只体检)"},inplace:{type:"boolean",description:"fix时直接改原文件(默认false,另存 -已校正.xlsx)"},profile:{type:"boolean",description:"是否叠加数据体检(类型/主键/重复/同物异名),分析前建议开"},sheet:{type:"string",description:"只体检某一张表(数据体检层生效)"},layout:{type:"string",description:`按哪档规范比对呈现:auto(默认)/ ${a}`}},required:["path"]}}},run:async i=>{const s=t.resolve(n,String(i?.path||""));if(!e.existsSync(s))return`文件不存在:${i?.path}`;const o=/\.csv$/i.test(s);if(!o&&!/\.xlsx|\.xlsm$/i.test(s))return"只支持 .xlsx/.xlsm(老的 .xls请先另存为xlsx;.xls可用run_python + pandas直接读),或 .csv";const a=[];if(!o){const e=[s];i?.fix&&e.push("--fix"),i?.inplace&&e.push("--inplace"),i?.layout&&"auto"!==i.layout&&e.push(`--profile=${i.layout}`),a.push((await p(n,"xlsx_check.py",e,{config:r})).trim())}if(o||i?.profile){const e=[s];i?.sheet&&e.push(`--sheet=${i.sheet}`),a.push((await p(n,"data_profile.py",e,{config:r})).trim())}return a.filter(Boolean).join("\n\n")||"(体检无输出,请检查托管Python是否就绪)"}}]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/* Shared compact command UI for Electron, VS Code and JetBrains webviews. */
|
|
2
|
+
(function (global) {
|
|
3
|
+
const style = `.hc-command-picker{--cp-accent:var(--wt-accent,var(--vscode-focusBorder,var(--hc-accent,#528bff)));--cp-bg:var(--wt-panel,var(--vscode-editor-background,var(--hc-bg,#20242b)));color:var(--wt-text,var(--vscode-foreground,var(--hc-fg,#ddd)));background:var(--cp-bg);border:1px solid var(--cp-accent);border-radius:10px;padding:10px;margin:6px 0;max-width:720px;min-width:0;font:inherit;box-sizing:border-box;box-shadow:0 4px 16px #0002}.hc-command-picker[hidden]{display:none}.hc-cp-head{display:flex;align-items:center;gap:8px;font-weight:600;margin-bottom:8px}.hc-cp-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.hc-cp-count,.hc-cp-hint,.hc-cp-detail{font-size:12px;opacity:.7}.hc-cp-close{border:0!important;background:transparent!important;color:inherit!important;cursor:pointer;padding:2px 6px!important}.hc-cp-search{box-sizing:border-box;width:100%;border:1px solid #8885;border-radius:6px;padding:6px 8px;background:transparent;color:inherit;font:inherit;margin-bottom:6px}.hc-cp-options{display:flex;flex-direction:column;gap:3px;min-width:0}.hc-cp-options.horizontal{flex-direction:row;overflow-x:auto;padding:2px;scrollbar-width:thin}.hc-cp-option{display:block;width:100%;text-align:left;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border:1px solid transparent;border-radius:6px;padding:7px 9px;background:transparent;color:inherit;font:inherit;cursor:pointer;min-height:32px}.hc-cp-option.active{border-color:var(--cp-accent);background:color-mix(in srgb,var(--cp-accent) 16%,transparent);font-weight:600}.hc-cp-options.horizontal .hc-cp-option{width:auto;flex:0 0 auto;text-align:center}.hc-cp-detail{margin:8px 0;line-height:1.5;max-height:3em;overflow:hidden}.hc-cp-hint{border-top:1px solid #8883;padding-top:7px}.hc-command-picker:focus{outline:none}`;
|
|
4
|
+
function match(commands, query) {
|
|
5
|
+
const q = query.toLowerCase().trim();
|
|
6
|
+
const rank = (c) => c.name === q ? 5 : (c.aliases || []).includes(q) ? 4 : c.name.startsWith(q) ? 3 : (c.aliases || []).some((a) => a.startsWith(q)) ? 2 : 1;
|
|
7
|
+
return commands.filter((c) => !q || [c.name, ...(c.aliases || []), c.desc, c.category].join(' ').toLowerCase().includes(q)).sort((a,b) => rank(b)-rank(a));
|
|
8
|
+
}
|
|
9
|
+
function mount(root, reply, restoreFocus) {
|
|
10
|
+
const doc = root.ownerDocument;
|
|
11
|
+
if (!doc.getElementById('hc-command-picker-style')) {
|
|
12
|
+
const css = doc.createElement('style'); css.id = 'hc-command-picker-style'; css.textContent = style; doc.head.append(css);
|
|
13
|
+
}
|
|
14
|
+
root.classList.add('hc-command-picker'); root.hidden = true; root.tabIndex = -1;
|
|
15
|
+
root.setAttribute('aria-label', 'HuiCai AI 命令选项');
|
|
16
|
+
let request = null, query = '', index = 0;
|
|
17
|
+
const el = (tag, className, text) => { const node = doc.createElement(tag); node.className = className; if (text != null) node.textContent = text; return node; };
|
|
18
|
+
const options = () => !request ? [] : request.menu.layout === 'segments' ? request.menu.options : request.menu.options.filter((o) => [o.value,o.label,o.description].join(' ').toLowerCase().includes(query.toLowerCase()));
|
|
19
|
+
function close() { request = null; root.hidden = true; root.replaceChildren(); restoreFocus?.(); }
|
|
20
|
+
function answer(value) { if (!request) return; const id = request.id; close(); reply({ type:'commandMenuResponse', id, value }); }
|
|
21
|
+
function render(focusSearch = false) {
|
|
22
|
+
if (!request) return;
|
|
23
|
+
const menu = request.menu, horizontal = menu.layout === 'segments', items = options();
|
|
24
|
+
index = Math.max(0, Math.min(index, items.length - 1));
|
|
25
|
+
const head = el('div','hc-cp-head');
|
|
26
|
+
head.append(el('span','hc-cp-title',menu.title),el('span','hc-cp-count',`${items.length ? index + 1 : 0}/${items.length}`));
|
|
27
|
+
const cancel = el('button','hc-cp-close','×'); cancel.type = 'button'; cancel.title = '取消 (Esc)'; cancel.onclick = () => answer(null); head.append(cancel);
|
|
28
|
+
root.replaceChildren(head);
|
|
29
|
+
let search;
|
|
30
|
+
if (!horizontal) {
|
|
31
|
+
search = el('input','hc-cp-search'); search.placeholder = '搜索选项…'; search.setAttribute('aria-label','搜索选项'); search.value = query;
|
|
32
|
+
search.oninput = () => { query = search.value; index = 0; render(true); }; root.append(search);
|
|
33
|
+
}
|
|
34
|
+
const list = el('div','hc-cp-options' + (horizontal ? ' horizontal' : '')); list.setAttribute('role','listbox');
|
|
35
|
+
const start = horizontal ? 0 : Math.max(0,Math.min(index - 1,items.length - 4));
|
|
36
|
+
const shown = horizontal ? items : items.slice(start,start+4);
|
|
37
|
+
shown.forEach((o,i) => {
|
|
38
|
+
const selected = i + start === index;
|
|
39
|
+
const button = el('button','hc-cp-option' + (selected ? ' active' : ''),(selected ? '› ' : '') + o.label + (o.value === menu.current ? ' ✓ 当前' : ''));
|
|
40
|
+
button.type = 'button'; button.setAttribute('role','option'); button.setAttribute('aria-selected',String(selected)); button.title = o.label;
|
|
41
|
+
button.onfocus = () => {
|
|
42
|
+
index = i + start;
|
|
43
|
+
list.querySelectorAll('.hc-cp-option').forEach((b,j) => {
|
|
44
|
+
const focused = j + start === index, item = items[j + start];
|
|
45
|
+
b.classList.toggle('active',focused); b.setAttribute('aria-selected',String(focused));
|
|
46
|
+
b.textContent = (focused ? '› ' : '') + item.label + (item.value === menu.current ? ' ✓ 当前' : '');
|
|
47
|
+
});
|
|
48
|
+
head.querySelector('.hc-cp-count').textContent = `${index+1}/${items.length}`;
|
|
49
|
+
const detail = root.querySelector('.hc-cp-detail'); if (detail) detail.textContent = o.description || '确认后生效,取消保持原设置。';
|
|
50
|
+
};
|
|
51
|
+
button.onclick = () => answer(o.value); list.append(button);
|
|
52
|
+
});
|
|
53
|
+
if (!items.length) list.append(el('span','hc-cp-detail','没有匹配项'));
|
|
54
|
+
root.append(list,el('div','hc-cp-detail',items[index]?.description || '确认后生效,取消保持原设置。'),el('div','hc-cp-hint',horizontal ? '←→ 选择 · Enter 确认 · Esc 取消' : `${start ? '↑ 上方还有选项 · ' : ''}${start+4<items.length ? '↓ 下方还有选项 · ' : ''}↑↓ 选择 · Enter 确认 · Esc 取消`));
|
|
55
|
+
if (horizontal) { root.focus(); const active = list.querySelector('.active'); if (active) list.scrollLeft = Math.max(0, active.offsetLeft - list.offsetLeft - list.clientWidth / 2); }
|
|
56
|
+
else if (focusSearch) { search.focus(); search.setSelectionRange(query.length,query.length); }
|
|
57
|
+
}
|
|
58
|
+
function key(e) {
|
|
59
|
+
if (!request || e.isComposing) return;
|
|
60
|
+
const horizontal = request.menu.layout === 'segments';
|
|
61
|
+
const delta = e.key === 'ArrowUp' || (horizontal && e.key === 'ArrowLeft') ? -1 : e.key === 'ArrowDown' || (horizontal && e.key === 'ArrowRight') ? 1 : e.key === 'PageUp' ? -4 : e.key === 'PageDown' ? 4 : 0;
|
|
62
|
+
if (delta || e.key === 'Escape' || e.key === 'Enter') {
|
|
63
|
+
e.preventDefault(); e.stopPropagation();
|
|
64
|
+
if (e.key === 'Escape' || (e.key === 'Enter' && e.target.classList.contains('hc-cp-close'))) answer(null);
|
|
65
|
+
else if (e.key === 'Enter') { const o = options()[index]; if (o) answer(o.value); }
|
|
66
|
+
else { index = Math.max(0,Math.min(options().length-1,index+delta)); render(!horizontal); }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
root.addEventListener('keydown',key);
|
|
70
|
+
return {
|
|
71
|
+
set(message) {
|
|
72
|
+
if (!message?.menu) { if (!message?.id || request?.id === message.id) close(); return; }
|
|
73
|
+
request = message; query = ''; index = Math.max(0, message.menu.options.findIndex((o) => o.value === message.menu.current)); root.hidden = false; render(true);
|
|
74
|
+
},
|
|
75
|
+
destroy() { root.removeEventListener('keydown',key); request = null; root.replaceChildren(); root.hidden = true; },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
global.HuiCaiCommandPicker = { mount, match };
|
|
79
|
+
})(globalThis);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|