dsh-crwu-workbench 0.0.10
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/CHANGELOG.md +1516 -0
- package/LICENSE +26 -0
- package/README.en.md +204 -0
- package/README.md +521 -0
- package/SECURITY.md +39 -0
- package/bin/darwin-arm64/crwu +0 -0
- package/bin/darwin-arm64/dws +0 -0
- package/bin/darwin-arm64/ossutil +0 -0
- package/bin/manifest.json +82 -0
- package/bin/win32-x64/crwu.exe +0 -0
- package/bin/win32-x64/dws.exe +4 -0
- package/bin/win32-x64/ossutil.exe +0 -0
- package/common/skills/.synced.json +8 -0
- package/common/skills/crwu-dws/SKILL.md +208 -0
- package/common/skills/crwu-dws/references/00-/347/233/256/345/275/225/345/277/253/347/205/247schema.md +113 -0
- package/common/skills/crwu-dws/references/01-/345/256/241/346/240/270/344/270/213/350/275/275/344/270/216manifest/350/247/204/350/214/203.md +115 -0
- package/common/skills/crwu-dws/references/02-/347/274/223/345/255/230/344/270/216/345/205/234/345/272/225/346/237/245/346/211/276/350/247/204/350/214/203.md +119 -0
- package/common/skills/crwu-dws/scripts/test_dws_source_contract.py +493 -0
- package/common/skills/crwu-h3yun-login/SKILL.md +130 -0
- package/common/skills/crwu-h3yun-query/SKILL.md +192 -0
- package/config/crwu-workbench.yml +26 -0
- package/cordis.patch.yml +46 -0
- package/lib/client.js +10346 -0
- package/lib/index.js +11584 -0
- package/package.json +119 -0
- package/scripts/prepare.mjs +59 -0
- package/skills/README.md +26 -0
- package/skills/crwu/crwu-audit/SKILL.md +152 -0
- package/skills/crwu/crwu-audit/references/00-input-and-route-profile.md +232 -0
- package/skills/crwu/crwu-audit/references/01-report-id-resolution.md +13 -0
- package/skills/crwu/crwu-audit/references/02-scope-classification.md +25 -0
- package/skills/crwu/crwu-audit/references/03-asset-classification.md +27 -0
- package/skills/crwu/crwu-audit/references/04-business-classification.md +63 -0
- package/skills/crwu/crwu-audit/references/05-method-classification.md +66 -0
- package/skills/crwu/crwu-audit/references/06-overlay-classification.md +42 -0
- package/skills/crwu/crwu-audit/references/07-skill-registry.md +57 -0
- package/skills/crwu/crwu-audit/references/08-union-dispatch-rules.md +142 -0
- package/skills/crwu/crwu-audit/references/09-review-risk-classification.md +85 -0
- package/skills/crwu/crwu-audit/references/10-capability-gap-proposal.md +68 -0
- package/skills/crwu/crwu-audit/references/11-html-delivery-spec.md +881 -0
- package/skills/crwu/crwu-audit/references/12-leaf-common-contract.md +202 -0
- package/skills/crwu/crwu-audit/references/13-dingtalk-result-publish.md +88 -0
- package/skills/crwu/crwu-audit/references/14-orchestration-workflow.md +52 -0
- package/skills/crwu/crwu-audit/references/99-maintenance.md +62 -0
- package/skills/crwu/crwu-audit/scripts/README.md +157 -0
- package/skills/crwu/crwu-audit/scripts/audit_delivery.py +2736 -0
- package/skills/crwu/crwu-audit/scripts/audit_result.schema.json +1659 -0
- package/skills/crwu/crwu-audit/scripts/examples/audit-result.sample.json +739 -0
- package/skills/crwu/crwu-audit/scripts/fetch_review_records.py +85 -0
- package/skills/crwu/crwu-audit/scripts/media_extract.py +513 -0
- package/skills/crwu/crwu-audit/scripts/prepare_materials.py +1252 -0
- package/skills/crwu/crwu-audit/scripts/test_audit_delivery.py +1678 -0
- package/skills/crwu/crwu-audit/scripts/test_audit_multiaxis_router.py +1339 -0
- package/skills/crwu/crwu-audit/scripts/test_fetch_review_records.py +77 -0
- package/skills/crwu/crwu-audit/scripts/test_media_extract.py +332 -0
- package/skills/crwu/crwu-audit/scripts/test_prepare_materials.py +1081 -0
- package/skills/crwu/crwu-audit/template/audit-report.html +435 -0
- package/skills/crwu/crwu-audit-asset-asset-group-goodwill/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/00-applicability.md +28 -0
- package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/01-kb-assembly.md +31 -0
- package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/02-review-focus.md +21 -0
- package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-debt/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-debt/references/00-applicability.md +40 -0
- package/skills/crwu/crwu-audit-asset-debt/references/01-kb-assembly.md +41 -0
- package/skills/crwu/crwu-audit-asset-debt/references/02-review-focus.md +54 -0
- package/skills/crwu/crwu-audit-asset-debt/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-enterprise-value/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-enterprise-value/references/00-applicability.md +31 -0
- package/skills/crwu/crwu-audit-asset-enterprise-value/references/01-kb-assembly.md +35 -0
- package/skills/crwu/crwu-audit-asset-enterprise-value/references/02-review-focus.md +33 -0
- package/skills/crwu/crwu-audit-asset-enterprise-value/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-equipment/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-equipment/references/00-applicability.md +40 -0
- package/skills/crwu/crwu-audit-asset-equipment/references/01-kb-assembly.md +39 -0
- package/skills/crwu/crwu-audit-asset-equipment/references/02-review-focus.md +52 -0
- package/skills/crwu/crwu-audit-asset-equipment/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-intangible/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-intangible/references/00-applicability.md +36 -0
- package/skills/crwu/crwu-audit-asset-intangible/references/01-kb-assembly.md +40 -0
- package/skills/crwu/crwu-audit-asset-intangible/references/02-review-focus.md +68 -0
- package/skills/crwu/crwu-audit-asset-intangible/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-inventory/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-inventory/references/00-applicability.md +28 -0
- package/skills/crwu/crwu-audit-asset-inventory/references/01-kb-assembly.md +31 -0
- package/skills/crwu/crwu-audit-asset-inventory/references/02-review-focus.md +21 -0
- package/skills/crwu/crwu-audit-asset-inventory/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-mining-right/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-mining-right/references/00-applicability.md +31 -0
- package/skills/crwu/crwu-audit-asset-mining-right/references/01-kb-assembly.md +35 -0
- package/skills/crwu/crwu-audit-asset-mining-right/references/02-review-focus.md +33 -0
- package/skills/crwu/crwu-audit-asset-mining-right/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-other/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-other/references/00-applicability.md +28 -0
- package/skills/crwu/crwu-audit-asset-other/references/01-kb-assembly.md +31 -0
- package/skills/crwu/crwu-audit-asset-other/references/02-review-focus.md +21 -0
- package/skills/crwu/crwu-audit-asset-other/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-portfolio/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-portfolio/references/00-applicability.md +28 -0
- package/skills/crwu/crwu-audit-asset-portfolio/references/01-kb-assembly.md +31 -0
- package/skills/crwu/crwu-audit-asset-portfolio/references/02-review-focus.md +21 -0
- package/skills/crwu/crwu-audit-asset-portfolio/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-realestate/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-realestate/references/00-applicability.md +43 -0
- package/skills/crwu/crwu-audit-asset-realestate/references/01-kb-assembly.md +75 -0
- package/skills/crwu/crwu-audit-asset-realestate/references/02-review-focus.md +93 -0
- package/skills/crwu/crwu-audit-asset-realestate/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-scrap-materials/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-scrap-materials/references/00-applicability.md +28 -0
- package/skills/crwu/crwu-audit-asset-scrap-materials/references/01-kb-assembly.md +31 -0
- package/skills/crwu/crwu-audit-asset-scrap-materials/references/02-review-focus.md +21 -0
- package/skills/crwu/crwu-audit-asset-scrap-materials/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-asset-transport-equipment/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-asset-transport-equipment/references/00-applicability.md +31 -0
- package/skills/crwu/crwu-audit-asset-transport-equipment/references/01-kb-assembly.md +35 -0
- package/skills/crwu/crwu-audit-asset-transport-equipment/references/02-review-focus.md +33 -0
- package/skills/crwu/crwu-audit-asset-transport-equipment/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-asset-operation/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-asset-operation/references/00-applicability.md +30 -0
- package/skills/crwu/crwu-audit-biz-asset-operation/references/01-kb-assembly.md +32 -0
- package/skills/crwu/crwu-audit-biz-asset-operation/references/02-review-focus.md +42 -0
- package/skills/crwu/crwu-audit-biz-asset-operation/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-consulting-review/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-consulting-review/references/00-applicability.md +30 -0
- package/skills/crwu/crwu-audit-biz-consulting-review/references/01-kb-assembly.md +32 -0
- package/skills/crwu/crwu-audit-biz-consulting-review/references/02-review-focus.md +40 -0
- package/skills/crwu/crwu-audit-biz-consulting-review/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-financial-reporting/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-financial-reporting/references/00-applicability.md +41 -0
- package/skills/crwu/crwu-audit-biz-financial-reporting/references/01-kb-assembly.md +40 -0
- package/skills/crwu/crwu-audit-biz-financial-reporting/references/02-review-focus.md +64 -0
- package/skills/crwu/crwu-audit-biz-financial-reporting/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-financing-debt/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-financing-debt/references/00-applicability.md +31 -0
- package/skills/crwu/crwu-audit-biz-financing-debt/references/01-kb-assembly.md +34 -0
- package/skills/crwu/crwu-audit-biz-financing-debt/references/02-review-focus.md +47 -0
- package/skills/crwu/crwu-audit-biz-financing-debt/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-investment-capital/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-investment-capital/references/00-applicability.md +31 -0
- package/skills/crwu/crwu-audit-biz-investment-capital/references/01-kb-assembly.md +34 -0
- package/skills/crwu/crwu-audit-biz-investment-capital/references/02-review-focus.md +47 -0
- package/skills/crwu/crwu-audit-biz-investment-capital/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/00-applicability.md +30 -0
- package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/01-kb-assembly.md +32 -0
- package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/02-review-focus.md +40 -0
- package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-tax-history/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-tax-history/references/00-applicability.md +29 -0
- package/skills/crwu/crwu-audit-biz-tax-history/references/01-kb-assembly.md +30 -0
- package/skills/crwu/crwu-audit-biz-tax-history/references/02-review-focus.md +33 -0
- package/skills/crwu/crwu-audit-biz-tax-history/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-biz-transaction-disposal/SKILL.md +38 -0
- package/skills/crwu/crwu-audit-biz-transaction-disposal/references/00-applicability.md +31 -0
- package/skills/crwu/crwu-audit-biz-transaction-disposal/references/01-kb-assembly.md +34 -0
- package/skills/crwu/crwu-audit-biz-transaction-disposal/references/02-review-focus.md +47 -0
- package/skills/crwu/crwu-audit-biz-transaction-disposal/references/03-common-contract.md +202 -0
- package/skills/crwu/crwu-audit-datacheck/SKILL.md +59 -0
- package/skills/crwu/crwu-audit-datacheck/references/00-KB/350/243/205/351/205/215/350/241/250.md +25 -0
- package/skills/crwu/crwu-audit-datacheck/references/01-runtime-and-check-catalog.md +139 -0
- package/skills/crwu/crwu-audit-datacheck/scripts/recalc_check.py +521 -0
- package/skills/crwu/crwu-audit-datacheck/scripts/test_recalc_check.py +419 -0
- package/skills/crwu/crwu-audit-datacheck/scripts/test_skill_self_containment.py +44 -0
- package/skills/crwu/crwu-audit-external-data/SKILL.md +73 -0
- package/skills/crwu/crwu-audit-external-data/references/00-KB/350/243/205/351/205/215/350/241/250.md +38 -0
- package/skills/crwu/crwu-audit-external-data/references/01-connector-access.md +116 -0
- package/skills/crwu/crwu-audit-external-data/references/02-verification-workflow.md +78 -0
- package/skills/crwu/crwu-audit-external-data/scripts/connector_probe.py +374 -0
- package/skills/crwu/crwu-audit-external-data/scripts/test_connector_probe.py +264 -0
- package/skills/crwu/crwu-audit-output-filter/SKILL.md +34 -0
- package/skills/crwu/crwu-audit-output-filter/references/00-applicability.md +7 -0
- package/skills/crwu/crwu-audit-output-filter/references/01-kb-assembly.md +11 -0
- package/skills/crwu/crwu-audit-output-filter/references/02-review-focus.md +25 -0
- package/skills/crwu/crwu-audit-public-general-standards/SKILL.md +46 -0
- package/skills/crwu/crwu-audit-public-general-standards/references/00-applicability.md +41 -0
- package/skills/crwu/crwu-audit-public-general-standards/references/01-kb-assembly.md +66 -0
- package/skills/crwu/crwu-audit-public-general-standards/references/02-review-focus.md +111 -0
- package/skills/crwu/crwu-audit-public-general-standards/references/03-execution-contract.md +89 -0
- package/skills/crwu/crwu-dev-audit-optimize/SKILL.md +98 -0
- package/skills/crwu/crwu-dev-audit-optimize/references/00-/344/274/230/345/214/226/350/247/204/350/214/203/344/270/216/346/226/207/344/273/266/350/220/275/347/202/271.md +132 -0
- package/skills/crwu/crwu-dev-audit-optimize/references/01-/345/217/215/351/246/210/345/256/232/344/275/215/344/270/216/347/224/273/345/203/217/346/265/201/347/250/213.md +108 -0
- package/skills/crwu/crwu-dev-audit-optimize/references/02-/346/226/271/346/241/210/346/250/241/346/235/277/344/270/216/347/241/256/350/256/244/351/227/250/347/246/201.md +92 -0
- package/skills/crwu/crwu-dev-audit-optimize/references/03-AI/344/272/272/345/267/245/345/267/256/350/267/235/345/210/206/346/236/220/346/265/201/347/250/213.md +95 -0
- package/skills/crwu/crwu-dev-audit-optimize/scripts/examples/gap-analysis.sample.json +116 -0
- package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis.schema.json +33 -0
- package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis_delivery.py +475 -0
- package/skills/crwu/crwu-dev-audit-optimize/scripts/test_gap_analysis_delivery.py +855 -0
- package/skills/crwu/crwu-dev-audit-optimize/template/NOTICE.echarts.txt +7 -0
- package/skills/crwu/crwu-dev-audit-optimize/template/echarts.min.js +45 -0
- package/skills/crwu/crwu-dev-audit-optimize/template/gap-analysis-report.html +71 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/SKILL.md +74 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/00-responsibility-and-modes.md +62 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/01-kb-source-discovery.md +89 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/02-child-skill-contract.md +99 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/03-review-item-execution-contract.md +51 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/04-registry-and-mapping-update.md +73 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/05-validation-and-delivery.md +127 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/06-live-routing-reconciliation.md +136 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/references/07-kb-skill-map.md +411 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/README.md +200 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/check_audit_skill_mappings.py +2477 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/kb_tool.py +577 -0
- package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/test_audit_skill_maintainer.py +5097 -0
- package/skills/dws/LICENSE +178 -0
- package/skills/dws/NOTICE +17 -0
- package/skills/dws/dingtalk-aisearch/SKILL.md +83 -0
- package/skills/dws/dingtalk-aisearch/references/aisearch.md +249 -0
- package/skills/dws/dingtalk-aisearch/references/intent-guide.md +15 -0
- package/skills/dws/dingtalk-aisearch/references/lite-recipes.md +31 -0
- package/skills/dws/dingtalk-aitable/SKILL.md +139 -0
- package/skills/dws/dingtalk-aitable/references/06-data-analytics.md +11 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-advperm.md +251 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-attachment.md +49 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-best-practices.md +48 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-cell-value.md +346 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +46 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-data-analysis-sop.md +131 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-datasource.md +272 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-error-recovery.md +128 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-export-import.md +113 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-field-properties.md +238 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-field.md +186 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-filter-sort.md +135 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-form.md +130 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-formula-guide.md +224 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-primary-doc.md +58 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-create.md +52 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-delete.md +20 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-history.md +97 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-name-key.md +47 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-query.md +120 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-share.md +54 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-stats.md +59 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-update.md +66 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-upsert.md +89 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-config.md +140 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-extras.md +195 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-types.md +95 -0
- package/skills/dws/dingtalk-aitable/references/aitable/aitable-workflow.md +384 -0
- package/skills/dws/dingtalk-aitable/references/aitable-record-ops.md +80 -0
- package/skills/dws/dingtalk-aitable/references/aitable-section.md +30 -0
- package/skills/dws/dingtalk-aitable/references/aitable.md +59 -0
- package/skills/dws/dingtalk-aitable/references/field-rules.md +17 -0
- package/skills/dws/dingtalk-aitable/references/intent-guide.md +17 -0
- package/skills/dws/dingtalk-aitable/references/recipes/conventions.md +44 -0
- package/skills/dws/dingtalk-aitable/references/url-patterns.md +144 -0
- package/skills/dws/dingtalk-aitable/scripts/aitable_export_via_task.py +210 -0
- package/skills/dws/dingtalk-aitable/scripts/aitable_import_via_task.py +173 -0
- package/skills/dws/dingtalk-aitable/scripts/bulk_add_fields.py +273 -0
- package/skills/dws/dingtalk-aitable/scripts/import_records.py +333 -0
- package/skills/dws/dingtalk-aitable/scripts/upload_attachment.py +190 -0
- package/skills/dws/dingtalk-calendar/SKILL.md +137 -0
- package/skills/dws/dingtalk-calendar/references/03-meeting.md +56 -0
- package/skills/dws/dingtalk-calendar/references/calendar.md +760 -0
- package/skills/dws/dingtalk-calendar/references/intent-guide.md +9 -0
- package/skills/dws/dingtalk-calendar/references/lite-recipes.md +25 -0
- package/skills/dws/dingtalk-calendar/scripts/calendar_free_slot_finder.py +199 -0
- package/skills/dws/dingtalk-calendar/scripts/calendar_schedule_meeting.py +233 -0
- package/skills/dws/dingtalk-calendar/scripts/calendar_today_agenda.py +139 -0
- package/skills/dws/dingtalk-chat/SKILL.md +127 -0
- package/skills/dws/dingtalk-chat/references/01-messaging.md +103 -0
- package/skills/dws/dingtalk-chat/references/card/callback.md +10 -0
- package/skills/dws/dingtalk-chat/references/card/create.md +27 -0
- package/skills/dws/dingtalk-chat/references/card/schema.md +14 -0
- package/skills/dws/dingtalk-chat/references/card/update.md +13 -0
- package/skills/dws/dingtalk-chat/references/chat/chat-bot.md +187 -0
- package/skills/dws/dingtalk-chat/references/chat/chat-conversation.md +165 -0
- package/skills/dws/dingtalk-chat/references/chat/group-admin.md +144 -0
- package/skills/dws/dingtalk-chat/references/chat/group-discovery.md +121 -0
- package/skills/dws/dingtalk-chat/references/chat/message-actions.md +132 -0
- package/skills/dws/dingtalk-chat/references/chat/message-media.md +76 -0
- package/skills/dws/dingtalk-chat/references/chat/message-query.md +135 -0
- package/skills/dws/dingtalk-chat/references/chat/thread.md +46 -0
- package/skills/dws/dingtalk-chat/references/chat-emoji-list.md +216 -0
- package/skills/dws/dingtalk-chat/references/chat.md +140 -0
- package/skills/dws/dingtalk-chat/references/contracts.md +70 -0
- package/skills/dws/dingtalk-chat/references/intent-guide.md +56 -0
- package/skills/dws/dingtalk-contact/SKILL.md +122 -0
- package/skills/dws/dingtalk-contact/references/08-directory.md +51 -0
- package/skills/dws/dingtalk-contact/references/contact.md +595 -0
- package/skills/dws/dingtalk-contact/references/intent-guide.md +14 -0
- package/skills/dws/dingtalk-contact/references/lite-recipes.md +30 -0
- package/skills/dws/dingtalk-contact/references/recipes/conventions.md +44 -0
- package/skills/dws/dingtalk-contact/scripts/contact_dept_members.py +130 -0
- package/skills/dws/dingtalk-doc/SKILL.md +112 -0
- package/skills/dws/dingtalk-doc/references/04-document.md +57 -0
- package/skills/dws/dingtalk-doc/references/contracts.md +40 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-block.md +39 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-comment.md +29 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-create.md +54 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-export.md +30 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-import.md +28 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-info.md +12 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-media.md +46 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-read.md +56 -0
- package/skills/dws/dingtalk-doc/references/doc/doc-update.md +87 -0
- package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-cookbook.md +469 -0
- package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.json +526 -0
- package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.md +711 -0
- package/skills/dws/dingtalk-doc/references/doc/style/doc-create-workflow.md +414 -0
- package/skills/dws/dingtalk-doc/references/doc/style/doc-style-guideline.md +404 -0
- package/skills/dws/dingtalk-doc/references/doc/style/doc-update-workflow.md +316 -0
- package/skills/dws/dingtalk-doc/references/doc.md +82 -0
- package/skills/dws/dingtalk-doc/references/intent-guide.md +20 -0
- package/skills/dws/dingtalk-drive/SKILL.md +128 -0
- package/skills/dws/dingtalk-drive/references/drive.md +126 -0
- package/skills/dws/dingtalk-drive/references/folder-sync.md +40 -0
- package/skills/dws/dingtalk-drive/references/intent-guide.md +22 -0
- package/skills/dws/dingtalk-drive/references/lite-recipes.md +39 -0
- package/skills/dws/dingtalk-event/SKILL.md +106 -0
- package/skills/dws/dingtalk-event/references/event-im-keys.md +54 -0
- package/skills/dws/dingtalk-event/references/event-im-lifecycle.md +35 -0
- package/skills/dws/dingtalk-event/references/event-im-operations.md +52 -0
- package/skills/dws/dingtalk-event/references/event-im-output.md +39 -0
- package/skills/dws/dingtalk-event/references/event-im.md +58 -0
- package/skills/dws/dingtalk-event/references/event-oa.md +155 -0
- package/skills/dws/dingtalk-event/references/event-todo.md +50 -0
- package/skills/dws/dingtalk-event/references/event-voip.md +69 -0
- package/skills/dws/dingtalk-mail/SKILL.md +113 -0
- package/skills/dws/dingtalk-mail/references/09-mail.md +45 -0
- package/skills/dws/dingtalk-mail/references/intent-guide.md +8 -0
- package/skills/dws/dingtalk-mail/references/lite-recipes.md +45 -0
- package/skills/dws/dingtalk-mail/references/mail.md +1889 -0
- package/skills/dws/dingtalk-mail/references/recipes/conventions.md +44 -0
- package/skills/dws/dingtalk-mail/scripts/mail_send_with_cc.py +122 -0
- package/skills/dws/dingtalk-mail/scripts/mail_unread_summary.py +114 -0
- package/skills/dws/dingtalk-minutes/SKILL.md +116 -0
- package/skills/dws/dingtalk-minutes/references/07-minutes.md +149 -0
- package/skills/dws/dingtalk-minutes/references/10-minutes-speaker-match.md +32 -0
- package/skills/dws/dingtalk-minutes/references/11-minutes-speaker-correct.md +40 -0
- package/skills/dws/dingtalk-minutes/references/intent-guide.md +79 -0
- package/skills/dws/dingtalk-minutes/references/lite-recipes.md +25 -0
- package/skills/dws/dingtalk-minutes/references/minutes.md +210 -0
- package/skills/dws/dingtalk-minutes/references/recipes/conventions.md +33 -0
- package/skills/dws/dingtalk-minutes/scripts/minutes_recent_summary.py +196 -0
- package/skills/dws/dingtalk-minutes/scripts/test_minutes_scripts.py +222 -0
- package/skills/dws/dingtalk-misc/SKILL.md +55 -0
- package/skills/dws/dingtalk-misc/references/05-reporting.md +29 -0
- package/skills/dws/dingtalk-misc/references/agoal.md +302 -0
- package/skills/dws/dingtalk-misc/references/attendance-intent-guide.md +7 -0
- package/skills/dws/dingtalk-misc/references/attendance-report.md +109 -0
- package/skills/dws/dingtalk-misc/references/attendance-schedule.md +115 -0
- package/skills/dws/dingtalk-misc/references/attendance-vacation.md +129 -0
- package/skills/dws/dingtalk-misc/references/attendance.md +149 -0
- package/skills/dws/dingtalk-misc/references/dev/app.md +42 -0
- package/skills/dws/dingtalk-misc/references/dev/connect.md +108 -0
- package/skills/dws/dingtalk-misc/references/dev/credentials.md +26 -0
- package/skills/dws/dingtalk-misc/references/dev/dev-index.md +13 -0
- package/skills/dws/dingtalk-misc/references/dev/event.md +47 -0
- package/skills/dws/dingtalk-misc/references/dev/mcp.md +106 -0
- package/skills/dws/dingtalk-misc/references/dev/member.md +19 -0
- package/skills/dws/dingtalk-misc/references/dev/permission.md +56 -0
- package/skills/dws/dingtalk-misc/references/dev/recipes.md +58 -0
- package/skills/dws/dingtalk-misc/references/dev/robot.md +74 -0
- package/skills/dws/dingtalk-misc/references/dev/security.md +21 -0
- package/skills/dws/dingtalk-misc/references/dev/version.md +99 -0
- package/skills/dws/dingtalk-misc/references/dev/webapp.md +22 -0
- package/skills/dws/dingtalk-misc/references/devapp.md +148 -0
- package/skills/dws/dingtalk-misc/references/devdoc-intent-guide.md +7 -0
- package/skills/dws/dingtalk-misc/references/devdoc.md +42 -0
- package/skills/dws/dingtalk-misc/references/ding.md +184 -0
- package/skills/dws/dingtalk-misc/references/hrbrain.md +67 -0
- package/skills/dws/dingtalk-misc/references/live.md +23 -0
- package/skills/dws/dingtalk-misc/references/markdown.md +167 -0
- package/skills/dws/dingtalk-misc/references/oa/oa-form-components.md +345 -0
- package/skills/dws/dingtalk-misc/references/oa/oa-process-nodes.md +374 -0
- package/skills/dws/dingtalk-misc/references/oa.md +1014 -0
- package/skills/dws/dingtalk-misc/references/openapi-explorer.md +47 -0
- package/skills/dws/dingtalk-misc/references/pat.md +52 -0
- package/skills/dws/dingtalk-misc/references/profile.md +41 -0
- package/skills/dws/dingtalk-misc/references/recruit.md +89 -0
- package/skills/dws/dingtalk-misc/references/report-conventions.md +44 -0
- package/skills/dws/dingtalk-misc/references/report-intent-guide.md +11 -0
- package/skills/dws/dingtalk-misc/references/report-lite-recipes.md +25 -0
- package/skills/dws/dingtalk-misc/references/report.md +109 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-batch-operations.md +104 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-chart.md +69 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-comment.md +166 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-conditional-format.md +59 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-dimension-operations.md +60 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-dropdown.md +103 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-export.md +135 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-filter-view.md +51 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-filter.md +181 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-formula.md +192 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-import.md +76 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-media-image.md +263 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-pivot-table.md +292 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-range-operations.md +161 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-read-data.md +60 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-revision-changeset.md +46 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-search-replace.md +120 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-style-format.md +71 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-version.md +84 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-workbook.md +59 -0
- package/skills/dws/dingtalk-misc/references/sheet/sheet-write-data.md +93 -0
- package/skills/dws/dingtalk-misc/references/sheet-conventions.md +44 -0
- package/skills/dws/dingtalk-misc/references/sheet-intent-guide.md +12 -0
- package/skills/dws/dingtalk-misc/references/sheet.md +166 -0
- package/skills/dws/dingtalk-misc/references/skill.md +98 -0
- package/skills/dws/dingtalk-misc/references/unsupported-scripts.md +49 -0
- package/skills/dws/dingtalk-misc/references/url-patterns.md +144 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/compose.md +200 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md +57 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md +288 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md +249 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md +395 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md +186 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md +313 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md +270 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md +61 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1.md +54 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/replace.md +41 -0
- package/skills/dws/dingtalk-misc/references/whiteboard/vector.md +51 -0
- package/skills/dws/dingtalk-misc/references/whiteboard.md +115 -0
- package/skills/dws/dingtalk-misc/scripts/aiapp_create_and_poll.py +138 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_my_record.py +91 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_report_checkin.py +440 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_report_common.py +1698 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_report_daily.py +558 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_report_detail.py +809 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_report_monthly.py +758 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_report_record.py +947 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_schedule_export.py +344 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_schedule_import.py +498 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_team_shift.py +89 -0
- package/skills/dws/dingtalk-misc/scripts/attendance_vacation_balance.py +617 -0
- package/skills/dws/dingtalk-misc/scripts/finance_daily_cashflow.py +70 -0
- package/skills/dws/dingtalk-misc/scripts/finance_expense_flow.py +138 -0
- package/skills/dws/dingtalk-misc/scripts/oa_batch_approve.py +158 -0
- package/skills/dws/dingtalk-misc/scripts/oa_pending_review.py +132 -0
- package/skills/dws/dingtalk-misc/scripts/report_received_today.py +315 -0
- package/skills/dws/dingtalk-misc/scripts/yida_custom_page_update.py +401 -0
- package/skills/dws/dingtalk-misc/scripts/yida_form_builder.py +526 -0
- package/skills/dws/dingtalk-misc/scripts/yida_form_fields.py +315 -0
- package/skills/dws/dingtalk-misc/scripts/yida_form_inspector.py +311 -0
- package/skills/dws/dingtalk-misc/scripts/yida_form_update.py +185 -0
- package/skills/dws/dingtalk-misc/scripts/yida_jsx_pipeline.py +1588 -0
- package/skills/dws/dingtalk-misc/scripts/yida_page_compiler.py +1441 -0
- package/skills/dws/dingtalk-misc/scripts/yida_page_generate.py +514 -0
- package/skills/dws/dingtalk-misc/scripts/yida_page_schema.py +519 -0
- package/skills/dws/dingtalk-misc/scripts/yida_page_self_check.py +398 -0
- package/skills/dws/dingtalk-misc/scripts/yida_process_flow.py +1478 -0
- package/skills/dws/dingtalk-misc/scripts/yida_process_update.py +589 -0
- package/skills/dws/dingtalk-misc/scripts/yida_report_builder.py +494 -0
- package/skills/dws/dingtalk-misc/scripts/yida_report_charts.py +1434 -0
- package/skills/dws/dingtalk-misc/scripts/yida_report_update.py +244 -0
- package/skills/dws/dingtalk-misc/scripts/yida_schema_common.py +340 -0
- package/skills/dws/dingtalk-shared/SKILL.md +83 -0
- package/skills/dws/dingtalk-shared/references/capability-limits.md +31 -0
- package/skills/dws/dingtalk-shared/references/channel-login.md +26 -0
- package/skills/dws/dingtalk-shared/references/confirmation.md +59 -0
- package/skills/dws/dingtalk-shared/references/error-codes.md +155 -0
- package/skills/dws/dingtalk-shared/references/global-reference.md +122 -0
- package/skills/dws/dingtalk-shared/references/intent-guide.md +537 -0
- package/skills/dws/dingtalk-shared/references/recipes/conventions.md +45 -0
- package/skills/dws/dingtalk-shared/references/recipes/lite-catalog.md +275 -0
- package/skills/dws/dingtalk-shared/references/recipes/meta.md +67 -0
- package/skills/dws/dingtalk-shared/references/routing.md +52 -0
- package/skills/dws/dingtalk-shared/references/runtime-contract.md +11 -0
- package/skills/dws/dingtalk-shared/references/schema-usage.md +88 -0
- package/skills/dws/dingtalk-shared/references/url-patterns.md +144 -0
- package/skills/dws/dingtalk-shared/references/workflow-routing.md +45 -0
- package/skills/dws/dingtalk-todo/SKILL.md +119 -0
- package/skills/dws/dingtalk-todo/references/02-task.md +70 -0
- package/skills/dws/dingtalk-todo/references/intent-guide.md +15 -0
- package/skills/dws/dingtalk-todo/references/lite-recipes.md +68 -0
- package/skills/dws/dingtalk-todo/references/recipes/conventions.md +44 -0
- package/skills/dws/dingtalk-todo/references/todo.md +517 -0
- package/skills/dws/dingtalk-todo/scripts/todo_batch_create.py +354 -0
- package/skills/dws/dingtalk-todo/scripts/todo_daily_summary.py +153 -0
- package/skills/dws/dingtalk-todo/scripts/todo_overdue_check.py +101 -0
- package/skills/dws/dingtalk-wiki/SKILL.md +117 -0
- package/skills/dws/dingtalk-wiki/references/intent-guide.md +14 -0
- package/skills/dws/dingtalk-wiki/references/lite-recipes.md +44 -0
- package/skills/dws/dingtalk-wiki/references/wiki-members.md +32 -0
- package/skills/dws/dingtalk-wiki/references/wiki-node-ops.md +46 -0
- package/skills/dws/dingtalk-wiki/references/wiki.md +65 -0
- package/skills/dws/provenance.json +27 -0
|
@@ -0,0 +1,1339 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import re
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# 源仓契约测试:据本文件位置上溯定位技能层与源仓根(不硬编码仓库布局)。
|
|
7
|
+
# 运行时不需要本测试;已安装副本内缺少同级技能或源仓文档时显式 skip(不静默通过)。
|
|
8
|
+
#
|
|
9
|
+
# **技能在源仓按层组织**:DSH 把每个技能**层**注册成一个技能根 ——
|
|
10
|
+
# `plugins/<插件>/skills/<层>/<技能>/`(本仓是 crwu / dws 两层)与公共层
|
|
11
|
+
# `plugins/common/skills/<技能>/`。所以"同级技能"必须**跨层**查找;只在本技能所在的层里找,
|
|
12
|
+
# 会把整个契约测试静默 skip 掉(2026-09 合并仓库时就发生过:8~12 条断言静默消失)。
|
|
13
|
+
SKILLS_ROOT = Path(__file__).resolve().parents[2] # skills/<层>/<技能>/scripts/<file> → 本技能所在的层
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _has_skill_child(directory: Path) -> bool:
|
|
17
|
+
"""目录里直接放着技能(`<子目录>/SKILL.md`)—— 按内容判定,不靠目录名约定。"""
|
|
18
|
+
return directory.is_dir() and any(
|
|
19
|
+
(child / "SKILL.md").is_file() for child in directory.iterdir() if child.is_dir()
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _skill_roots(start: Path) -> tuple[Path, ...]:
|
|
24
|
+
"""本技能所在的层 + 源仓里其它所有技能层(跨插件、跨层)。
|
|
25
|
+
|
|
26
|
+
DSH 运行时把每个层都注册成技能根,所以"同级技能"必须跨层查找(只在本技能所在的层里找,
|
|
27
|
+
整个契约测试会被静默 skip 掉)。安装副本里没有 `plugins/`,只剩本技能所在的层 ——
|
|
28
|
+
那时同级技能本来就不存在,记 skip 是对的。
|
|
29
|
+
"""
|
|
30
|
+
roots = [start]
|
|
31
|
+
for candidate in start.parents:
|
|
32
|
+
plugins = candidate / "plugins"
|
|
33
|
+
if not plugins.is_dir():
|
|
34
|
+
continue
|
|
35
|
+
for skills in sorted(plugins.glob("*/skills")):
|
|
36
|
+
if not skills.is_dir():
|
|
37
|
+
continue
|
|
38
|
+
if _has_skill_child(skills):
|
|
39
|
+
roots.append(skills) # 公共层:技能直接放在 skills/ 下
|
|
40
|
+
else:
|
|
41
|
+
roots.extend(sorted(layer for layer in skills.iterdir() if _has_skill_child(layer)))
|
|
42
|
+
break
|
|
43
|
+
return tuple(dict.fromkeys(root for root in roots if root.is_dir()))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
SKILLS_ROOTS = _skill_roots(SKILLS_ROOT)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def has_skill(name: str) -> bool:
|
|
50
|
+
"""两个技能根里任一存在该技能目录即为已安装(安装副本只装了单个技能时为 False)。"""
|
|
51
|
+
return any((root / name).is_dir() for root in SKILLS_ROOTS)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def skill_path(rel: str) -> Path:
|
|
55
|
+
"""按「技能名/技能内相对路径」在两个技能根里定位文件;都没有时回落到本技能所在的根。"""
|
|
56
|
+
for root in SKILLS_ROOTS:
|
|
57
|
+
candidate = root / rel
|
|
58
|
+
if candidate.exists():
|
|
59
|
+
return candidate
|
|
60
|
+
return SKILLS_ROOT / rel
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def skill_root_of(name: str) -> Path:
|
|
64
|
+
"""技能 `name` 所属的技能根(安装副本里找不到时回落到本技能所在的根)。"""
|
|
65
|
+
for root in SKILLS_ROOTS:
|
|
66
|
+
if (root / name).is_dir():
|
|
67
|
+
return root
|
|
68
|
+
return SKILLS_ROOT
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _source_repo_root(start: Path) -> Path:
|
|
72
|
+
"""源仓根:向上找带 `go.mod` + `Makefile` 的那一层(安装副本里找不到就回落一层)。"""
|
|
73
|
+
for candidate in (start, *start.parents):
|
|
74
|
+
if (candidate / "go.mod").is_file() and (candidate / "Makefile").is_file():
|
|
75
|
+
return candidate
|
|
76
|
+
return start.parent
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
REPO_ROOT = _source_repo_root(Path(__file__).resolve())
|
|
80
|
+
AUDIT_SKILL_ROOT = SKILLS_ROOT / "crwu-audit"
|
|
81
|
+
|
|
82
|
+
EXPECTED_REFERENCE_FILES = (
|
|
83
|
+
"00-input-and-route-profile.md",
|
|
84
|
+
"01-report-id-resolution.md",
|
|
85
|
+
"02-scope-classification.md",
|
|
86
|
+
"03-asset-classification.md",
|
|
87
|
+
"04-business-classification.md",
|
|
88
|
+
"05-method-classification.md",
|
|
89
|
+
"06-overlay-classification.md",
|
|
90
|
+
"07-skill-registry.md",
|
|
91
|
+
"08-union-dispatch-rules.md",
|
|
92
|
+
"09-review-risk-classification.md",
|
|
93
|
+
"10-capability-gap-proposal.md",
|
|
94
|
+
"11-html-delivery-spec.md",
|
|
95
|
+
"12-leaf-common-contract.md",
|
|
96
|
+
"13-dingtalk-result-publish.md",
|
|
97
|
+
"14-orchestration-workflow.md",
|
|
98
|
+
"99-maintenance.md",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# F-016:router 入口必须**渐进披露**——运行时编排细节归 references/14,入口只留指针与门禁。
|
|
102
|
+
ORCHESTRATION_REFERENCE = "14-orchestration-workflow.md"
|
|
103
|
+
ROUTER_ENTRY_MAX_BYTES = 16000
|
|
104
|
+
ROUTER_ENTRY_MAX_LINES = 160
|
|
105
|
+
# 只应出现在 14 的运行时细节标识(入口里出现即说明编排正文又回流了)。
|
|
106
|
+
ORCHESTRATION_ONLY_TOKENS = (
|
|
107
|
+
"load_workspace_dependencies",
|
|
108
|
+
"crwu_audit_knowledge_materialize",
|
|
109
|
+
"L-resolved",
|
|
110
|
+
"hitExplanation",
|
|
111
|
+
"request_kind=directory",
|
|
112
|
+
)
|
|
113
|
+
# 14 必须保留的执行语义标识(只锁标识,不锁整段文案)。
|
|
114
|
+
ORCHESTRATION_KEPT_TOKENS = (
|
|
115
|
+
"阶段一",
|
|
116
|
+
"阶段二",
|
|
117
|
+
"冻结",
|
|
118
|
+
"复核对照",
|
|
119
|
+
"隔离补审",
|
|
120
|
+
"recursive=true",
|
|
121
|
+
"crwu_audit_dingtalk_archive",
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def router_entry_text() -> str:
|
|
126
|
+
"""router 入口自身的契约文本(只读 SKILL.md)。"""
|
|
127
|
+
return (AUDIT_SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def router_orchestration_text() -> str:
|
|
131
|
+
"""运行时编排契约文本 = 入口 + references/14。
|
|
132
|
+
|
|
133
|
+
入口自身的断言(体积上限、指针、门禁)继续只读 `router_entry_text()`;只有确实检查
|
|
134
|
+
"运行时编排细节"的断言才用组合文本 —— 否则细节一旦合法下沉,契约测试就会退化成
|
|
135
|
+
入口体积测试。
|
|
136
|
+
"""
|
|
137
|
+
reference = AUDIT_SKILL_ROOT / "references" / ORCHESTRATION_REFERENCE
|
|
138
|
+
return router_entry_text() + "\n" + reference.read_text(encoding="utf-8")
|
|
139
|
+
|
|
140
|
+
REQUIRED_ROUTER_TERMS = (
|
|
141
|
+
"SeqNo",
|
|
142
|
+
"ObjectId",
|
|
143
|
+
"scope_types[]",
|
|
144
|
+
"asset_types[]",
|
|
145
|
+
"business_types[]",
|
|
146
|
+
"methods[]",
|
|
147
|
+
"overlays[]",
|
|
148
|
+
"review_risk_class",
|
|
149
|
+
"materiality",
|
|
150
|
+
"skills_to_load",
|
|
151
|
+
"02-scope-classification.md",
|
|
152
|
+
"03-asset-classification.md",
|
|
153
|
+
"04-business-classification.md",
|
|
154
|
+
"05-method-classification.md",
|
|
155
|
+
"06-overlay-classification.md",
|
|
156
|
+
"07-skill-registry.md",
|
|
157
|
+
"08-union-dispatch-rules.md",
|
|
158
|
+
"13-dingtalk-result-publish.md",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
REGISTRY_HEADER = ("axis", "label", "skill", "status", "load behavior")
|
|
162
|
+
|
|
163
|
+
# Stable rows only: business-axis names are mid-migration to crwu-audit-biz-* and
|
|
164
|
+
# are reported by the skill-maintainer mapping checker instead of being pinned here.
|
|
165
|
+
EXPECTED_REGISTRY_ROWS = (
|
|
166
|
+
("asset", "房地产", "crwu-audit-asset-realestate", "available", "load"),
|
|
167
|
+
("asset", "机器设备", "crwu-audit-asset-equipment", "available", "load"),
|
|
168
|
+
("public", "通用准则", "crwu-audit-public-general-standards", "available", "load always"),
|
|
169
|
+
("public", "审核意见屏蔽", "crwu-audit-output-filter", "available", "load always; postprocess before phase1 freeze"),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Public-axis capabilities are report-shape independent. Every one of them must be
|
|
173
|
+
# reachable from the union algorithm without depending on any professional axis label.
|
|
174
|
+
UNCONDITIONAL_PUBLIC_SKILLS = (
|
|
175
|
+
"crwu-audit-public-general-standards",
|
|
176
|
+
"crwu-audit-output-filter",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# 能力型公共轴技能:有各自的触发条件,不得被写成恒装配。
|
|
180
|
+
CONDITIONAL_PUBLIC_SKILLS = ("crwu-audit-datacheck", "crwu-audit-external-data")
|
|
181
|
+
|
|
182
|
+
DISPATCH_AXES = ("scope", "asset", "business", "method", "overlay", "public")
|
|
183
|
+
|
|
184
|
+
LEGACY_REGISTRY_SKILL = "crwu-audit-realestate-rent"
|
|
185
|
+
LEGACY_BUSINESS_PREFIX = "crwu-audit-business-"
|
|
186
|
+
|
|
187
|
+
# R3B 宿主入口边界:同花顺 iFinD 的取数入口随宿主而异,下面三份文档必须口径一致。
|
|
188
|
+
# `connector_probe.py` 只服务 WorkBuddy;DeepSeek Harness 的入口是宿主的结构化 Tool。
|
|
189
|
+
EXTERNAL_DATA_ENTRY_DOCS = (
|
|
190
|
+
"crwu-audit-external-data/SKILL.md",
|
|
191
|
+
"crwu-audit-external-data/references/01-connector-access.md",
|
|
192
|
+
"crwu-audit/references/08-union-dispatch-rules.md",
|
|
193
|
+
)
|
|
194
|
+
HOST_CONNECTOR_PROBE = "connector_probe.py"
|
|
195
|
+
DSH_IFIND_TOOL = "crwu_audit_ifind_query"
|
|
196
|
+
LEGACY_DSH_IFIND_SKILL = "ifind-finance-data"
|
|
197
|
+
HOST_LABELS = ("WorkBuddy", "DeepSeek Harness")
|
|
198
|
+
|
|
199
|
+
# OPT-009C:external-data 入口必须**渐进披露**——基准日三档、核验步骤 0–7 与交付字段契约归
|
|
200
|
+
# references/02;入口只留调用门禁、输入、单源与宿主安全边界、必读导航。
|
|
201
|
+
EXTERNAL_DATA_SKILL = "crwu-audit-external-data"
|
|
202
|
+
EXTERNAL_DATA_WORKFLOW_REFERENCE = "crwu-audit-external-data/references/02-verification-workflow.md"
|
|
203
|
+
EXTERNAL_DATA_ENTRY_MAX_LINES = 100
|
|
204
|
+
EXTERNAL_DATA_ENTRY_MAX_BYTES = 10000
|
|
205
|
+
EXTERNAL_DATA_REFERENCE_SET = (
|
|
206
|
+
"00-KB装配表.md",
|
|
207
|
+
"01-connector-access.md",
|
|
208
|
+
"02-verification-workflow.md",
|
|
209
|
+
)
|
|
210
|
+
# 只应出现在 02 的条件/字段细节(入口里出现即说明核验流程正文又回流了)。
|
|
211
|
+
EXTERNAL_DATA_DETAIL_TOKENS = (
|
|
212
|
+
"多候选(ROUTE003 挂起",
|
|
213
|
+
"条件性取数",
|
|
214
|
+
"公司属性事实",
|
|
215
|
+
"deviationAgainst",
|
|
216
|
+
"只要有 `checks`",
|
|
217
|
+
)
|
|
218
|
+
# 02 必须保留的流程与交付语义。
|
|
219
|
+
EXTERNAL_DATA_WORKFLOW_TOKENS = (
|
|
220
|
+
"applicability.status=not_applicable",
|
|
221
|
+
"applicability.status=required",
|
|
222
|
+
"base_date",
|
|
223
|
+
"base_date_status",
|
|
224
|
+
"externalDataVerification",
|
|
225
|
+
"applicability",
|
|
226
|
+
"baseDate",
|
|
227
|
+
"sources[]",
|
|
228
|
+
"checks[]",
|
|
229
|
+
"deviationAgainst",
|
|
230
|
+
"取数时点、口径、单位、参数",
|
|
231
|
+
"口径无法统一时禁止作数值判定",
|
|
232
|
+
"事实型数据",
|
|
233
|
+
"模型型数据",
|
|
234
|
+
)
|
|
235
|
+
# 入口必须继续**直接**保留的稳定边界。
|
|
236
|
+
EXTERNAL_DATA_ENTRY_TOKENS = (
|
|
237
|
+
"`methods[]`",
|
|
238
|
+
"收益法",
|
|
239
|
+
"市场法",
|
|
240
|
+
"仅经 `crwu-audit` router 编排调用,禁止单独调用",
|
|
241
|
+
"同花顺 iFinD",
|
|
242
|
+
"不启用万得",
|
|
243
|
+
"references/01-connector-access.md",
|
|
244
|
+
"connector_probe.py",
|
|
245
|
+
"WorkBuddy",
|
|
246
|
+
"DeepSeek Harness",
|
|
247
|
+
"DeepSeek Harness 不运行 `connector_probe.py`",
|
|
248
|
+
"`crwu_audit_ifind_query`",
|
|
249
|
+
"route_profile",
|
|
250
|
+
"record_context.base_date",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def external_data_workflow_text() -> str:
|
|
255
|
+
"""external-data 契约文本 = 入口 `SKILL.md` + `references/02-verification-workflow.md`。
|
|
256
|
+
|
|
257
|
+
OPT-009C 把基准日三档、核验步骤 0–7 与交付字段契约下沉到 02;凡检查这些**核验流程**的
|
|
258
|
+
断言读组合文本。**宿主边界断言仍只读入口与 `01-connector-access.md`**,见
|
|
259
|
+
`test_external_data_host_entries_are_host_bound`。
|
|
260
|
+
"""
|
|
261
|
+
entry = skill_path(f"{EXTERNAL_DATA_SKILL}/SKILL.md").read_text(encoding="utf-8")
|
|
262
|
+
reference = skill_path(EXTERNAL_DATA_WORKFLOW_REFERENCE).read_text(encoding="utf-8")
|
|
263
|
+
return entry + "\n" + reference
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _markdown_cells(line):
|
|
267
|
+
stripped = line.strip()
|
|
268
|
+
if "|" not in stripped:
|
|
269
|
+
return None
|
|
270
|
+
cells = []
|
|
271
|
+
for raw_cell in stripped.strip("|").split("|"):
|
|
272
|
+
cell = raw_cell.strip()
|
|
273
|
+
inline_code = re.fullmatch(r"`([^`]*)`", cell)
|
|
274
|
+
if inline_code:
|
|
275
|
+
cell = inline_code.group(1).strip()
|
|
276
|
+
cells.append(cell.casefold())
|
|
277
|
+
return tuple(cells)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _is_markdown_separator(cells):
|
|
281
|
+
return all(re.fullmatch(r":?-{3,}:?", cell.replace(" ", "")) for cell in cells)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _registry_data_rows(text):
|
|
285
|
+
lines = text.splitlines()
|
|
286
|
+
for header_index, line in enumerate(lines):
|
|
287
|
+
if _markdown_cells(line) != REGISTRY_HEADER:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
rows = []
|
|
291
|
+
for candidate in lines[header_index + 1 :]:
|
|
292
|
+
cells = _markdown_cells(candidate)
|
|
293
|
+
if cells is None:
|
|
294
|
+
if rows:
|
|
295
|
+
break
|
|
296
|
+
continue
|
|
297
|
+
if len(cells) != len(REGISTRY_HEADER):
|
|
298
|
+
break
|
|
299
|
+
if _is_markdown_separator(cells):
|
|
300
|
+
continue
|
|
301
|
+
rows.append(cells)
|
|
302
|
+
return rows
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# 源仓契约测试所需的完整技能族:安装副本里缺任何一个 → 显式 skip(不失败、不静默通过)。
|
|
308
|
+
_REQUIRED_SIBLINGS = (
|
|
309
|
+
"crwu-audit",
|
|
310
|
+
"crwu-audit-asset-realestate",
|
|
311
|
+
"crwu-audit-biz-asset-operation",
|
|
312
|
+
"crwu-audit-public-general-standards",
|
|
313
|
+
"crwu-audit-datacheck",
|
|
314
|
+
"crwu-audit-output-filter",
|
|
315
|
+
"crwu-dev-audit-optimize",
|
|
316
|
+
"crwu-dev-audit-skill-maintainer",
|
|
317
|
+
"crwu-dws",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _requires_skill_tree(test):
|
|
322
|
+
"""源仓契约测试前提:同级技能与源仓文档齐备。
|
|
323
|
+
|
|
324
|
+
技能以副本安装时这些内容可能不在(例如只装了单个技能)——此时**显式 skip**,
|
|
325
|
+
既不当成失败,也不静默通过。运行时不需要本测试。
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
def wrapper(self, *args, **kwargs):
|
|
329
|
+
missing = [name for name in _REQUIRED_SIBLINGS if not has_skill(name)]
|
|
330
|
+
if missing:
|
|
331
|
+
self.skipTest(f"非完整技能树(缺少同级技能 {', '.join(missing)}):跳过源仓契约测试")
|
|
332
|
+
return test(self, *args, **kwargs)
|
|
333
|
+
|
|
334
|
+
return wrapper
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class AuditMultiaxisRouterContractTest(unittest.TestCase):
|
|
338
|
+
def test_multiaxis_router_reference_set_exists(self):
|
|
339
|
+
references_root = AUDIT_SKILL_ROOT / "references"
|
|
340
|
+
expected = set(EXPECTED_REFERENCE_FILES)
|
|
341
|
+
actual = {path.name for path in references_root.glob("*.md")}
|
|
342
|
+
missing = sorted(expected - actual)
|
|
343
|
+
extra = sorted(actual - expected)
|
|
344
|
+
|
|
345
|
+
self.assertEqual(
|
|
346
|
+
expected,
|
|
347
|
+
actual,
|
|
348
|
+
f"multiaxis router reference set mismatch: missing={missing}, extra={extra}",
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def test_root_router_declares_multiaxis_profile_and_reference_set(self):
|
|
352
|
+
router_path = AUDIT_SKILL_ROOT / "SKILL.md"
|
|
353
|
+
self.assertTrue(router_path.is_file(), f"missing root router: {router_path}")
|
|
354
|
+
router_text = router_path.read_text(encoding="utf-8")
|
|
355
|
+
|
|
356
|
+
missing = [term for term in REQUIRED_ROUTER_TERMS if term not in router_text]
|
|
357
|
+
|
|
358
|
+
self.assertEqual([], missing, f"root router is missing contract terms: {missing}")
|
|
359
|
+
|
|
360
|
+
def test_router_entrypoint_progressively_discloses_orchestration(self):
|
|
361
|
+
"""F-016:运行时编排细节沉到 `references/14`,入口只留指针、门禁与体积上限。
|
|
362
|
+
|
|
363
|
+
只锁**文件所有权、入口大小与稳定语义标识**,不断言整段逐字文案 —— 文案可以在 14 里
|
|
364
|
+
继续演进,但"细节只在 14 出现一次"这条所有权不得漂回入口。
|
|
365
|
+
"""
|
|
366
|
+
entry_path = AUDIT_SKILL_ROOT / "SKILL.md"
|
|
367
|
+
reference_path = AUDIT_SKILL_ROOT / "references" / ORCHESTRATION_REFERENCE
|
|
368
|
+
self.assertTrue(reference_path.is_file(), f"missing orchestration reference: {reference_path}")
|
|
369
|
+
|
|
370
|
+
entry = router_entry_text()
|
|
371
|
+
reference = reference_path.read_text(encoding="utf-8")
|
|
372
|
+
|
|
373
|
+
# 1) 入口必须显式链接 14(渐进披露的指针)。
|
|
374
|
+
self.assertIn(
|
|
375
|
+
f"references/{ORCHESTRATION_REFERENCE}", entry,
|
|
376
|
+
f"router SKILL.md must link references/{ORCHESTRATION_REFERENCE}",
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# 2) 入口体积上限:编排正文不应再占入口。
|
|
380
|
+
byte_size = len(entry.encode("utf-8"))
|
|
381
|
+
self.assertLessEqual(
|
|
382
|
+
byte_size, ROUTER_ENTRY_MAX_BYTES,
|
|
383
|
+
f"router 入口过大:{byte_size} bytes(上限 {ROUTER_ENTRY_MAX_BYTES})"
|
|
384
|
+
f"——运行时细节应下沉到 references/{ORCHESTRATION_REFERENCE}",
|
|
385
|
+
)
|
|
386
|
+
line_count = len(entry.splitlines())
|
|
387
|
+
self.assertLessEqual(
|
|
388
|
+
line_count, ROUTER_ENTRY_MAX_LINES,
|
|
389
|
+
f"router 入口过长:{line_count} 行(上限 {ROUTER_ENTRY_MAX_LINES})"
|
|
390
|
+
f"——运行时细节应下沉到 references/{ORCHESTRATION_REFERENCE}",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# 3) 运行时细节只在 14:入口不得再出现,14 必须承载。
|
|
394
|
+
for token in ORCHESTRATION_ONLY_TOKENS:
|
|
395
|
+
self.assertNotIn(
|
|
396
|
+
token, entry,
|
|
397
|
+
f"{token} 属运行时编排细节,应只出现在 references/{ORCHESTRATION_REFERENCE},不得留在入口",
|
|
398
|
+
)
|
|
399
|
+
self.assertIn(
|
|
400
|
+
token, reference,
|
|
401
|
+
f"references/{ORCHESTRATION_REFERENCE} 必须承载运行时细节 {token}",
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# 4) 14 必须按序保留步骤 1–15(执行顺序不得丢步或改序)。
|
|
405
|
+
steps = [int(number) for number in re.findall(r"(?m)^(\d+)\.\s", reference)]
|
|
406
|
+
self.assertEqual(
|
|
407
|
+
list(range(1, 16)), steps,
|
|
408
|
+
f"references/{ORCHESTRATION_REFERENCE} 必须按序保留 1–15 全部步骤,实际={steps}",
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# 5) 14 必须保留阶段隔离、Python 运行时与 KB 装配边界的稳定标识。
|
|
412
|
+
for token in ORCHESTRATION_KEPT_TOKENS:
|
|
413
|
+
self.assertIn(
|
|
414
|
+
token, reference,
|
|
415
|
+
f"references/{ORCHESTRATION_REFERENCE} 必须保留执行语义标识 {token}",
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
def test_root_router_rejects_legacy_level_routing(self):
|
|
419
|
+
router_path = AUDIT_SKILL_ROOT / "SKILL.md"
|
|
420
|
+
self.assertTrue(router_path.is_file(), f"missing root router: {router_path}")
|
|
421
|
+
# 旧 L1/L2 路由模型属运行时路由细节,F-016 后归 references/14;断言改读组合文本,
|
|
422
|
+
# 否则该契约会随下沉一起被削弱(模型回流到 14 就没人拦)。
|
|
423
|
+
router_text = router_orchestration_text()
|
|
424
|
+
|
|
425
|
+
self.assertIsNone(
|
|
426
|
+
re.search(r"\bL[12]\b", router_text, flags=re.IGNORECASE),
|
|
427
|
+
"root router must not retain the active L1/L2 routing model",
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
def test_excel_delivery_evidence_must_use_original_coordinates(self):
|
|
431
|
+
profile_path = AUDIT_SKILL_ROOT / "references/00-input-and-route-profile.md"
|
|
432
|
+
profile_text = profile_path.read_text(encoding="utf-8")
|
|
433
|
+
required_terms = (
|
|
434
|
+
"交付物中的 Excel 证据定位",
|
|
435
|
+
"必须使用 raw 原件坐标",
|
|
436
|
+
"工作版坐标仅限审核执行内部使用",
|
|
437
|
+
"映射回原件坐标",
|
|
438
|
+
"无法可靠映射",
|
|
439
|
+
"不得下发该定位",
|
|
440
|
+
"待人工确认事项",
|
|
441
|
+
"capability gap",
|
|
442
|
+
)
|
|
443
|
+
missing = [term for term in required_terms if term not in profile_text]
|
|
444
|
+
|
|
445
|
+
self.assertEqual(
|
|
446
|
+
[],
|
|
447
|
+
missing,
|
|
448
|
+
f"Excel delivery coordinate contract is incomplete: missing={missing}",
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
def test_union_dispatch_rules_define_stable_unique_algorithm(self):
|
|
452
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
453
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
454
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
455
|
+
algorithm = re.search(
|
|
456
|
+
r"\bskills_to_load\s*=\s*stable_unique\s*\((?P<arguments>[\s\S]*?)\)",
|
|
457
|
+
rules_text,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
self.assertIsNotNone(
|
|
461
|
+
algorithm,
|
|
462
|
+
"union dispatch rules must define an executable stable_unique load algorithm",
|
|
463
|
+
)
|
|
464
|
+
arguments = algorithm.group("arguments")
|
|
465
|
+
required_inputs = (
|
|
466
|
+
"scope_skills",
|
|
467
|
+
"asset_skills",
|
|
468
|
+
"business_skills",
|
|
469
|
+
"method_skills",
|
|
470
|
+
"overlay_skills",
|
|
471
|
+
"public_skills",
|
|
472
|
+
)
|
|
473
|
+
missing = [
|
|
474
|
+
name
|
|
475
|
+
for name in required_inputs
|
|
476
|
+
if re.search(rf"\b{re.escape(name)}\b", arguments) is None
|
|
477
|
+
]
|
|
478
|
+
|
|
479
|
+
self.assertEqual([], missing, f"stable union algorithm is missing inputs: {missing}")
|
|
480
|
+
|
|
481
|
+
def test_cost_method_uses_generic_rule_and_realestate_adds_specialized_checklist(self):
|
|
482
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
483
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
484
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
485
|
+
cost_rows = [
|
|
486
|
+
line
|
|
487
|
+
for line in rules_text.splitlines()
|
|
488
|
+
if re.search(r"\|\s*method\s*\|\s*`成本法`\s*\|", line)
|
|
489
|
+
]
|
|
490
|
+
|
|
491
|
+
self.assertEqual(
|
|
492
|
+
1,
|
|
493
|
+
len(cost_rows),
|
|
494
|
+
"成本法必须有且只有一条独立装配映射,不能与资产基础法共用映射行",
|
|
495
|
+
)
|
|
496
|
+
cost_row = cost_rows[0]
|
|
497
|
+
self.assertIn(
|
|
498
|
+
"03-评估方法/00-评估方法准则2019-精编/评估方法准则2019-精编条目/04-成本法",
|
|
499
|
+
cost_row,
|
|
500
|
+
"成本法映射必须装配成本法准则条目",
|
|
501
|
+
)
|
|
502
|
+
self.assertNotIn(
|
|
503
|
+
"06-规则库/清单-M-成本法/",
|
|
504
|
+
cost_row,
|
|
505
|
+
"通用成本法映射不得无条件装配房地产专项清单",
|
|
506
|
+
)
|
|
507
|
+
self.assertNotIn(
|
|
508
|
+
"06-规则库/清单-M-资产基础法/",
|
|
509
|
+
cost_row,
|
|
510
|
+
"成本法不得误装资产基础法专项清单",
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
realestate_cost_rows = [
|
|
514
|
+
line
|
|
515
|
+
for line in rules_text.splitlines()
|
|
516
|
+
if re.search(
|
|
517
|
+
r"\|\s*method\+asset\s*\|\s*`成本法`\s*∧\s*`房地产`\s*\|",
|
|
518
|
+
line,
|
|
519
|
+
)
|
|
520
|
+
]
|
|
521
|
+
self.assertEqual(
|
|
522
|
+
1,
|
|
523
|
+
len(realestate_cost_rows),
|
|
524
|
+
"房地产成本法专项清单必须有独立的 method+asset 条件映射",
|
|
525
|
+
)
|
|
526
|
+
self.assertIn(
|
|
527
|
+
"06-规则库/清单-M-成本法/",
|
|
528
|
+
realestate_cost_rows[0],
|
|
529
|
+
"成本法且资产类型为房地产时必须追加不动产成本法专项清单",
|
|
530
|
+
)
|
|
531
|
+
self.assertIn(
|
|
532
|
+
"跨轴专项映射是在单轴共性映射基础上追加,不替代单轴共性资料",
|
|
533
|
+
rules_text,
|
|
534
|
+
"分发规则必须明确跨轴专项清单不会替代成本法共性准则",
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
def test_asset_based_method_inherits_cost_principles_and_realestate_adds_checklist(self):
|
|
538
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
539
|
+
classification_path = AUDIT_SKILL_ROOT / "references/05-method-classification.md"
|
|
540
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
541
|
+
self.assertTrue(
|
|
542
|
+
classification_path.is_file(),
|
|
543
|
+
f"missing method classification rules: {classification_path}",
|
|
544
|
+
)
|
|
545
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
546
|
+
classification_text = classification_path.read_text(encoding="utf-8")
|
|
547
|
+
asset_based_rows = [
|
|
548
|
+
line
|
|
549
|
+
for line in rules_text.splitlines()
|
|
550
|
+
if re.search(r"\|\s*method\s*\|\s*`资产基础法`\s*\|", line)
|
|
551
|
+
]
|
|
552
|
+
|
|
553
|
+
self.assertEqual(
|
|
554
|
+
1,
|
|
555
|
+
len(asset_based_rows),
|
|
556
|
+
"资产基础法必须有且只有一条独立装配映射",
|
|
557
|
+
)
|
|
558
|
+
asset_based_row = asset_based_rows[0]
|
|
559
|
+
self.assertIn(
|
|
560
|
+
"03-评估方法/00-评估方法准则2019-精编/评估方法准则2019-精编条目/04-成本法",
|
|
561
|
+
asset_based_row,
|
|
562
|
+
"资产基础法属于成本法项下的成本加和法,必须继承成本法共性准则",
|
|
563
|
+
)
|
|
564
|
+
self.assertIn(
|
|
565
|
+
"03-评估方法/03-资产基础法/",
|
|
566
|
+
asset_based_row,
|
|
567
|
+
"资产基础法必须继续装配自身方法说明",
|
|
568
|
+
)
|
|
569
|
+
self.assertNotIn(
|
|
570
|
+
"06-规则库/清单-M-资产基础法/",
|
|
571
|
+
asset_based_row,
|
|
572
|
+
"通用资产基础法映射不得无条件装配房地产专项清单",
|
|
573
|
+
)
|
|
574
|
+
self.assertNotIn(
|
|
575
|
+
"06-规则库/清单-M-成本法/",
|
|
576
|
+
asset_based_row,
|
|
577
|
+
"继承成本法共性准则不等于装配直接成本法专项清单",
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
realestate_asset_based_rows = [
|
|
581
|
+
line
|
|
582
|
+
for line in rules_text.splitlines()
|
|
583
|
+
if re.search(
|
|
584
|
+
r"\|\s*method\+asset\s*\|\s*`资产基础法`\s*∧\s*`房地产`\s*\|",
|
|
585
|
+
line,
|
|
586
|
+
)
|
|
587
|
+
]
|
|
588
|
+
self.assertEqual(
|
|
589
|
+
1,
|
|
590
|
+
len(realestate_asset_based_rows),
|
|
591
|
+
"房地产资产基础法专项清单必须有独立的 method+asset 条件映射",
|
|
592
|
+
)
|
|
593
|
+
self.assertIn(
|
|
594
|
+
"06-规则库/清单-M-资产基础法/",
|
|
595
|
+
realestate_asset_based_rows[0],
|
|
596
|
+
"资产基础法且资产类型为房地产时必须追加不动产专项清单",
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
required_classification_terms = (
|
|
600
|
+
"资产基础法即成本法项下的成本加和法",
|
|
601
|
+
"保留独立 canonical 标签",
|
|
602
|
+
"不得据此新增一个 `成本法` 标签",
|
|
603
|
+
"不得装配 `06-规则库/清单-M-成本法/`",
|
|
604
|
+
"仅在 `asset_types[]` 同时命中 `房地产` 时追加资产基础法不动产专项清单",
|
|
605
|
+
)
|
|
606
|
+
missing = [
|
|
607
|
+
term for term in required_classification_terms if term not in classification_text
|
|
608
|
+
]
|
|
609
|
+
self.assertEqual(
|
|
610
|
+
[],
|
|
611
|
+
missing,
|
|
612
|
+
f"方法分类缺少资产基础法与成本法的继承/隔离规则: {missing}",
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
def test_financial_reporting_coverage_uses_business_trigger(self):
|
|
616
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
617
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
618
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
619
|
+
financial_business_rows = [
|
|
620
|
+
line
|
|
621
|
+
for line in rules_text.splitlines()
|
|
622
|
+
if re.search(r"\|\s*business\s*\|\s*`财务报告`\s*\|", line)
|
|
623
|
+
]
|
|
624
|
+
financial_overlay_rows = [
|
|
625
|
+
line
|
|
626
|
+
for line in rules_text.splitlines()
|
|
627
|
+
if re.search(r"\|\s*overlay\s*\|\s*`财务报告`\s*\|", line)
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
self.assertEqual(
|
|
631
|
+
1,
|
|
632
|
+
len(financial_business_rows),
|
|
633
|
+
"财务报告覆盖规则必须由已存在的 business=财务报告 标签装配",
|
|
634
|
+
)
|
|
635
|
+
self.assertIn(
|
|
636
|
+
"04-监管覆盖/财务报告/",
|
|
637
|
+
financial_business_rows[0],
|
|
638
|
+
"财务报告业务映射必须装配财务报告覆盖层规则",
|
|
639
|
+
)
|
|
640
|
+
self.assertEqual(
|
|
641
|
+
[],
|
|
642
|
+
financial_overlay_rows,
|
|
643
|
+
"overlays[] 不会产生财务报告标签,不得保留不可达的 overlay 映射",
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
financial_skill_root = SKILLS_ROOT / "crwu-audit-biz-financial-reporting"
|
|
647
|
+
if not financial_skill_root.is_dir():
|
|
648
|
+
self.skipTest("未安装财务报告业务技能:跳过其源仓执行契约检查")
|
|
649
|
+
skill_text = (financial_skill_root / "SKILL.md").read_text(encoding="utf-8")
|
|
650
|
+
review_text = (financial_skill_root / "references/02-review-focus.md").read_text(
|
|
651
|
+
encoding="utf-8"
|
|
652
|
+
)
|
|
653
|
+
self.assertIn(
|
|
654
|
+
"RULE-01-02-617~629",
|
|
655
|
+
skill_text,
|
|
656
|
+
"财务报告业务技能必须执行已下载的财务报告覆盖层规则",
|
|
657
|
+
)
|
|
658
|
+
self.assertIn(
|
|
659
|
+
"04-监管覆盖/财务报告/财务报告覆盖层规则",
|
|
660
|
+
review_text,
|
|
661
|
+
"财务报告业务审核关注点必须声明覆盖层规则的实际库内来源",
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
def _table_rows_under(self, text: str, heading_keyword: str) -> list[list[str]]:
|
|
665
|
+
"""Markdown 表格行(去掉表头与分隔行),只取 `heading_keyword` 小节内的。"""
|
|
666
|
+
rows: list[list[str]] = []
|
|
667
|
+
inside = False
|
|
668
|
+
for line in text.splitlines():
|
|
669
|
+
stripped = line.strip()
|
|
670
|
+
if stripped.startswith("#"):
|
|
671
|
+
if inside:
|
|
672
|
+
break
|
|
673
|
+
inside = heading_keyword in stripped
|
|
674
|
+
continue
|
|
675
|
+
if not inside or not stripped.startswith("|"):
|
|
676
|
+
continue
|
|
677
|
+
cells = [cell.strip() for cell in stripped.strip("|").split("|")]
|
|
678
|
+
if _is_markdown_separator(cells) or cells[0] in ("一级业务", "canonical 一级业务"):
|
|
679
|
+
continue
|
|
680
|
+
rows.append(cells)
|
|
681
|
+
return rows
|
|
682
|
+
|
|
683
|
+
def test_financial_reporting_classification_lists_all_five_subroutes(self):
|
|
684
|
+
"""财务报告的子业务取值域必须是 5 项,且含"商誉减值"(快照实况)。"""
|
|
685
|
+
classification = (AUDIT_SKILL_ROOT / "references/04-business-classification.md").read_text(
|
|
686
|
+
encoding="utf-8"
|
|
687
|
+
)
|
|
688
|
+
rows = {
|
|
689
|
+
cells[0]: cells[1]
|
|
690
|
+
for cells in self._table_rows_under(classification, "子业务识别")
|
|
691
|
+
if len(cells) >= 2
|
|
692
|
+
}
|
|
693
|
+
self.assertIn("财务报告", rows, f"子业务表缺少财务报告行:{rows}")
|
|
694
|
+
subroutes = [item for item in re.split(r"[、,,]", rows["财务报告"]) if item]
|
|
695
|
+
self.assertEqual(
|
|
696
|
+
["公允价值计量", "合并对价分摊", "商誉减值", "资产入账", "资产减值测试"],
|
|
697
|
+
subroutes,
|
|
698
|
+
"财务报告子业务必须为 5 项且含「商誉减值」",
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
def test_goodwill_impairment_test_maps_to_goodwill_subroute(self):
|
|
702
|
+
""""商誉减值测试"必须落到子业务"商誉减值",不得再归并到"资产减值测试"。"""
|
|
703
|
+
classification = (AUDIT_SKILL_ROOT / "references/04-business-classification.md").read_text(
|
|
704
|
+
encoding="utf-8"
|
|
705
|
+
)
|
|
706
|
+
self.assertNotIn(
|
|
707
|
+
"商誉减值测试` 得到 `财务报告`(子业务 `资产减值测试`)",
|
|
708
|
+
classification,
|
|
709
|
+
"旧的错误示例(商誉减值测试 → 资产减值测试)必须已移除",
|
|
710
|
+
)
|
|
711
|
+
self.assertIn("商誉减值测试", classification, "分类表必须保留「商誉减值测试」这个命中信号")
|
|
712
|
+
self.assertRegex(
|
|
713
|
+
classification,
|
|
714
|
+
r"商誉减值测试[^\n]{0,40}商誉减值",
|
|
715
|
+
"命中示例必须把「商誉减值测试」指向子业务「商誉减值」",
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
def test_goodwill_impairment_test_keeps_both_axes(self):
|
|
719
|
+
""""商誉减值测试"同时产生资产轴与业务轴结果,任一轴不得替代另一轴。"""
|
|
720
|
+
asset_classification = (AUDIT_SKILL_ROOT / "references/03-asset-classification.md").read_text(
|
|
721
|
+
encoding="utf-8"
|
|
722
|
+
)
|
|
723
|
+
self.assertRegex(
|
|
724
|
+
asset_classification,
|
|
725
|
+
r"资产组-含商誉[^\n]*商誉减值测试[^\n]*crwu-audit-asset-asset-group-goodwill",
|
|
726
|
+
"资产轴必须把「商誉减值测试」映射到 crwu-audit-asset-asset-group-goodwill",
|
|
727
|
+
)
|
|
728
|
+
business_classification = (
|
|
729
|
+
AUDIT_SKILL_ROOT / "references/04-business-classification.md"
|
|
730
|
+
).read_text(encoding="utf-8")
|
|
731
|
+
self.assertIn("crwu-audit-biz-financial-reporting", business_classification)
|
|
732
|
+
dispatch = (AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md").read_text(
|
|
733
|
+
encoding="utf-8"
|
|
734
|
+
)
|
|
735
|
+
self.assertIn("stable_unique", dispatch, "两轴结果必须经稳定并集合并")
|
|
736
|
+
# 08 的现行口径是「禁止首个命中即停止 / 用一个轴覆盖另一个轴」,以及归并时
|
|
737
|
+
# 「不能只留最后写入者」——不得改写成别的说法,否则这条断言失去它守的东西。
|
|
738
|
+
self.assertIn("用一个轴覆盖另一个轴", dispatch, "任一轴不得覆盖另一轴")
|
|
739
|
+
self.assertIn("不能只留最后写入者", dispatch, "跨轴同一事实归并后必须保留全部来源")
|
|
740
|
+
|
|
741
|
+
def test_second_level_selection_keeps_all_hits_and_flags_low_confidence(self):
|
|
742
|
+
"""二级选择:多命中全部保留、低置信度标记 review_required=true。"""
|
|
743
|
+
for relative in (
|
|
744
|
+
"references/03-asset-classification.md",
|
|
745
|
+
"references/04-business-classification.md",
|
|
746
|
+
):
|
|
747
|
+
text = (AUDIT_SKILL_ROOT / relative).read_text(encoding="utf-8")
|
|
748
|
+
self.assertIn("首次命中即停止", text, f"{relative} 必须显式禁止首次命中即停止")
|
|
749
|
+
# 字段名两处一致;`=true` 的显式取值写在 04(本次可改范围),03 只登记字段。
|
|
750
|
+
self.assertIn("review_required", text, f"{relative} 必须登记低置信度人工复核字段")
|
|
751
|
+
four = (AUDIT_SKILL_ROOT / "references/04-business-classification.md").read_text(
|
|
752
|
+
encoding="utf-8"
|
|
753
|
+
)
|
|
754
|
+
self.assertIn(
|
|
755
|
+
"review_required=true", four, "04 必须显式要求低置信度标记 review_required=true"
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
def test_asset_and_business_leaves_no_longer_claim_missing_second_level(self):
|
|
759
|
+
"""机器设备/债权不得再声称没有 02-细分对象;财务报告必须索引 5 个子业务。"""
|
|
760
|
+
expectations = {
|
|
761
|
+
"crwu-audit-asset-equipment": ["通用设备", "专用设备", "电子设备", "闲置及报废设备"],
|
|
762
|
+
"crwu-audit-asset-debt": ["不良债权", "应收账款", "其他应收款", "抵债资产"],
|
|
763
|
+
}
|
|
764
|
+
for skill, labels in expectations.items():
|
|
765
|
+
leaf = SKILLS_ROOT / skill
|
|
766
|
+
if not leaf.is_dir():
|
|
767
|
+
self.skipTest(f"未安装 {skill}:跳过其二级索引检查")
|
|
768
|
+
texts = {
|
|
769
|
+
name: (leaf / "references" / name).read_text(encoding="utf-8")
|
|
770
|
+
for name in ("00-applicability.md", "01-kb-assembly.md", "02-review-focus.md")
|
|
771
|
+
}
|
|
772
|
+
joined = "\n".join(texts.values())
|
|
773
|
+
self.assertNotIn("未提供 `02-细分对象", joined, f"{skill} 仍声明没有细分对象")
|
|
774
|
+
for label in labels:
|
|
775
|
+
self.assertIn(label, texts["00-applicability.md"], f"{skill} 分类表缺少 {label}")
|
|
776
|
+
self.assertIn(label, texts["01-kb-assembly.md"], f"{skill} 装配索引缺少 {label}")
|
|
777
|
+
self.assertIn(label, texts["02-review-focus.md"], f"{skill} 审核关注点缺少 {label}")
|
|
778
|
+
|
|
779
|
+
financial = SKILLS_ROOT / "crwu-audit-biz-financial-reporting"
|
|
780
|
+
if financial.is_dir():
|
|
781
|
+
joined = "\n".join(
|
|
782
|
+
(financial / "references" / name).read_text(encoding="utf-8")
|
|
783
|
+
for name in ("00-applicability.md", "01-kb-assembly.md", "02-review-focus.md")
|
|
784
|
+
)
|
|
785
|
+
for subroute in ("公允价值计量", "合并对价分摊", "商誉减值", "资产入账", "资产减值测试"):
|
|
786
|
+
self.assertIn(subroute, joined, f"财务报告叶子缺少子业务 {subroute}")
|
|
787
|
+
|
|
788
|
+
def test_income_checklist_requires_enterprise_value_asset(self):
|
|
789
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
790
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
791
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
792
|
+
income_method_rows = [
|
|
793
|
+
line
|
|
794
|
+
for line in rules_text.splitlines()
|
|
795
|
+
if re.search(r"\|\s*method\s*\|\s*`收益法`\s*\|", line)
|
|
796
|
+
]
|
|
797
|
+
enterprise_income_rows = [
|
|
798
|
+
line
|
|
799
|
+
for line in rules_text.splitlines()
|
|
800
|
+
if re.search(
|
|
801
|
+
r"\|\s*method\+asset\s*\|\s*`收益法`\s*∧\s*`企业价值`\s*\|",
|
|
802
|
+
line,
|
|
803
|
+
)
|
|
804
|
+
]
|
|
805
|
+
|
|
806
|
+
self.assertEqual(
|
|
807
|
+
1,
|
|
808
|
+
len(income_method_rows),
|
|
809
|
+
"收益法必须有且只有一条不区分资产类型的通用方法映射",
|
|
810
|
+
)
|
|
811
|
+
self.assertIn(
|
|
812
|
+
"03-评估方法/02-收益法/",
|
|
813
|
+
income_method_rows[0],
|
|
814
|
+
"所有收益法项目都必须装配通用收益法目录",
|
|
815
|
+
)
|
|
816
|
+
self.assertNotIn(
|
|
817
|
+
"06-规则库/清单-M-收益法/",
|
|
818
|
+
income_method_rows[0],
|
|
819
|
+
"通用收益法映射不得无条件装配企业价值专项清单",
|
|
820
|
+
)
|
|
821
|
+
self.assertEqual(
|
|
822
|
+
1,
|
|
823
|
+
len(enterprise_income_rows),
|
|
824
|
+
"企业价值收益法专项清单必须有独立的 method+asset 条件映射",
|
|
825
|
+
)
|
|
826
|
+
self.assertIn(
|
|
827
|
+
"06-规则库/清单-M-收益法/",
|
|
828
|
+
enterprise_income_rows[0],
|
|
829
|
+
"收益法且资产类型为企业价值时必须装配企业价值收益法专项清单",
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
def test_market_method_uses_generic_rules_and_realestate_adds_specialized_checklist(self):
|
|
833
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
834
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
835
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
836
|
+
market_method_rows = [
|
|
837
|
+
line
|
|
838
|
+
for line in rules_text.splitlines()
|
|
839
|
+
if re.search(r"\|\s*method\s*\|\s*`市场法`\s*\|", line)
|
|
840
|
+
]
|
|
841
|
+
realestate_market_rows = [
|
|
842
|
+
line
|
|
843
|
+
for line in rules_text.splitlines()
|
|
844
|
+
if re.search(
|
|
845
|
+
r"\|\s*method\+asset\s*\|\s*`市场法`\s*∧\s*`房地产`\s*\|",
|
|
846
|
+
line,
|
|
847
|
+
)
|
|
848
|
+
]
|
|
849
|
+
|
|
850
|
+
self.assertEqual(1, len(market_method_rows), "市场法必须有一条通用方法映射")
|
|
851
|
+
market_row = market_method_rows[0]
|
|
852
|
+
self.assertIn(
|
|
853
|
+
"03-评估方法/00-评估方法准则2019-精编/评估方法准则2019-精编条目/02-市场法",
|
|
854
|
+
market_row,
|
|
855
|
+
"所有市场法项目都必须装配市场法准则条目",
|
|
856
|
+
)
|
|
857
|
+
self.assertIn(
|
|
858
|
+
"03-评估方法/01-市场法/",
|
|
859
|
+
market_row,
|
|
860
|
+
"所有市场法项目都必须装配通用市场法说明",
|
|
861
|
+
)
|
|
862
|
+
self.assertNotIn(
|
|
863
|
+
"06-规则库/清单-M-市场法/",
|
|
864
|
+
market_row,
|
|
865
|
+
"通用市场法映射不得无条件装配房地产专项清单",
|
|
866
|
+
)
|
|
867
|
+
self.assertEqual(
|
|
868
|
+
1,
|
|
869
|
+
len(realestate_market_rows),
|
|
870
|
+
"房地产市场法专项清单必须有独立的 method+asset 条件映射",
|
|
871
|
+
)
|
|
872
|
+
self.assertIn(
|
|
873
|
+
"06-规则库/清单-M-市场法/",
|
|
874
|
+
realestate_market_rows[0],
|
|
875
|
+
"市场法且资产类型为房地产时必须追加不动产市场法专项清单",
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
realestate_assembly = (
|
|
879
|
+
SKILLS_ROOT / "crwu-audit-asset-realestate/references/01-kb-assembly.md"
|
|
880
|
+
)
|
|
881
|
+
if not realestate_assembly.is_file():
|
|
882
|
+
self.skipTest("未安装房地产资产技能:跳过方法层所有权检查")
|
|
883
|
+
assembly_text = realestate_assembly.read_text(encoding="utf-8")
|
|
884
|
+
obsolete_keys = (
|
|
885
|
+
"VALUATION_METHOD_INTERFACE",
|
|
886
|
+
"MARKET_METHOD_CHECKLIST",
|
|
887
|
+
"MARKET_METHOD_NOTES",
|
|
888
|
+
"METHOD_DEFECT_LIST",
|
|
889
|
+
"PITFALL_LIBRARY",
|
|
890
|
+
)
|
|
891
|
+
remaining = [key for key in obsolete_keys if f"`{key}`" in assembly_text]
|
|
892
|
+
self.assertEqual(
|
|
893
|
+
[],
|
|
894
|
+
remaining,
|
|
895
|
+
f"房地产资产叶子不得无条件持有方法层或公共层装配键: {remaining}",
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
def test_union_dispatch_rules_load_four_skills_for_realestate_liquidation_auction(self):
|
|
899
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
900
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
901
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
902
|
+
scenario_start = rules_text.find("房地产清算后拍卖处置")
|
|
903
|
+
|
|
904
|
+
self.assertGreaterEqual(
|
|
905
|
+
scenario_start,
|
|
906
|
+
0,
|
|
907
|
+
"union dispatch rules must include the realestate liquidation auction scenario",
|
|
908
|
+
)
|
|
909
|
+
scenario_body = rules_text[scenario_start : scenario_start + 1200]
|
|
910
|
+
expected_skills = (
|
|
911
|
+
"crwu-audit-asset-realestate",
|
|
912
|
+
"crwu-audit-biz-judicial-liquidation-compensation",
|
|
913
|
+
"crwu-audit-biz-transaction-disposal",
|
|
914
|
+
)
|
|
915
|
+
missing = [skill for skill in expected_skills if skill not in scenario_body]
|
|
916
|
+
|
|
917
|
+
self.assertEqual(
|
|
918
|
+
[],
|
|
919
|
+
missing,
|
|
920
|
+
f"realestate liquidation auction scenario is missing nearby skills: {missing}",
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
@_requires_skill_tree
|
|
924
|
+
def test_axis_named_leaf_skills_replace_legacy_combined_skills(self):
|
|
925
|
+
asset_leaf = SKILLS_ROOT / "crwu-audit-asset-realestate/SKILL.md"
|
|
926
|
+
obsolete_skill_dirs = (
|
|
927
|
+
SKILLS_ROOT / "crwu-audit-realestate",
|
|
928
|
+
SKILLS_ROOT / "crwu-audit-realestate-rent",
|
|
929
|
+
)
|
|
930
|
+
remaining = [str(path.relative_to(REPO_ROOT)) for path in obsolete_skill_dirs if path.exists()]
|
|
931
|
+
|
|
932
|
+
self.assertTrue(asset_leaf.is_file(), f"missing axis-named asset leaf: {asset_leaf}")
|
|
933
|
+
self.assertEqual([], remaining, f"legacy combined skill directories remain: {remaining}")
|
|
934
|
+
|
|
935
|
+
@_requires_skill_tree
|
|
936
|
+
def test_leaf_skill_directories_use_axis_prefixes_only(self):
|
|
937
|
+
approved = ("crwu-audit-asset-", "crwu-audit-biz-")
|
|
938
|
+
offenders = sorted(
|
|
939
|
+
child.name
|
|
940
|
+
for child in SKILLS_ROOT.iterdir()
|
|
941
|
+
if child.is_dir()
|
|
942
|
+
and child.name.startswith("crwu-audit-")
|
|
943
|
+
and not child.name.startswith(approved)
|
|
944
|
+
and child.name
|
|
945
|
+
not in {
|
|
946
|
+
"crwu-audit",
|
|
947
|
+
"crwu-dev-audit-optimize",
|
|
948
|
+
"crwu-dev-audit-skill-maintainer",
|
|
949
|
+
"crwu-audit-datacheck",
|
|
950
|
+
"crwu-audit-external-data",
|
|
951
|
+
"crwu-audit-output-filter",
|
|
952
|
+
"crwu-audit-public-general-standards",
|
|
953
|
+
}
|
|
954
|
+
)
|
|
955
|
+
|
|
956
|
+
self.assertEqual([], offenders, f"crwu-audit leaf directories must use axis prefixes: {offenders}")
|
|
957
|
+
|
|
958
|
+
def test_unconditional_public_skills_are_not_conditioned_on_professional_axes(self):
|
|
959
|
+
rules_path = AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md"
|
|
960
|
+
router_path = AUDIT_SKILL_ROOT / "SKILL.md"
|
|
961
|
+
self.assertTrue(rules_path.is_file(), f"missing union dispatch rules: {rules_path}")
|
|
962
|
+
self.assertTrue(router_path.is_file(), f"missing router skill entry: {router_path}")
|
|
963
|
+
rules_text = rules_path.read_text(encoding="utf-8")
|
|
964
|
+
# 建 public_skills 的**可执行步骤**(F-016 后归 references/14)也必须写明无条件规则,
|
|
965
|
+
# 否则并集算法(08)与可执行步骤会再次漂移 —— 故此处读组合契约文本。
|
|
966
|
+
router_text = router_orchestration_text()
|
|
967
|
+
|
|
968
|
+
public_expression = re.search(
|
|
969
|
+
r"public_skills\s*=\s*(?P<expression>[\s\S]*?)\n\n",
|
|
970
|
+
rules_text,
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
self.assertIsNotNone(
|
|
974
|
+
public_expression,
|
|
975
|
+
"union dispatch rules must define the public_skills expression",
|
|
976
|
+
)
|
|
977
|
+
expression = public_expression.group("expression")
|
|
978
|
+
for skill in UNCONDITIONAL_PUBLIC_SKILLS:
|
|
979
|
+
self.assertIn(
|
|
980
|
+
skill,
|
|
981
|
+
expression,
|
|
982
|
+
f"public_skills expression must always include {skill}",
|
|
983
|
+
)
|
|
984
|
+
self.assertNotIn(
|
|
985
|
+
f"{skill}] when",
|
|
986
|
+
expression,
|
|
987
|
+
f"{skill} must not be conditioned on materials or professional labels",
|
|
988
|
+
)
|
|
989
|
+
self.assertNotIn(
|
|
990
|
+
"else []",
|
|
991
|
+
expression.split("crwu-audit-public-general-standards", 1)[0],
|
|
992
|
+
"unconditional public skills must be declared before any conditional branch",
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
# The router step that builds public_skills must state the unconditional rule too,
|
|
996
|
+
# otherwise the algorithm and the runnable step can drift apart.
|
|
997
|
+
self.assertIn(
|
|
998
|
+
"crwu-audit-public-general-standards",
|
|
999
|
+
router_text,
|
|
1000
|
+
"router SKILL.md must name the unconditional public skill in its public-capability step",
|
|
1001
|
+
)
|
|
1002
|
+
self.assertIn(
|
|
1003
|
+
"crwu-audit-output-filter",
|
|
1004
|
+
router_text,
|
|
1005
|
+
"router SKILL.md must name the unconditional output-filter skill",
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
@_requires_skill_tree
|
|
1009
|
+
def test_public_skill_directory_and_references_exist(self):
|
|
1010
|
+
for skill in UNCONDITIONAL_PUBLIC_SKILLS:
|
|
1011
|
+
skill_dir = SKILLS_ROOT / skill
|
|
1012
|
+
self.assertTrue(skill_dir.is_dir(), f"missing public-axis skill directory: {skill}")
|
|
1013
|
+
skill_entry = skill_dir / "SKILL.md"
|
|
1014
|
+
self.assertTrue(skill_entry.is_file(), f"missing public-axis SKILL.md: {skill}")
|
|
1015
|
+
entry_text = skill_entry.read_text(encoding="utf-8")
|
|
1016
|
+
self.assertIn(
|
|
1017
|
+
f"name: {skill}",
|
|
1018
|
+
entry_text,
|
|
1019
|
+
f"{skill} frontmatter name must match its directory name",
|
|
1020
|
+
)
|
|
1021
|
+
self.assertIn(
|
|
1022
|
+
"仅经 `crwu-audit` router 编排调用,禁止单独调用",
|
|
1023
|
+
entry_text,
|
|
1024
|
+
f"{skill} must declare the router-only invocation gate",
|
|
1025
|
+
)
|
|
1026
|
+
for reference in ("00-applicability.md", "01-kb-assembly.md", "02-review-focus.md"):
|
|
1027
|
+
reference_path = skill_dir / "references" / reference
|
|
1028
|
+
self.assertTrue(
|
|
1029
|
+
reference_path.is_file(),
|
|
1030
|
+
f"{skill} is missing required reference: {reference}",
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
@_requires_skill_tree
|
|
1034
|
+
def test_output_filter_uses_only_the_runtime_checklist_before_phase1_freeze(self):
|
|
1035
|
+
skill_dir = SKILLS_ROOT / "crwu-audit-output-filter"
|
|
1036
|
+
entry_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
|
|
1037
|
+
assembly_text = (skill_dir / "references/01-kb-assembly.md").read_text(encoding="utf-8")
|
|
1038
|
+
# "输出过滤发生在阶段一冻结前"是运行时编排顺序,F-016 后由 references/14 承载,
|
|
1039
|
+
# 故顺序断言读组合契约文本(技能自身的断言仍只读其 SKILL.md)。
|
|
1040
|
+
router_text = router_orchestration_text()
|
|
1041
|
+
|
|
1042
|
+
self.assertIn("06-规则库/04-AI审核意见屏蔽/02-屏蔽清单", assembly_text)
|
|
1043
|
+
self.assertNotIn("06-规则库/04-AI审核意见屏蔽/01-维护规则", assembly_text)
|
|
1044
|
+
self.assertIn("候选审核意见汇总完成后", entry_text)
|
|
1045
|
+
self.assertIn("阶段一冻结前", entry_text)
|
|
1046
|
+
self.assertIn("匹配标识", entry_text)
|
|
1047
|
+
self.assertIn("不屏蔽例外", entry_text)
|
|
1048
|
+
self.assertIn("不进入正式审核结果", entry_text)
|
|
1049
|
+
|
|
1050
|
+
filter_pos = router_text.index("crwu-audit-output-filter")
|
|
1051
|
+
freeze_pos = router_text.index("阶段一独立汇总、过滤并冻结")
|
|
1052
|
+
self.assertLess(filter_pos, freeze_pos, "output filter must run before the phase-one freeze")
|
|
1053
|
+
|
|
1054
|
+
@_requires_skill_tree
|
|
1055
|
+
def test_external_data_skill_is_registered_and_conditionally_dispatched(self):
|
|
1056
|
+
"""外部数据核验能力:public 轴登记 + 按收益法/市场法条件装配 + 技能自带装配与探测件齐备。"""
|
|
1057
|
+
registry_text = (AUDIT_SKILL_ROOT / "references/07-skill-registry.md").read_text(encoding="utf-8")
|
|
1058
|
+
rows = _registry_data_rows(registry_text)
|
|
1059
|
+
self.assertIsNotNone(rows, "skill registry table must be parseable")
|
|
1060
|
+
self.assertIn(
|
|
1061
|
+
("public", "外部数据核验", "crwu-audit-external-data", "available",
|
|
1062
|
+
"触发语义见 `08-union-dispatch-rules.md`(`methods[]` 命中 收益法/市场法);本表不复述"),
|
|
1063
|
+
rows,
|
|
1064
|
+
"crwu-audit-external-data must be registered on the public axis",
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
rules_text = (AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md").read_text(encoding="utf-8")
|
|
1068
|
+
expression = re.search(r"public_skills\s*=\s*(?P<expression>[\s\S]*?)\n\n", rules_text)
|
|
1069
|
+
self.assertIsNotNone(expression, "union dispatch rules must define the public_skills expression")
|
|
1070
|
+
body = expression.group("expression")
|
|
1071
|
+
self.assertIn("crwu-audit-external-data", body, "public_skills must include the external-data capability")
|
|
1072
|
+
self.assertNotIn(
|
|
1073
|
+
"crwu-audit-external-data] when methods",
|
|
1074
|
+
body.split("crwu-audit-public-general-standards", 1)[0],
|
|
1075
|
+
"conditional public skills must be declared after the unconditional one",
|
|
1076
|
+
)
|
|
1077
|
+
|
|
1078
|
+
skill_dir = SKILLS_ROOT / "crwu-audit-external-data"
|
|
1079
|
+
self.assertTrue(skill_dir.is_dir(), "missing crwu-audit-external-data skill directory")
|
|
1080
|
+
for rel in ("SKILL.md", "references/00-KB装配表.md", "references/01-connector-access.md",
|
|
1081
|
+
"references/02-verification-workflow.md",
|
|
1082
|
+
"scripts/connector_probe.py", "scripts/test_connector_probe.py"):
|
|
1083
|
+
self.assertTrue((skill_dir / rel).is_file(), f"crwu-audit-external-data is missing {rel}")
|
|
1084
|
+
entry_text = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
|
|
1085
|
+
self.assertIn("name: crwu-audit-external-data", entry_text)
|
|
1086
|
+
self.assertIn("仅经 `crwu-audit` router 编排调用,禁止单独调用", entry_text)
|
|
1087
|
+
self.assertIn("06-规则库/M-外部数据核验/01-模块-外部数据核验",
|
|
1088
|
+
(skill_dir / "references/00-KB装配表.md").read_text(encoding="utf-8"),
|
|
1089
|
+
"KB assembly table must address the external-data module by in-KB path")
|
|
1090
|
+
|
|
1091
|
+
@_requires_skill_tree
|
|
1092
|
+
def test_external_data_base_date_gate_is_tiered(self):
|
|
1093
|
+
"""基准日门禁必须是三档,且不得退回"挂起即整线不取数"的一刀切。
|
|
1094
|
+
|
|
1095
|
+
依据 08-union-dispatch-rules.md:ROUTE003 挂起 base_date 及依赖基准日的规则/结论,
|
|
1096
|
+
但不按技能身份无差别停载;不依赖冲突字段的检查仍须执行。
|
|
1097
|
+
"""
|
|
1098
|
+
# OPT-009C:基准日三档已下沉到 references/02-verification-workflow.md;断言读组合契约文本。
|
|
1099
|
+
workflow_text = external_data_workflow_text()
|
|
1100
|
+
for tier in ("唯一确定", "多候选(ROUTE003 挂起", "缺失 / 不可解析"):
|
|
1101
|
+
self.assertIn(tier, workflow_text, f"base-date gate must define tier: {tier}")
|
|
1102
|
+
self.assertIn("条件性取数", workflow_text, "multi-candidate tier must allow conditional fetch")
|
|
1103
|
+
self.assertIn("不自行判定 ROUTE 冲突", workflow_text, "the gate must defer conflict judgement to the router")
|
|
1104
|
+
self.assertIn(
|
|
1105
|
+
"不依赖冲突字段的规则仍须执行",
|
|
1106
|
+
workflow_text,
|
|
1107
|
+
"the gate must not stop all checks on a hung base date",
|
|
1108
|
+
)
|
|
1109
|
+
self.assertNotIn(
|
|
1110
|
+
"基准日本身存疑(ROUTE003 挂起)时,本技能不取数",
|
|
1111
|
+
workflow_text,
|
|
1112
|
+
"the one-size-fits-all kill switch must not come back",
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
@_requires_skill_tree
|
|
1116
|
+
def test_external_data_host_entries_are_host_bound(self):
|
|
1117
|
+
"""R3B:取数入口文档必须 host-bound —— 探测脚本只属 WorkBuddy,DSH 入口是结构化 Tool。
|
|
1118
|
+
|
|
1119
|
+
过期文档把 `connector_probe.py` 写成"两宿主通用的可用性预检",并把 DeepSeek Harness 的
|
|
1120
|
+
入口写成第三方技能目录。本测试只锁稳定语义:宿主边界、脚本的宿主归属、DSH 不运行探测脚本、
|
|
1121
|
+
以及 DSH 入口的结构化 Tool 名(除 Tool 名外不锁具体措辞)。
|
|
1122
|
+
"""
|
|
1123
|
+
docs = {}
|
|
1124
|
+
for rel in EXTERNAL_DATA_ENTRY_DOCS:
|
|
1125
|
+
path = skill_path(rel)
|
|
1126
|
+
self.assertTrue(path.is_file(), f"缺少宿主入口文档:{rel}")
|
|
1127
|
+
docs[rel] = path.read_text(encoding="utf-8")
|
|
1128
|
+
|
|
1129
|
+
# 1) 三份文档都不得再把 DSH 取数入口写成第三方技能。
|
|
1130
|
+
for rel, text in docs.items():
|
|
1131
|
+
self.assertNotIn(
|
|
1132
|
+
LEGACY_DSH_IFIND_SKILL,
|
|
1133
|
+
text,
|
|
1134
|
+
f"{rel} 仍把 DSH 取数入口写成第三方技能(应为结构化 Tool {DSH_IFIND_TOOL})",
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
skill_md = docs[EXTERNAL_DATA_ENTRY_DOCS[0]]
|
|
1138
|
+
access_md = docs[EXTERNAL_DATA_ENTRY_DOCS[1]]
|
|
1139
|
+
rules_md = docs[EXTERNAL_DATA_ENTRY_DOCS[2]]
|
|
1140
|
+
|
|
1141
|
+
# 2) 探测脚本只能出现在明确的宿主语境里 —— 不得再作"两宿主通用预检"。
|
|
1142
|
+
for rel in EXTERNAL_DATA_ENTRY_DOCS[:2]:
|
|
1143
|
+
probe_lines = [line for line in docs[rel].splitlines() if HOST_CONNECTOR_PROBE in line]
|
|
1144
|
+
self.assertTrue(probe_lines, f"{rel} 必须保留 WorkBuddy 侧的探测脚本说明")
|
|
1145
|
+
for line in probe_lines:
|
|
1146
|
+
self.assertTrue(
|
|
1147
|
+
any(label in line for label in HOST_LABELS),
|
|
1148
|
+
f"{rel} 里探测脚本必须写明所属宿主(不得写成通用预检):{line.strip()!r}",
|
|
1149
|
+
)
|
|
1150
|
+
|
|
1151
|
+
# 3) SKILL.md:明确 DeepSeek Harness 不运行该脚本判断可用性。
|
|
1152
|
+
self.assertRegex(
|
|
1153
|
+
skill_md,
|
|
1154
|
+
r"DeepSeek Harness[^\n。]{0,40}不运行[^\n。]{0,40}" + re.escape(HOST_CONNECTOR_PROBE),
|
|
1155
|
+
"SKILL.md 必须写明 DeepSeek Harness 不运行探测脚本判断可用性",
|
|
1156
|
+
)
|
|
1157
|
+
|
|
1158
|
+
# 4) 01-connector-access.md:DSH 没有 WorkBuddy 的宿主声明探测级(L2)。
|
|
1159
|
+
self.assertRegex(
|
|
1160
|
+
access_md,
|
|
1161
|
+
r"DeepSeek Harness[^\n。]{0,20}无此级",
|
|
1162
|
+
"01-connector-access.md 必须写明 DeepSeek Harness 没有宿主声明探测级",
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
# 5) 08-union-dispatch-rules.md:DSH 路径写成结构化 Tool。
|
|
1166
|
+
self.assertIn(
|
|
1167
|
+
DSH_IFIND_TOOL,
|
|
1168
|
+
rules_md,
|
|
1169
|
+
f"08-union-dispatch-rules.md 必须把 DSH 取数路径写成结构化 Tool {DSH_IFIND_TOOL}",
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
@_requires_skill_tree
|
|
1173
|
+
def test_current_catalog_and_design_docs_name_the_structured_tool(self):
|
|
1174
|
+
"""R3C:现行技能目录、架构说明与可执行样例都不得再留旧 DSH 技能路径。
|
|
1175
|
+
|
|
1176
|
+
这三处都是"现行"内容:`docs/skills.md` 是技能目录,设计文档仍承担架构说明职责,
|
|
1177
|
+
样例 JSON 会渲染进交付 HTML。源仓文档在安装副本里不存在 → 显式 skip,不静默通过。
|
|
1178
|
+
"""
|
|
1179
|
+
catalog_docs = (
|
|
1180
|
+
REPO_ROOT / "docs/skills.md",
|
|
1181
|
+
REPO_ROOT / "docs/v0.0.1/design-crwu-audit-skills.md",
|
|
1182
|
+
)
|
|
1183
|
+
present = [path for path in catalog_docs if path.is_file()]
|
|
1184
|
+
if not present:
|
|
1185
|
+
self.skipTest("安装副本内没有源仓现行文档:跳过技能目录与架构说明的路径检查")
|
|
1186
|
+
|
|
1187
|
+
sample_rel = "crwu-audit/scripts/examples/audit-result.sample.json"
|
|
1188
|
+
targets = [(str(path.relative_to(REPO_ROOT)), path.read_text(encoding="utf-8")) for path in present]
|
|
1189
|
+
targets.append((sample_rel, skill_path(sample_rel).read_text(encoding="utf-8")))
|
|
1190
|
+
|
|
1191
|
+
for rel, text in targets:
|
|
1192
|
+
self.assertNotIn(
|
|
1193
|
+
LEGACY_DSH_IFIND_SKILL,
|
|
1194
|
+
text,
|
|
1195
|
+
f"{rel} 仍把 DSH 取数入口写成第三方技能(应为结构化 Tool {DSH_IFIND_TOOL})",
|
|
1196
|
+
)
|
|
1197
|
+
self.assertIn(
|
|
1198
|
+
DSH_IFIND_TOOL,
|
|
1199
|
+
text,
|
|
1200
|
+
f"{rel} 必须把 DSH 取数入口写成结构化 Tool {DSH_IFIND_TOOL}",
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
@_requires_skill_tree
|
|
1204
|
+
def test_external_data_progressively_discloses_verification_workflow(self):
|
|
1205
|
+
"""OPT-009C:external-data 入口必须渐进披露——核验流程与字段契约归 `references/02`。
|
|
1206
|
+
|
|
1207
|
+
只锁**文件所有权、入口大小与稳定语义标识**,不断言整段逐字文案。入口的调用门禁、输入、
|
|
1208
|
+
单源声明与宿主安全边界仍必须**直接**留在 `SKILL.md`。
|
|
1209
|
+
"""
|
|
1210
|
+
entry_path = skill_path(f"{EXTERNAL_DATA_SKILL}/SKILL.md")
|
|
1211
|
+
reference_path = skill_path(EXTERNAL_DATA_WORKFLOW_REFERENCE)
|
|
1212
|
+
self.assertTrue(reference_path.is_file(), f"missing verification workflow reference: {reference_path}")
|
|
1213
|
+
entry = entry_path.read_text(encoding="utf-8")
|
|
1214
|
+
reference = reference_path.read_text(encoding="utf-8")
|
|
1215
|
+
|
|
1216
|
+
# 1+2) 入口必须显式链接该 reference,并要求执行前读取。
|
|
1217
|
+
self.assertIn(
|
|
1218
|
+
"references/02-verification-workflow.md", entry,
|
|
1219
|
+
"external-data 入口必须链接 references/02-verification-workflow.md",
|
|
1220
|
+
)
|
|
1221
|
+
self.assertIn("执行前必读", entry, "入口必须要求执行前读取核验流程 reference")
|
|
1222
|
+
|
|
1223
|
+
# 3) 入口体积上限:核验流程正文不应再占入口。
|
|
1224
|
+
line_count = len(entry.splitlines())
|
|
1225
|
+
self.assertLessEqual(
|
|
1226
|
+
line_count, EXTERNAL_DATA_ENTRY_MAX_LINES,
|
|
1227
|
+
f"external-data 入口过长:{line_count} 行(上限 {EXTERNAL_DATA_ENTRY_MAX_LINES})",
|
|
1228
|
+
)
|
|
1229
|
+
byte_size = len(entry.encode("utf-8"))
|
|
1230
|
+
self.assertLessEqual(
|
|
1231
|
+
byte_size, EXTERNAL_DATA_ENTRY_MAX_BYTES,
|
|
1232
|
+
f"external-data 入口过大:{byte_size} bytes(上限 {EXTERNAL_DATA_ENTRY_MAX_BYTES})",
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
# 4) 条件/字段细节只在 02:入口不得再出现,02 必须承载。
|
|
1236
|
+
for token in EXTERNAL_DATA_DETAIL_TOKENS:
|
|
1237
|
+
self.assertNotIn(
|
|
1238
|
+
token, entry,
|
|
1239
|
+
f"{token} 属核验流程/交付字段细节,应只出现在 {EXTERNAL_DATA_WORKFLOW_REFERENCE}",
|
|
1240
|
+
)
|
|
1241
|
+
self.assertIn(
|
|
1242
|
+
token, reference,
|
|
1243
|
+
f"{EXTERNAL_DATA_WORKFLOW_REFERENCE} 必须承载 {token}",
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
# 5) 基准日三档必须完整保留在 02。
|
|
1247
|
+
for tier in ("唯一确定", "多候选(ROUTE003 挂起", "缺失 / 不可解析"):
|
|
1248
|
+
self.assertIn(tier, reference, f"02 必须保留基准日档位:{tier}")
|
|
1249
|
+
|
|
1250
|
+
# 6) 02 必须保留流程与交付语义标识。
|
|
1251
|
+
for token in EXTERNAL_DATA_WORKFLOW_TOKENS:
|
|
1252
|
+
self.assertIn(token, reference, f"{EXTERNAL_DATA_WORKFLOW_REFERENCE} 必须保留 {token}")
|
|
1253
|
+
|
|
1254
|
+
# 7) 02 中核验步骤 0–7 必须完整且有序(步骤行形如 `N. **...**`)。
|
|
1255
|
+
steps = [int(number) for number in re.findall(r"(?m)^(\d+)\.\s\*\*", reference)]
|
|
1256
|
+
self.assertEqual(
|
|
1257
|
+
list(range(8)), steps,
|
|
1258
|
+
f"{EXTERNAL_DATA_WORKFLOW_REFERENCE} 必须按序保留核验步骤 0–7,实际={steps}",
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
# 8) 入口必须继续直接保留稳定边界。
|
|
1262
|
+
for token in EXTERNAL_DATA_ENTRY_TOKENS:
|
|
1263
|
+
self.assertIn(token, entry, f"入口必须直接保留稳定边界标识:{token}")
|
|
1264
|
+
|
|
1265
|
+
# 9) references 集合必须恰为三者(无缺失、无意外额外文件)。
|
|
1266
|
+
references_root = entry_path.parent / "references"
|
|
1267
|
+
actual = {path.name for path in references_root.glob("*.md")}
|
|
1268
|
+
self.assertEqual(
|
|
1269
|
+
set(EXTERNAL_DATA_REFERENCE_SET), actual,
|
|
1270
|
+
f"external-data reference 集合不符:{sorted(actual)}",
|
|
1271
|
+
)
|
|
1272
|
+
|
|
1273
|
+
@_requires_skill_tree
|
|
1274
|
+
def test_conditional_public_skills_are_not_unconditional(self):
|
|
1275
|
+
rules_text = (AUDIT_SKILL_ROOT / "references/08-union-dispatch-rules.md").read_text(encoding="utf-8")
|
|
1276
|
+
expression = re.search(r"public_skills\s*=\s*(?P<expression>[\s\S]*?)\n\n", rules_text)
|
|
1277
|
+
self.assertIsNotNone(expression, "union dispatch rules must define the public_skills expression")
|
|
1278
|
+
body = expression.group("expression")
|
|
1279
|
+
for skill in CONDITIONAL_PUBLIC_SKILLS:
|
|
1280
|
+
self.assertIn(skill, body, f"public_skills must declare the condition for {skill}")
|
|
1281
|
+
self.assertRegex(
|
|
1282
|
+
body,
|
|
1283
|
+
re.compile(re.escape(skill) + r"\]\s*when\b"),
|
|
1284
|
+
f"{skill} must carry an explicit trigger condition",
|
|
1285
|
+
)
|
|
1286
|
+
|
|
1287
|
+
@_requires_skill_tree
|
|
1288
|
+
def test_active_contracts_do_not_reference_deleted_combined_skill(self):
|
|
1289
|
+
active_paths = (
|
|
1290
|
+
REPO_ROOT / "docs/skills.md",
|
|
1291
|
+
AUDIT_SKILL_ROOT / "SKILL.md",
|
|
1292
|
+
SKILLS_ROOT / "crwu-audit-asset-realestate/SKILL.md",
|
|
1293
|
+
SKILLS_ROOT / "crwu-dev-audit-skill-maintainer/SKILL.md",
|
|
1294
|
+
)
|
|
1295
|
+
forbidden_skill = "crwu-audit-realestate-rent"
|
|
1296
|
+
violations = [
|
|
1297
|
+
str(path.relative_to(REPO_ROOT))
|
|
1298
|
+
for path in active_paths
|
|
1299
|
+
if path.is_file() and forbidden_skill in path.read_text(encoding="utf-8")
|
|
1300
|
+
]
|
|
1301
|
+
|
|
1302
|
+
self.assertEqual(
|
|
1303
|
+
[],
|
|
1304
|
+
violations,
|
|
1305
|
+
f"active contracts still reference {forbidden_skill}: {violations}",
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
def test_skill_registry_names_every_dispatch_axis(self):
|
|
1309
|
+
registry_path = AUDIT_SKILL_ROOT / "references/07-skill-registry.md"
|
|
1310
|
+
self.assertTrue(registry_path.is_file(), f"missing skill registry: {registry_path}")
|
|
1311
|
+
registry_text = registry_path.read_text(encoding="utf-8")
|
|
1312
|
+
registry_rows = _registry_data_rows(registry_text)
|
|
1313
|
+
|
|
1314
|
+
self.assertIsNotNone(
|
|
1315
|
+
registry_rows,
|
|
1316
|
+
f"registry must contain the Markdown header: {' | '.join(REGISTRY_HEADER)}",
|
|
1317
|
+
)
|
|
1318
|
+
missing = [row for row in EXPECTED_REGISTRY_ROWS if row not in registry_rows]
|
|
1319
|
+
registered_skills = {row[2] for row in registry_rows}
|
|
1320
|
+
|
|
1321
|
+
self.assertEqual([], missing, f"skill registry is missing required relationship rows: {missing}")
|
|
1322
|
+
self.assertNotIn(
|
|
1323
|
+
LEGACY_REGISTRY_SKILL,
|
|
1324
|
+
registered_skills,
|
|
1325
|
+
f"legacy combined skill remains registered: {LEGACY_REGISTRY_SKILL}",
|
|
1326
|
+
)
|
|
1327
|
+
axes = {row[0] for row in registry_rows}
|
|
1328
|
+
absent = [axis for axis in DISPATCH_AXES if axis not in axes]
|
|
1329
|
+
self.assertEqual([], absent, f"registry does not name every dispatch axis: {absent}")
|
|
1330
|
+
# Registry<->reality hygiene (a retired-prefix row still marked `available`, or an
|
|
1331
|
+
# available row whose skill directory does not exist) is owned by the skill
|
|
1332
|
+
# maintainer mapping checker, which reports it with per-row evidence:
|
|
1333
|
+
# python3 <skills 根>/crwu-dev-audit-skill-maintainer/scripts/check_audit_skill_mappings.py
|
|
1334
|
+
# -> LEGACY_BUSINESS_PREFIX / AVAILABLE_SKILL_DIRECTORY_MISSING / REGISTRY_LABEL_NOT_IN_CATALOG
|
|
1335
|
+
# It is deliberately not duplicated here, so the in-flight migration has one owner.
|
|
1336
|
+
|
|
1337
|
+
|
|
1338
|
+
if __name__ == "__main__":
|
|
1339
|
+
unittest.main()
|