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.
Files changed (479) hide show
  1. package/CHANGELOG.md +1516 -0
  2. package/LICENSE +26 -0
  3. package/README.en.md +204 -0
  4. package/README.md +521 -0
  5. package/SECURITY.md +39 -0
  6. package/bin/darwin-arm64/crwu +0 -0
  7. package/bin/darwin-arm64/dws +0 -0
  8. package/bin/darwin-arm64/ossutil +0 -0
  9. package/bin/manifest.json +82 -0
  10. package/bin/win32-x64/crwu.exe +0 -0
  11. package/bin/win32-x64/dws.exe +4 -0
  12. package/bin/win32-x64/ossutil.exe +0 -0
  13. package/common/skills/.synced.json +8 -0
  14. package/common/skills/crwu-dws/SKILL.md +208 -0
  15. package/common/skills/crwu-dws/references/00-/347/233/256/345/275/225/345/277/253/347/205/247schema.md +113 -0
  16. 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
  17. 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
  18. package/common/skills/crwu-dws/scripts/test_dws_source_contract.py +493 -0
  19. package/common/skills/crwu-h3yun-login/SKILL.md +130 -0
  20. package/common/skills/crwu-h3yun-query/SKILL.md +192 -0
  21. package/config/crwu-workbench.yml +26 -0
  22. package/cordis.patch.yml +46 -0
  23. package/lib/client.js +10346 -0
  24. package/lib/index.js +11584 -0
  25. package/package.json +119 -0
  26. package/scripts/prepare.mjs +59 -0
  27. package/skills/README.md +26 -0
  28. package/skills/crwu/crwu-audit/SKILL.md +152 -0
  29. package/skills/crwu/crwu-audit/references/00-input-and-route-profile.md +232 -0
  30. package/skills/crwu/crwu-audit/references/01-report-id-resolution.md +13 -0
  31. package/skills/crwu/crwu-audit/references/02-scope-classification.md +25 -0
  32. package/skills/crwu/crwu-audit/references/03-asset-classification.md +27 -0
  33. package/skills/crwu/crwu-audit/references/04-business-classification.md +63 -0
  34. package/skills/crwu/crwu-audit/references/05-method-classification.md +66 -0
  35. package/skills/crwu/crwu-audit/references/06-overlay-classification.md +42 -0
  36. package/skills/crwu/crwu-audit/references/07-skill-registry.md +57 -0
  37. package/skills/crwu/crwu-audit/references/08-union-dispatch-rules.md +142 -0
  38. package/skills/crwu/crwu-audit/references/09-review-risk-classification.md +85 -0
  39. package/skills/crwu/crwu-audit/references/10-capability-gap-proposal.md +68 -0
  40. package/skills/crwu/crwu-audit/references/11-html-delivery-spec.md +881 -0
  41. package/skills/crwu/crwu-audit/references/12-leaf-common-contract.md +202 -0
  42. package/skills/crwu/crwu-audit/references/13-dingtalk-result-publish.md +88 -0
  43. package/skills/crwu/crwu-audit/references/14-orchestration-workflow.md +52 -0
  44. package/skills/crwu/crwu-audit/references/99-maintenance.md +62 -0
  45. package/skills/crwu/crwu-audit/scripts/README.md +157 -0
  46. package/skills/crwu/crwu-audit/scripts/audit_delivery.py +2736 -0
  47. package/skills/crwu/crwu-audit/scripts/audit_result.schema.json +1659 -0
  48. package/skills/crwu/crwu-audit/scripts/examples/audit-result.sample.json +739 -0
  49. package/skills/crwu/crwu-audit/scripts/fetch_review_records.py +85 -0
  50. package/skills/crwu/crwu-audit/scripts/media_extract.py +513 -0
  51. package/skills/crwu/crwu-audit/scripts/prepare_materials.py +1252 -0
  52. package/skills/crwu/crwu-audit/scripts/test_audit_delivery.py +1678 -0
  53. package/skills/crwu/crwu-audit/scripts/test_audit_multiaxis_router.py +1339 -0
  54. package/skills/crwu/crwu-audit/scripts/test_fetch_review_records.py +77 -0
  55. package/skills/crwu/crwu-audit/scripts/test_media_extract.py +332 -0
  56. package/skills/crwu/crwu-audit/scripts/test_prepare_materials.py +1081 -0
  57. package/skills/crwu/crwu-audit/template/audit-report.html +435 -0
  58. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/SKILL.md +38 -0
  59. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/00-applicability.md +28 -0
  60. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/01-kb-assembly.md +31 -0
  61. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/02-review-focus.md +21 -0
  62. package/skills/crwu/crwu-audit-asset-asset-group-goodwill/references/03-common-contract.md +202 -0
  63. package/skills/crwu/crwu-audit-asset-debt/SKILL.md +38 -0
  64. package/skills/crwu/crwu-audit-asset-debt/references/00-applicability.md +40 -0
  65. package/skills/crwu/crwu-audit-asset-debt/references/01-kb-assembly.md +41 -0
  66. package/skills/crwu/crwu-audit-asset-debt/references/02-review-focus.md +54 -0
  67. package/skills/crwu/crwu-audit-asset-debt/references/03-common-contract.md +202 -0
  68. package/skills/crwu/crwu-audit-asset-enterprise-value/SKILL.md +38 -0
  69. package/skills/crwu/crwu-audit-asset-enterprise-value/references/00-applicability.md +31 -0
  70. package/skills/crwu/crwu-audit-asset-enterprise-value/references/01-kb-assembly.md +35 -0
  71. package/skills/crwu/crwu-audit-asset-enterprise-value/references/02-review-focus.md +33 -0
  72. package/skills/crwu/crwu-audit-asset-enterprise-value/references/03-common-contract.md +202 -0
  73. package/skills/crwu/crwu-audit-asset-equipment/SKILL.md +38 -0
  74. package/skills/crwu/crwu-audit-asset-equipment/references/00-applicability.md +40 -0
  75. package/skills/crwu/crwu-audit-asset-equipment/references/01-kb-assembly.md +39 -0
  76. package/skills/crwu/crwu-audit-asset-equipment/references/02-review-focus.md +52 -0
  77. package/skills/crwu/crwu-audit-asset-equipment/references/03-common-contract.md +202 -0
  78. package/skills/crwu/crwu-audit-asset-intangible/SKILL.md +38 -0
  79. package/skills/crwu/crwu-audit-asset-intangible/references/00-applicability.md +36 -0
  80. package/skills/crwu/crwu-audit-asset-intangible/references/01-kb-assembly.md +40 -0
  81. package/skills/crwu/crwu-audit-asset-intangible/references/02-review-focus.md +68 -0
  82. package/skills/crwu/crwu-audit-asset-intangible/references/03-common-contract.md +202 -0
  83. package/skills/crwu/crwu-audit-asset-inventory/SKILL.md +38 -0
  84. package/skills/crwu/crwu-audit-asset-inventory/references/00-applicability.md +28 -0
  85. package/skills/crwu/crwu-audit-asset-inventory/references/01-kb-assembly.md +31 -0
  86. package/skills/crwu/crwu-audit-asset-inventory/references/02-review-focus.md +21 -0
  87. package/skills/crwu/crwu-audit-asset-inventory/references/03-common-contract.md +202 -0
  88. package/skills/crwu/crwu-audit-asset-mining-right/SKILL.md +38 -0
  89. package/skills/crwu/crwu-audit-asset-mining-right/references/00-applicability.md +31 -0
  90. package/skills/crwu/crwu-audit-asset-mining-right/references/01-kb-assembly.md +35 -0
  91. package/skills/crwu/crwu-audit-asset-mining-right/references/02-review-focus.md +33 -0
  92. package/skills/crwu/crwu-audit-asset-mining-right/references/03-common-contract.md +202 -0
  93. package/skills/crwu/crwu-audit-asset-other/SKILL.md +38 -0
  94. package/skills/crwu/crwu-audit-asset-other/references/00-applicability.md +28 -0
  95. package/skills/crwu/crwu-audit-asset-other/references/01-kb-assembly.md +31 -0
  96. package/skills/crwu/crwu-audit-asset-other/references/02-review-focus.md +21 -0
  97. package/skills/crwu/crwu-audit-asset-other/references/03-common-contract.md +202 -0
  98. package/skills/crwu/crwu-audit-asset-portfolio/SKILL.md +38 -0
  99. package/skills/crwu/crwu-audit-asset-portfolio/references/00-applicability.md +28 -0
  100. package/skills/crwu/crwu-audit-asset-portfolio/references/01-kb-assembly.md +31 -0
  101. package/skills/crwu/crwu-audit-asset-portfolio/references/02-review-focus.md +21 -0
  102. package/skills/crwu/crwu-audit-asset-portfolio/references/03-common-contract.md +202 -0
  103. package/skills/crwu/crwu-audit-asset-realestate/SKILL.md +38 -0
  104. package/skills/crwu/crwu-audit-asset-realestate/references/00-applicability.md +43 -0
  105. package/skills/crwu/crwu-audit-asset-realestate/references/01-kb-assembly.md +75 -0
  106. package/skills/crwu/crwu-audit-asset-realestate/references/02-review-focus.md +93 -0
  107. package/skills/crwu/crwu-audit-asset-realestate/references/03-common-contract.md +202 -0
  108. package/skills/crwu/crwu-audit-asset-scrap-materials/SKILL.md +38 -0
  109. package/skills/crwu/crwu-audit-asset-scrap-materials/references/00-applicability.md +28 -0
  110. package/skills/crwu/crwu-audit-asset-scrap-materials/references/01-kb-assembly.md +31 -0
  111. package/skills/crwu/crwu-audit-asset-scrap-materials/references/02-review-focus.md +21 -0
  112. package/skills/crwu/crwu-audit-asset-scrap-materials/references/03-common-contract.md +202 -0
  113. package/skills/crwu/crwu-audit-asset-transport-equipment/SKILL.md +38 -0
  114. package/skills/crwu/crwu-audit-asset-transport-equipment/references/00-applicability.md +31 -0
  115. package/skills/crwu/crwu-audit-asset-transport-equipment/references/01-kb-assembly.md +35 -0
  116. package/skills/crwu/crwu-audit-asset-transport-equipment/references/02-review-focus.md +33 -0
  117. package/skills/crwu/crwu-audit-asset-transport-equipment/references/03-common-contract.md +202 -0
  118. package/skills/crwu/crwu-audit-biz-asset-operation/SKILL.md +38 -0
  119. package/skills/crwu/crwu-audit-biz-asset-operation/references/00-applicability.md +30 -0
  120. package/skills/crwu/crwu-audit-biz-asset-operation/references/01-kb-assembly.md +32 -0
  121. package/skills/crwu/crwu-audit-biz-asset-operation/references/02-review-focus.md +42 -0
  122. package/skills/crwu/crwu-audit-biz-asset-operation/references/03-common-contract.md +202 -0
  123. package/skills/crwu/crwu-audit-biz-consulting-review/SKILL.md +38 -0
  124. package/skills/crwu/crwu-audit-biz-consulting-review/references/00-applicability.md +30 -0
  125. package/skills/crwu/crwu-audit-biz-consulting-review/references/01-kb-assembly.md +32 -0
  126. package/skills/crwu/crwu-audit-biz-consulting-review/references/02-review-focus.md +40 -0
  127. package/skills/crwu/crwu-audit-biz-consulting-review/references/03-common-contract.md +202 -0
  128. package/skills/crwu/crwu-audit-biz-financial-reporting/SKILL.md +38 -0
  129. package/skills/crwu/crwu-audit-biz-financial-reporting/references/00-applicability.md +41 -0
  130. package/skills/crwu/crwu-audit-biz-financial-reporting/references/01-kb-assembly.md +40 -0
  131. package/skills/crwu/crwu-audit-biz-financial-reporting/references/02-review-focus.md +64 -0
  132. package/skills/crwu/crwu-audit-biz-financial-reporting/references/03-common-contract.md +202 -0
  133. package/skills/crwu/crwu-audit-biz-financing-debt/SKILL.md +38 -0
  134. package/skills/crwu/crwu-audit-biz-financing-debt/references/00-applicability.md +31 -0
  135. package/skills/crwu/crwu-audit-biz-financing-debt/references/01-kb-assembly.md +34 -0
  136. package/skills/crwu/crwu-audit-biz-financing-debt/references/02-review-focus.md +47 -0
  137. package/skills/crwu/crwu-audit-biz-financing-debt/references/03-common-contract.md +202 -0
  138. package/skills/crwu/crwu-audit-biz-investment-capital/SKILL.md +38 -0
  139. package/skills/crwu/crwu-audit-biz-investment-capital/references/00-applicability.md +31 -0
  140. package/skills/crwu/crwu-audit-biz-investment-capital/references/01-kb-assembly.md +34 -0
  141. package/skills/crwu/crwu-audit-biz-investment-capital/references/02-review-focus.md +47 -0
  142. package/skills/crwu/crwu-audit-biz-investment-capital/references/03-common-contract.md +202 -0
  143. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/SKILL.md +38 -0
  144. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/00-applicability.md +30 -0
  145. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/01-kb-assembly.md +32 -0
  146. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/02-review-focus.md +40 -0
  147. package/skills/crwu/crwu-audit-biz-judicial-liquidation-compensation/references/03-common-contract.md +202 -0
  148. package/skills/crwu/crwu-audit-biz-tax-history/SKILL.md +38 -0
  149. package/skills/crwu/crwu-audit-biz-tax-history/references/00-applicability.md +29 -0
  150. package/skills/crwu/crwu-audit-biz-tax-history/references/01-kb-assembly.md +30 -0
  151. package/skills/crwu/crwu-audit-biz-tax-history/references/02-review-focus.md +33 -0
  152. package/skills/crwu/crwu-audit-biz-tax-history/references/03-common-contract.md +202 -0
  153. package/skills/crwu/crwu-audit-biz-transaction-disposal/SKILL.md +38 -0
  154. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/00-applicability.md +31 -0
  155. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/01-kb-assembly.md +34 -0
  156. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/02-review-focus.md +47 -0
  157. package/skills/crwu/crwu-audit-biz-transaction-disposal/references/03-common-contract.md +202 -0
  158. package/skills/crwu/crwu-audit-datacheck/SKILL.md +59 -0
  159. package/skills/crwu/crwu-audit-datacheck/references/00-KB/350/243/205/351/205/215/350/241/250.md +25 -0
  160. package/skills/crwu/crwu-audit-datacheck/references/01-runtime-and-check-catalog.md +139 -0
  161. package/skills/crwu/crwu-audit-datacheck/scripts/recalc_check.py +521 -0
  162. package/skills/crwu/crwu-audit-datacheck/scripts/test_recalc_check.py +419 -0
  163. package/skills/crwu/crwu-audit-datacheck/scripts/test_skill_self_containment.py +44 -0
  164. package/skills/crwu/crwu-audit-external-data/SKILL.md +73 -0
  165. package/skills/crwu/crwu-audit-external-data/references/00-KB/350/243/205/351/205/215/350/241/250.md +38 -0
  166. package/skills/crwu/crwu-audit-external-data/references/01-connector-access.md +116 -0
  167. package/skills/crwu/crwu-audit-external-data/references/02-verification-workflow.md +78 -0
  168. package/skills/crwu/crwu-audit-external-data/scripts/connector_probe.py +374 -0
  169. package/skills/crwu/crwu-audit-external-data/scripts/test_connector_probe.py +264 -0
  170. package/skills/crwu/crwu-audit-output-filter/SKILL.md +34 -0
  171. package/skills/crwu/crwu-audit-output-filter/references/00-applicability.md +7 -0
  172. package/skills/crwu/crwu-audit-output-filter/references/01-kb-assembly.md +11 -0
  173. package/skills/crwu/crwu-audit-output-filter/references/02-review-focus.md +25 -0
  174. package/skills/crwu/crwu-audit-public-general-standards/SKILL.md +46 -0
  175. package/skills/crwu/crwu-audit-public-general-standards/references/00-applicability.md +41 -0
  176. package/skills/crwu/crwu-audit-public-general-standards/references/01-kb-assembly.md +66 -0
  177. package/skills/crwu/crwu-audit-public-general-standards/references/02-review-focus.md +111 -0
  178. package/skills/crwu/crwu-audit-public-general-standards/references/03-execution-contract.md +89 -0
  179. package/skills/crwu/crwu-dev-audit-optimize/SKILL.md +98 -0
  180. 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
  181. 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
  182. 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
  183. 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
  184. package/skills/crwu/crwu-dev-audit-optimize/scripts/examples/gap-analysis.sample.json +116 -0
  185. package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis.schema.json +33 -0
  186. package/skills/crwu/crwu-dev-audit-optimize/scripts/gap_analysis_delivery.py +475 -0
  187. package/skills/crwu/crwu-dev-audit-optimize/scripts/test_gap_analysis_delivery.py +855 -0
  188. package/skills/crwu/crwu-dev-audit-optimize/template/NOTICE.echarts.txt +7 -0
  189. package/skills/crwu/crwu-dev-audit-optimize/template/echarts.min.js +45 -0
  190. package/skills/crwu/crwu-dev-audit-optimize/template/gap-analysis-report.html +71 -0
  191. package/skills/crwu/crwu-dev-audit-skill-maintainer/SKILL.md +74 -0
  192. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/00-responsibility-and-modes.md +62 -0
  193. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/01-kb-source-discovery.md +89 -0
  194. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/02-child-skill-contract.md +99 -0
  195. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/03-review-item-execution-contract.md +51 -0
  196. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/04-registry-and-mapping-update.md +73 -0
  197. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/05-validation-and-delivery.md +127 -0
  198. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/06-live-routing-reconciliation.md +136 -0
  199. package/skills/crwu/crwu-dev-audit-skill-maintainer/references/07-kb-skill-map.md +411 -0
  200. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/README.md +200 -0
  201. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/check_audit_skill_mappings.py +2477 -0
  202. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/kb_tool.py +577 -0
  203. package/skills/crwu/crwu-dev-audit-skill-maintainer/scripts/test_audit_skill_maintainer.py +5097 -0
  204. package/skills/dws/LICENSE +178 -0
  205. package/skills/dws/NOTICE +17 -0
  206. package/skills/dws/dingtalk-aisearch/SKILL.md +83 -0
  207. package/skills/dws/dingtalk-aisearch/references/aisearch.md +249 -0
  208. package/skills/dws/dingtalk-aisearch/references/intent-guide.md +15 -0
  209. package/skills/dws/dingtalk-aisearch/references/lite-recipes.md +31 -0
  210. package/skills/dws/dingtalk-aitable/SKILL.md +139 -0
  211. package/skills/dws/dingtalk-aitable/references/06-data-analytics.md +11 -0
  212. package/skills/dws/dingtalk-aitable/references/aitable/aitable-advperm.md +251 -0
  213. package/skills/dws/dingtalk-aitable/references/aitable/aitable-attachment.md +49 -0
  214. package/skills/dws/dingtalk-aitable/references/aitable/aitable-best-practices.md +48 -0
  215. package/skills/dws/dingtalk-aitable/references/aitable/aitable-cell-value.md +346 -0
  216. package/skills/dws/dingtalk-aitable/references/aitable/aitable-dashboard-chart.md +46 -0
  217. package/skills/dws/dingtalk-aitable/references/aitable/aitable-data-analysis-sop.md +131 -0
  218. package/skills/dws/dingtalk-aitable/references/aitable/aitable-datasource.md +272 -0
  219. package/skills/dws/dingtalk-aitable/references/aitable/aitable-error-recovery.md +128 -0
  220. package/skills/dws/dingtalk-aitable/references/aitable/aitable-export-import.md +113 -0
  221. package/skills/dws/dingtalk-aitable/references/aitable/aitable-field-properties.md +238 -0
  222. package/skills/dws/dingtalk-aitable/references/aitable/aitable-field.md +186 -0
  223. package/skills/dws/dingtalk-aitable/references/aitable/aitable-filter-sort.md +135 -0
  224. package/skills/dws/dingtalk-aitable/references/aitable/aitable-form.md +130 -0
  225. package/skills/dws/dingtalk-aitable/references/aitable/aitable-formula-guide.md +224 -0
  226. package/skills/dws/dingtalk-aitable/references/aitable/aitable-primary-doc.md +58 -0
  227. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-create.md +52 -0
  228. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-delete.md +20 -0
  229. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-history.md +97 -0
  230. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-name-key.md +47 -0
  231. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-query.md +120 -0
  232. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-share.md +54 -0
  233. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-stats.md +59 -0
  234. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-update.md +66 -0
  235. package/skills/dws/dingtalk-aitable/references/aitable/aitable-record-upsert.md +89 -0
  236. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-config.md +140 -0
  237. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-extras.md +195 -0
  238. package/skills/dws/dingtalk-aitable/references/aitable/aitable-view-types.md +95 -0
  239. package/skills/dws/dingtalk-aitable/references/aitable/aitable-workflow.md +384 -0
  240. package/skills/dws/dingtalk-aitable/references/aitable-record-ops.md +80 -0
  241. package/skills/dws/dingtalk-aitable/references/aitable-section.md +30 -0
  242. package/skills/dws/dingtalk-aitable/references/aitable.md +59 -0
  243. package/skills/dws/dingtalk-aitable/references/field-rules.md +17 -0
  244. package/skills/dws/dingtalk-aitable/references/intent-guide.md +17 -0
  245. package/skills/dws/dingtalk-aitable/references/recipes/conventions.md +44 -0
  246. package/skills/dws/dingtalk-aitable/references/url-patterns.md +144 -0
  247. package/skills/dws/dingtalk-aitable/scripts/aitable_export_via_task.py +210 -0
  248. package/skills/dws/dingtalk-aitable/scripts/aitable_import_via_task.py +173 -0
  249. package/skills/dws/dingtalk-aitable/scripts/bulk_add_fields.py +273 -0
  250. package/skills/dws/dingtalk-aitable/scripts/import_records.py +333 -0
  251. package/skills/dws/dingtalk-aitable/scripts/upload_attachment.py +190 -0
  252. package/skills/dws/dingtalk-calendar/SKILL.md +137 -0
  253. package/skills/dws/dingtalk-calendar/references/03-meeting.md +56 -0
  254. package/skills/dws/dingtalk-calendar/references/calendar.md +760 -0
  255. package/skills/dws/dingtalk-calendar/references/intent-guide.md +9 -0
  256. package/skills/dws/dingtalk-calendar/references/lite-recipes.md +25 -0
  257. package/skills/dws/dingtalk-calendar/scripts/calendar_free_slot_finder.py +199 -0
  258. package/skills/dws/dingtalk-calendar/scripts/calendar_schedule_meeting.py +233 -0
  259. package/skills/dws/dingtalk-calendar/scripts/calendar_today_agenda.py +139 -0
  260. package/skills/dws/dingtalk-chat/SKILL.md +127 -0
  261. package/skills/dws/dingtalk-chat/references/01-messaging.md +103 -0
  262. package/skills/dws/dingtalk-chat/references/card/callback.md +10 -0
  263. package/skills/dws/dingtalk-chat/references/card/create.md +27 -0
  264. package/skills/dws/dingtalk-chat/references/card/schema.md +14 -0
  265. package/skills/dws/dingtalk-chat/references/card/update.md +13 -0
  266. package/skills/dws/dingtalk-chat/references/chat/chat-bot.md +187 -0
  267. package/skills/dws/dingtalk-chat/references/chat/chat-conversation.md +165 -0
  268. package/skills/dws/dingtalk-chat/references/chat/group-admin.md +144 -0
  269. package/skills/dws/dingtalk-chat/references/chat/group-discovery.md +121 -0
  270. package/skills/dws/dingtalk-chat/references/chat/message-actions.md +132 -0
  271. package/skills/dws/dingtalk-chat/references/chat/message-media.md +76 -0
  272. package/skills/dws/dingtalk-chat/references/chat/message-query.md +135 -0
  273. package/skills/dws/dingtalk-chat/references/chat/thread.md +46 -0
  274. package/skills/dws/dingtalk-chat/references/chat-emoji-list.md +216 -0
  275. package/skills/dws/dingtalk-chat/references/chat.md +140 -0
  276. package/skills/dws/dingtalk-chat/references/contracts.md +70 -0
  277. package/skills/dws/dingtalk-chat/references/intent-guide.md +56 -0
  278. package/skills/dws/dingtalk-contact/SKILL.md +122 -0
  279. package/skills/dws/dingtalk-contact/references/08-directory.md +51 -0
  280. package/skills/dws/dingtalk-contact/references/contact.md +595 -0
  281. package/skills/dws/dingtalk-contact/references/intent-guide.md +14 -0
  282. package/skills/dws/dingtalk-contact/references/lite-recipes.md +30 -0
  283. package/skills/dws/dingtalk-contact/references/recipes/conventions.md +44 -0
  284. package/skills/dws/dingtalk-contact/scripts/contact_dept_members.py +130 -0
  285. package/skills/dws/dingtalk-doc/SKILL.md +112 -0
  286. package/skills/dws/dingtalk-doc/references/04-document.md +57 -0
  287. package/skills/dws/dingtalk-doc/references/contracts.md +40 -0
  288. package/skills/dws/dingtalk-doc/references/doc/doc-block.md +39 -0
  289. package/skills/dws/dingtalk-doc/references/doc/doc-comment.md +29 -0
  290. package/skills/dws/dingtalk-doc/references/doc/doc-create.md +54 -0
  291. package/skills/dws/dingtalk-doc/references/doc/doc-export.md +30 -0
  292. package/skills/dws/dingtalk-doc/references/doc/doc-import.md +28 -0
  293. package/skills/dws/dingtalk-doc/references/doc/doc-info.md +12 -0
  294. package/skills/dws/dingtalk-doc/references/doc/doc-media.md +46 -0
  295. package/skills/dws/dingtalk-doc/references/doc/doc-read.md +56 -0
  296. package/skills/dws/dingtalk-doc/references/doc/doc-update.md +87 -0
  297. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-cookbook.md +469 -0
  298. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.json +526 -0
  299. package/skills/dws/dingtalk-doc/references/doc/format/doc-jsonml-schema.md +711 -0
  300. package/skills/dws/dingtalk-doc/references/doc/style/doc-create-workflow.md +414 -0
  301. package/skills/dws/dingtalk-doc/references/doc/style/doc-style-guideline.md +404 -0
  302. package/skills/dws/dingtalk-doc/references/doc/style/doc-update-workflow.md +316 -0
  303. package/skills/dws/dingtalk-doc/references/doc.md +82 -0
  304. package/skills/dws/dingtalk-doc/references/intent-guide.md +20 -0
  305. package/skills/dws/dingtalk-drive/SKILL.md +128 -0
  306. package/skills/dws/dingtalk-drive/references/drive.md +126 -0
  307. package/skills/dws/dingtalk-drive/references/folder-sync.md +40 -0
  308. package/skills/dws/dingtalk-drive/references/intent-guide.md +22 -0
  309. package/skills/dws/dingtalk-drive/references/lite-recipes.md +39 -0
  310. package/skills/dws/dingtalk-event/SKILL.md +106 -0
  311. package/skills/dws/dingtalk-event/references/event-im-keys.md +54 -0
  312. package/skills/dws/dingtalk-event/references/event-im-lifecycle.md +35 -0
  313. package/skills/dws/dingtalk-event/references/event-im-operations.md +52 -0
  314. package/skills/dws/dingtalk-event/references/event-im-output.md +39 -0
  315. package/skills/dws/dingtalk-event/references/event-im.md +58 -0
  316. package/skills/dws/dingtalk-event/references/event-oa.md +155 -0
  317. package/skills/dws/dingtalk-event/references/event-todo.md +50 -0
  318. package/skills/dws/dingtalk-event/references/event-voip.md +69 -0
  319. package/skills/dws/dingtalk-mail/SKILL.md +113 -0
  320. package/skills/dws/dingtalk-mail/references/09-mail.md +45 -0
  321. package/skills/dws/dingtalk-mail/references/intent-guide.md +8 -0
  322. package/skills/dws/dingtalk-mail/references/lite-recipes.md +45 -0
  323. package/skills/dws/dingtalk-mail/references/mail.md +1889 -0
  324. package/skills/dws/dingtalk-mail/references/recipes/conventions.md +44 -0
  325. package/skills/dws/dingtalk-mail/scripts/mail_send_with_cc.py +122 -0
  326. package/skills/dws/dingtalk-mail/scripts/mail_unread_summary.py +114 -0
  327. package/skills/dws/dingtalk-minutes/SKILL.md +116 -0
  328. package/skills/dws/dingtalk-minutes/references/07-minutes.md +149 -0
  329. package/skills/dws/dingtalk-minutes/references/10-minutes-speaker-match.md +32 -0
  330. package/skills/dws/dingtalk-minutes/references/11-minutes-speaker-correct.md +40 -0
  331. package/skills/dws/dingtalk-minutes/references/intent-guide.md +79 -0
  332. package/skills/dws/dingtalk-minutes/references/lite-recipes.md +25 -0
  333. package/skills/dws/dingtalk-minutes/references/minutes.md +210 -0
  334. package/skills/dws/dingtalk-minutes/references/recipes/conventions.md +33 -0
  335. package/skills/dws/dingtalk-minutes/scripts/minutes_recent_summary.py +196 -0
  336. package/skills/dws/dingtalk-minutes/scripts/test_minutes_scripts.py +222 -0
  337. package/skills/dws/dingtalk-misc/SKILL.md +55 -0
  338. package/skills/dws/dingtalk-misc/references/05-reporting.md +29 -0
  339. package/skills/dws/dingtalk-misc/references/agoal.md +302 -0
  340. package/skills/dws/dingtalk-misc/references/attendance-intent-guide.md +7 -0
  341. package/skills/dws/dingtalk-misc/references/attendance-report.md +109 -0
  342. package/skills/dws/dingtalk-misc/references/attendance-schedule.md +115 -0
  343. package/skills/dws/dingtalk-misc/references/attendance-vacation.md +129 -0
  344. package/skills/dws/dingtalk-misc/references/attendance.md +149 -0
  345. package/skills/dws/dingtalk-misc/references/dev/app.md +42 -0
  346. package/skills/dws/dingtalk-misc/references/dev/connect.md +108 -0
  347. package/skills/dws/dingtalk-misc/references/dev/credentials.md +26 -0
  348. package/skills/dws/dingtalk-misc/references/dev/dev-index.md +13 -0
  349. package/skills/dws/dingtalk-misc/references/dev/event.md +47 -0
  350. package/skills/dws/dingtalk-misc/references/dev/mcp.md +106 -0
  351. package/skills/dws/dingtalk-misc/references/dev/member.md +19 -0
  352. package/skills/dws/dingtalk-misc/references/dev/permission.md +56 -0
  353. package/skills/dws/dingtalk-misc/references/dev/recipes.md +58 -0
  354. package/skills/dws/dingtalk-misc/references/dev/robot.md +74 -0
  355. package/skills/dws/dingtalk-misc/references/dev/security.md +21 -0
  356. package/skills/dws/dingtalk-misc/references/dev/version.md +99 -0
  357. package/skills/dws/dingtalk-misc/references/dev/webapp.md +22 -0
  358. package/skills/dws/dingtalk-misc/references/devapp.md +148 -0
  359. package/skills/dws/dingtalk-misc/references/devdoc-intent-guide.md +7 -0
  360. package/skills/dws/dingtalk-misc/references/devdoc.md +42 -0
  361. package/skills/dws/dingtalk-misc/references/ding.md +184 -0
  362. package/skills/dws/dingtalk-misc/references/hrbrain.md +67 -0
  363. package/skills/dws/dingtalk-misc/references/live.md +23 -0
  364. package/skills/dws/dingtalk-misc/references/markdown.md +167 -0
  365. package/skills/dws/dingtalk-misc/references/oa/oa-form-components.md +345 -0
  366. package/skills/dws/dingtalk-misc/references/oa/oa-process-nodes.md +374 -0
  367. package/skills/dws/dingtalk-misc/references/oa.md +1014 -0
  368. package/skills/dws/dingtalk-misc/references/openapi-explorer.md +47 -0
  369. package/skills/dws/dingtalk-misc/references/pat.md +52 -0
  370. package/skills/dws/dingtalk-misc/references/profile.md +41 -0
  371. package/skills/dws/dingtalk-misc/references/recruit.md +89 -0
  372. package/skills/dws/dingtalk-misc/references/report-conventions.md +44 -0
  373. package/skills/dws/dingtalk-misc/references/report-intent-guide.md +11 -0
  374. package/skills/dws/dingtalk-misc/references/report-lite-recipes.md +25 -0
  375. package/skills/dws/dingtalk-misc/references/report.md +109 -0
  376. package/skills/dws/dingtalk-misc/references/sheet/sheet-batch-operations.md +104 -0
  377. package/skills/dws/dingtalk-misc/references/sheet/sheet-chart.md +69 -0
  378. package/skills/dws/dingtalk-misc/references/sheet/sheet-comment.md +166 -0
  379. package/skills/dws/dingtalk-misc/references/sheet/sheet-conditional-format.md +59 -0
  380. package/skills/dws/dingtalk-misc/references/sheet/sheet-dimension-operations.md +60 -0
  381. package/skills/dws/dingtalk-misc/references/sheet/sheet-dropdown.md +103 -0
  382. package/skills/dws/dingtalk-misc/references/sheet/sheet-export.md +135 -0
  383. package/skills/dws/dingtalk-misc/references/sheet/sheet-filter-view.md +51 -0
  384. package/skills/dws/dingtalk-misc/references/sheet/sheet-filter.md +181 -0
  385. package/skills/dws/dingtalk-misc/references/sheet/sheet-formula.md +192 -0
  386. package/skills/dws/dingtalk-misc/references/sheet/sheet-import.md +76 -0
  387. package/skills/dws/dingtalk-misc/references/sheet/sheet-media-image.md +263 -0
  388. package/skills/dws/dingtalk-misc/references/sheet/sheet-pivot-table.md +292 -0
  389. package/skills/dws/dingtalk-misc/references/sheet/sheet-range-operations.md +161 -0
  390. package/skills/dws/dingtalk-misc/references/sheet/sheet-read-data.md +60 -0
  391. package/skills/dws/dingtalk-misc/references/sheet/sheet-revision-changeset.md +46 -0
  392. package/skills/dws/dingtalk-misc/references/sheet/sheet-search-replace.md +120 -0
  393. package/skills/dws/dingtalk-misc/references/sheet/sheet-style-format.md +71 -0
  394. package/skills/dws/dingtalk-misc/references/sheet/sheet-version.md +84 -0
  395. package/skills/dws/dingtalk-misc/references/sheet/sheet-workbook.md +59 -0
  396. package/skills/dws/dingtalk-misc/references/sheet/sheet-write-data.md +93 -0
  397. package/skills/dws/dingtalk-misc/references/sheet-conventions.md +44 -0
  398. package/skills/dws/dingtalk-misc/references/sheet-intent-guide.md +12 -0
  399. package/skills/dws/dingtalk-misc/references/sheet.md +166 -0
  400. package/skills/dws/dingtalk-misc/references/skill.md +98 -0
  401. package/skills/dws/dingtalk-misc/references/unsupported-scripts.md +49 -0
  402. package/skills/dws/dingtalk-misc/references/url-patterns.md +144 -0
  403. package/skills/dws/dingtalk-misc/references/whiteboard/compose.md +200 -0
  404. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/01-overview.md +57 -0
  405. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/02-query.md +288 -0
  406. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/03-update.md +249 -0
  407. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/04-text-style.md +395 -0
  408. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/05-shape-frame-group-connector.md +186 -0
  409. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/06-vector-icon-path.md +313 -0
  410. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/07-examples-errors-write-support.md +270 -0
  411. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1/08-catalogs.md +61 -0
  412. package/skills/dws/dingtalk-misc/references/whiteboard/open-nodes-v1.md +54 -0
  413. package/skills/dws/dingtalk-misc/references/whiteboard/replace.md +41 -0
  414. package/skills/dws/dingtalk-misc/references/whiteboard/vector.md +51 -0
  415. package/skills/dws/dingtalk-misc/references/whiteboard.md +115 -0
  416. package/skills/dws/dingtalk-misc/scripts/aiapp_create_and_poll.py +138 -0
  417. package/skills/dws/dingtalk-misc/scripts/attendance_my_record.py +91 -0
  418. package/skills/dws/dingtalk-misc/scripts/attendance_report_checkin.py +440 -0
  419. package/skills/dws/dingtalk-misc/scripts/attendance_report_common.py +1698 -0
  420. package/skills/dws/dingtalk-misc/scripts/attendance_report_daily.py +558 -0
  421. package/skills/dws/dingtalk-misc/scripts/attendance_report_detail.py +809 -0
  422. package/skills/dws/dingtalk-misc/scripts/attendance_report_monthly.py +758 -0
  423. package/skills/dws/dingtalk-misc/scripts/attendance_report_record.py +947 -0
  424. package/skills/dws/dingtalk-misc/scripts/attendance_schedule_export.py +344 -0
  425. package/skills/dws/dingtalk-misc/scripts/attendance_schedule_import.py +498 -0
  426. package/skills/dws/dingtalk-misc/scripts/attendance_team_shift.py +89 -0
  427. package/skills/dws/dingtalk-misc/scripts/attendance_vacation_balance.py +617 -0
  428. package/skills/dws/dingtalk-misc/scripts/finance_daily_cashflow.py +70 -0
  429. package/skills/dws/dingtalk-misc/scripts/finance_expense_flow.py +138 -0
  430. package/skills/dws/dingtalk-misc/scripts/oa_batch_approve.py +158 -0
  431. package/skills/dws/dingtalk-misc/scripts/oa_pending_review.py +132 -0
  432. package/skills/dws/dingtalk-misc/scripts/report_received_today.py +315 -0
  433. package/skills/dws/dingtalk-misc/scripts/yida_custom_page_update.py +401 -0
  434. package/skills/dws/dingtalk-misc/scripts/yida_form_builder.py +526 -0
  435. package/skills/dws/dingtalk-misc/scripts/yida_form_fields.py +315 -0
  436. package/skills/dws/dingtalk-misc/scripts/yida_form_inspector.py +311 -0
  437. package/skills/dws/dingtalk-misc/scripts/yida_form_update.py +185 -0
  438. package/skills/dws/dingtalk-misc/scripts/yida_jsx_pipeline.py +1588 -0
  439. package/skills/dws/dingtalk-misc/scripts/yida_page_compiler.py +1441 -0
  440. package/skills/dws/dingtalk-misc/scripts/yida_page_generate.py +514 -0
  441. package/skills/dws/dingtalk-misc/scripts/yida_page_schema.py +519 -0
  442. package/skills/dws/dingtalk-misc/scripts/yida_page_self_check.py +398 -0
  443. package/skills/dws/dingtalk-misc/scripts/yida_process_flow.py +1478 -0
  444. package/skills/dws/dingtalk-misc/scripts/yida_process_update.py +589 -0
  445. package/skills/dws/dingtalk-misc/scripts/yida_report_builder.py +494 -0
  446. package/skills/dws/dingtalk-misc/scripts/yida_report_charts.py +1434 -0
  447. package/skills/dws/dingtalk-misc/scripts/yida_report_update.py +244 -0
  448. package/skills/dws/dingtalk-misc/scripts/yida_schema_common.py +340 -0
  449. package/skills/dws/dingtalk-shared/SKILL.md +83 -0
  450. package/skills/dws/dingtalk-shared/references/capability-limits.md +31 -0
  451. package/skills/dws/dingtalk-shared/references/channel-login.md +26 -0
  452. package/skills/dws/dingtalk-shared/references/confirmation.md +59 -0
  453. package/skills/dws/dingtalk-shared/references/error-codes.md +155 -0
  454. package/skills/dws/dingtalk-shared/references/global-reference.md +122 -0
  455. package/skills/dws/dingtalk-shared/references/intent-guide.md +537 -0
  456. package/skills/dws/dingtalk-shared/references/recipes/conventions.md +45 -0
  457. package/skills/dws/dingtalk-shared/references/recipes/lite-catalog.md +275 -0
  458. package/skills/dws/dingtalk-shared/references/recipes/meta.md +67 -0
  459. package/skills/dws/dingtalk-shared/references/routing.md +52 -0
  460. package/skills/dws/dingtalk-shared/references/runtime-contract.md +11 -0
  461. package/skills/dws/dingtalk-shared/references/schema-usage.md +88 -0
  462. package/skills/dws/dingtalk-shared/references/url-patterns.md +144 -0
  463. package/skills/dws/dingtalk-shared/references/workflow-routing.md +45 -0
  464. package/skills/dws/dingtalk-todo/SKILL.md +119 -0
  465. package/skills/dws/dingtalk-todo/references/02-task.md +70 -0
  466. package/skills/dws/dingtalk-todo/references/intent-guide.md +15 -0
  467. package/skills/dws/dingtalk-todo/references/lite-recipes.md +68 -0
  468. package/skills/dws/dingtalk-todo/references/recipes/conventions.md +44 -0
  469. package/skills/dws/dingtalk-todo/references/todo.md +517 -0
  470. package/skills/dws/dingtalk-todo/scripts/todo_batch_create.py +354 -0
  471. package/skills/dws/dingtalk-todo/scripts/todo_daily_summary.py +153 -0
  472. package/skills/dws/dingtalk-todo/scripts/todo_overdue_check.py +101 -0
  473. package/skills/dws/dingtalk-wiki/SKILL.md +117 -0
  474. package/skills/dws/dingtalk-wiki/references/intent-guide.md +14 -0
  475. package/skills/dws/dingtalk-wiki/references/lite-recipes.md +44 -0
  476. package/skills/dws/dingtalk-wiki/references/wiki-members.md +32 -0
  477. package/skills/dws/dingtalk-wiki/references/wiki-node-ops.md +46 -0
  478. package/skills/dws/dingtalk-wiki/references/wiki.md +65 -0
  479. package/skills/dws/provenance.json +27 -0
@@ -0,0 +1,855 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import copy
5
+ import json
6
+ import re
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ SCRIPT_DIR = Path(__file__).resolve().parent
13
+ sys.path.insert(0, str(SCRIPT_DIR))
14
+
15
+ import gap_analysis_delivery as delivery
16
+
17
+ SAMPLE = SCRIPT_DIR / "examples" / "gap-analysis.sample.json"
18
+ TEMPLATE = SCRIPT_DIR.parent / "template" / "gap-analysis-report.html"
19
+
20
+ # 源仓契约测试:据本文件位置上溯定位**技能层**,只读核对同级 maintainer 技能的权威正文(不硬编码仓库布局)。
21
+ # 运行时不需要本测试;已安装副本内缺少同级技能时显式 skip(不静默通过)。
22
+ SKILLS_LAYER = SCRIPT_DIR.parents[1] # skills/<层>/
23
+ MAINTAINER_SKILL = SKILLS_LAYER / "crwu-dev-audit-skill-maintainer"
24
+ MAINTAINER_SKILL_NAME = "crwu-dev-audit-skill-maintainer"
25
+ GATE_OWNER_FILE = "04-registry-and-mapping-update.md"
26
+ VALIDATION_OWNER_FILE = "05-validation-and-delivery.md"
27
+ # F-021:一级资产/业务 classification 与 registry 的三份 owner 文件(唯一 owner = maintainer)。
28
+ CLASSIFICATION_FILES = (
29
+ "03-asset-classification.md",
30
+ "04-business-classification.md",
31
+ "07-skill-registry.md",
32
+ )
33
+ # optimize 侧四个文档:共享门禁/校验口径一律按名回指,不得各自再定义一份。
34
+ OPTIMIZE_CONTRACT_DOCS = (
35
+ "SKILL.md",
36
+ "references/00-优化规范与文件落点.md",
37
+ "references/01-反馈定位与画像流程.md",
38
+ "references/02-方案模板与确认门禁.md",
39
+ )
40
+ # 共享校验命令的**命令行形态**(只禁活跃命令,不禁 `validate` 单词与普通回指)。
41
+ _SHARED_COMMAND_RE = re.compile(r"^(?:\$ )?python3\b")
42
+ _SHARED_TOOL_RE = re.compile(r"kb_tool\.py|check_audit_skill_mappings\.py")
43
+ _SHARED_PATH_RE = re.compile(r"\$SKILLS_ROOT[^\s`]*/(?:kb_tool\.py|check_audit_skill_mappings\.py)")
44
+ # maintainer 侧短名(`references/04` / `references/05`,后面不接 `-`)—— 完整文件名不算。
45
+ _SHORT_OWNER_RE = re.compile(r"references/0[45](?!-)")
46
+
47
+
48
+ def _fenced_template(text: str, heading_prefix: str) -> str:
49
+ """取 `heading_prefix` 之后第一个 fenced 代码块的正文。
50
+
51
+ 模板里本身含 `## 1. …` 这类子标题,所以**不能**按"下一个 H2"截断,必须按围栏取。
52
+ """
53
+ lines = text.splitlines()
54
+ start = next((i for i, line in enumerate(lines) if line.startswith(heading_prefix)), None)
55
+ if start is None:
56
+ return ""
57
+ fence = next((i for i in range(start, len(lines)) if lines[i].startswith("```")), None)
58
+ if fence is None:
59
+ return ""
60
+ end = next((i for i in range(fence + 1, len(lines)) if lines[i].startswith("```")), None)
61
+ return "\n".join(lines[fence + 1:end if end is not None else len(lines)])
62
+
63
+ # F-016/F-018:入口只留边界、模式、门禁与导航;详细流程按**完整文件名**回指四个 reference。
64
+ ENTRY_REFERENCE_NAMES = (
65
+ "00-优化规范与文件落点.md",
66
+ "01-反馈定位与画像流程.md",
67
+ "02-方案模板与确认门禁.md",
68
+ "03-AI人工差距分析流程.md",
69
+ )
70
+ # 详细实现标识 → 唯一详细落点(不得留在入口,必须存在于该 reference)。
71
+ DETAIL_TOKEN_OWNERS = (
72
+ ("sourceGapIds[]", "03-AI人工差距分析流程.md"),
73
+ ("python3 scripts/gap_analysis_delivery.py validate", "03-AI人工差距分析流程.md"),
74
+ ("BG8169 / 300673", "00-优化规范与文件落点.md"),
75
+ ("两跑导出", "00-优化规范与文件落点.md"),
76
+ ("八层追踪", "03-AI人工差距分析流程.md"),
77
+ ("画像五维提取", "01-反馈定位与画像流程.md"),
78
+ )
79
+ # 安全与授权门禁必须**直接**留在入口。
80
+ ENTRY_GATE_TOKENS = (
81
+ "不产出任何审核意见",
82
+ "先方案、后动文件",
83
+ "用户明确确认",
84
+ "知识库只读",
85
+ "manual_only",
86
+ "禁止修改知识库",
87
+ "FIX-*",
88
+ "crwu-dev-audit-skill-maintainer",
89
+ "AI—人工差距分析模式",
90
+ "常规反馈优化模式",
91
+ "受控执行模式",
92
+ # OPT-012(F-031):Python 运行时口径已收归 maintainer SKILL.md 单一 owner,入口改为按名回指;
93
+ # `load_workspace_dependencies` / 裸 `python3` / 静默降级到系统解释器 三条不再要求出现在**入口**,
94
+ # 改由 test_p3_document_debt_is_closed_and_python_runtime_has_one_owner 对 **owner 正文**逐条严格断言。
95
+ )
96
+ # A–E 五桶及执行归属必须仍可从入口识别。
97
+ ENTRY_BUCKET_TOKENS = (
98
+ "A 规则/清单内容",
99
+ "B 覆盖缺失",
100
+ "C 词表/画像取值",
101
+ "D 技能算法与流程",
102
+ "E 路径/装配/指针漂移",
103
+ "知识库人工修复",
104
+ "知识库部分人工修复",
105
+ "交接 maintainer",
106
+ )
107
+ # OPT-012(F-031):Python 运行时的**专属语义**。同一份 token 集同时用作
108
+ # 「owner 必须逐条具备」与「consumer 必须逐条不出现」,避免只禁少数短语时被**部分复述**绕过。
109
+ PYTHON_RUNTIME_OWNER_TOKENS = (
110
+ "DSH 自带的 Python",
111
+ "load_workspace_dependencies",
112
+ "`python` 字段",
113
+ "调用**一次**并在同一次会话内复用",
114
+ "裸 `python3`",
115
+ "任何解释器查找",
116
+ "静默降级到系统解释器",
117
+ "不安装 Python",
118
+ "不执行 pip",
119
+ "`python3 scripts/<文件>`",
120
+ )
121
+ # consumer 侧必须**显式具备**的指针与失败门禁语义(缺一即失败)。
122
+ PYTHON_RUNTIME_CONSUMER_REQUIRED = (
123
+ "按名回指",
124
+ "不复述",
125
+ "capability gap",
126
+ "停止",
127
+ )
128
+ PYTHON_RUNTIME_CONSUMER_UNIQUE_RE = re.compile(r"唯一维护|唯一 owner|唯一事实源")
129
+
130
+
131
+ def load_sample() -> dict:
132
+ return json.loads(SAMPLE.read_text(encoding="utf-8"))
133
+
134
+
135
+ class GapAnalysisValidationTest(unittest.TestCase):
136
+ def test_sample_passes(self):
137
+ self.assertEqual([], delivery.validate(load_sample()))
138
+
139
+ def test_every_actionable_gap_has_a_fix(self):
140
+ result = load_sample()
141
+ result["reviewerOnlyItems"][0]["linkedFixIds"] = []
142
+ errors = delivery.validate(result)
143
+ self.assertTrue(any("linkedFixIds" in item for item in errors), errors)
144
+
145
+ def test_every_fix_points_back_to_a_gap(self):
146
+ result = load_sample()
147
+ result["repairPlans"][0]["sourceGapIds"] = []
148
+ errors = delivery.validate(result)
149
+ self.assertTrue(any("sourceGapIds" in item for item in errors), errors)
150
+
151
+ def test_non_actionable_gap_cannot_enter_repair_plan(self):
152
+ result = load_sample()
153
+ result["repairPlans"][0]["sourceGapIds"].append("L-03")
154
+ errors = delivery.validate(result)
155
+ self.assertTrue(any("L-resolved" in item for item in errors), errors)
156
+
157
+ def test_kb_plan_is_manual_only(self):
158
+ result = load_sample()
159
+ kb = next(item for item in result["repairPlans"] if item["type"] == "kb_manual")
160
+ kb["executionMode"] = "ai"
161
+ errors = delivery.validate(result)
162
+ self.assertTrue(any("manual_only" in item for item in errors), errors)
163
+
164
+ def test_duplicate_gap_id_is_rejected(self):
165
+ result = load_sample()
166
+ result["reviewerOnlyItems"].append(copy.deepcopy(result["reviewerOnlyItems"][0]))
167
+ result["summary"]["reviewerOnly"] += 1
168
+ result["summary"]["actionableMisses"] += 1
169
+ errors = delivery.validate(result)
170
+ self.assertTrue(any("重复" in item for item in errors), errors)
171
+
172
+ def test_absolute_paths_are_rejected(self):
173
+ result = load_sample()
174
+ result["project"]["auditResult"] = "/Users/example/审核意见.json"
175
+ errors = delivery.validate(result)
176
+ self.assertTrue(any("绝对路径" in item for item in errors), errors)
177
+
178
+
179
+ class GapAnalysisRenderTest(unittest.TestCase):
180
+ def setUp(self):
181
+ self.result = load_sample()
182
+
183
+ def test_is_single_file_and_offline(self):
184
+ document = delivery.render(self.result)
185
+ for token in ("<link", "script src", "@import"):
186
+ self.assertNotIn(token, document)
187
+ self.assertIn("echarts.init", document)
188
+
189
+ def test_required_sections_exist(self):
190
+ document = delivery.render(self.result)
191
+ for region in (
192
+ "gap-summary",
193
+ "gap-charts",
194
+ "reviewer-only-table",
195
+ "execution-trace",
196
+ "gap-fix-map",
197
+ "manual-kb-plans",
198
+ "skill-repair-prompts",
199
+ "validation-plan",
200
+ ):
201
+ self.assertIn(f'id="{region}"', document)
202
+
203
+ def test_a4_and_table_print_contract(self):
204
+ document = delivery.render(self.result)
205
+ self.assertIn("@page { size: A4", document)
206
+ self.assertIn("thead { display: table-header-group; }", document)
207
+ self.assertIn("break-inside: avoid", document)
208
+
209
+ def test_embedded_payload_is_parseable(self):
210
+ document = delivery.render(self.result)
211
+ match = re.search(
212
+ r'<script id="gap-analysis-data" type="application/json">(.*?)</script>',
213
+ document,
214
+ re.S,
215
+ )
216
+ self.assertIsNotNone(match)
217
+ self.assertEqual(self.result["reportId"], json.loads(match.group(1))["reportId"])
218
+
219
+ def test_manual_and_skill_outputs_are_separated(self):
220
+ kb = next(item for item in self.result["repairPlans"] if item["type"] == "kb_manual")
221
+ manual = delivery.build_manual_kb_instruction(kb)
222
+ prompt = delivery.build_skill_prompt(self.result, ["FIX-SKILL-01"])
223
+ self.assertIn("FIX-KB-01", manual)
224
+ self.assertIn("仅人工", manual)
225
+ self.assertIn("FIX-SKILL-01", prompt)
226
+ self.assertIn("crwu-dws 只读", prompt)
227
+ self.assertIn("不得对知识库执行任何写操作", prompt)
228
+ self.assertNotIn("修改知识库", prompt)
229
+
230
+ def test_unapproved_skill_plan_is_rejected(self):
231
+ self.result["repairPlans"][1]["approvalState"] = "pending"
232
+ with self.assertRaisesRegex(ValueError, "未批准"):
233
+ delivery.build_skill_prompt(self.result, ["FIX-SKILL-01"])
234
+
235
+ def test_root_cause_chart_excludes_non_actionable_items(self):
236
+ self.assertEqual({"K2": 1, "S2": 1}, delivery.actionable_cause_counts(self.result))
237
+
238
+ def test_documented_cli_flags_validate_and_render(self):
239
+ self.assertEqual(0, delivery.main(["validate", "--input", str(SAMPLE)]))
240
+ with tempfile.TemporaryDirectory() as temp_dir:
241
+ output = Path(temp_dir) / "report.html"
242
+ self.assertEqual(
243
+ 0,
244
+ delivery.main(
245
+ ["render", "--input", str(SAMPLE), "--output", str(output)]
246
+ ),
247
+ )
248
+ self.assertIn("AI—人工审核差距分析", output.read_text(encoding="utf-8"))
249
+
250
+
251
+ class SkillIntegrationTest(unittest.TestCase):
252
+ def test_skill_is_a_concise_progressive_disclosure_entry(self):
253
+ """F-016/F-018:入口只留边界、模式、门禁与导航;详细流程按完整文件名回指四个 reference。"""
254
+ skill_dir = SCRIPT_DIR.parent
255
+ entry = (skill_dir / "SKILL.md").read_text(encoding="utf-8")
256
+ references_root = skill_dir / "references"
257
+
258
+ # 1) 入口尺寸上限。
259
+ line_count = len(entry.splitlines())
260
+ self.assertLessEqual(line_count, 100, f"入口过长:{line_count} 行(上限 100)")
261
+ byte_size = len(entry.encode("utf-8"))
262
+ self.assertLessEqual(byte_size, 10000, f"入口过大:{byte_size} bytes(上限 10000)")
263
+
264
+ # 2) 四个完整文件名必须逐字出现(短名导航不可发现)。
265
+ for name in ENTRY_REFERENCE_NAMES:
266
+ self.assertIn(f"references/{name}", entry, f"入口必须逐字链接 references/{name}")
267
+
268
+ # 3) reference 集合必须恰为四者:无新增、无缺失。
269
+ actual = {path.name for path in references_root.glob("*.md")}
270
+ self.assertEqual(set(ENTRY_REFERENCE_NAMES), actual, f"reference 集合不符:{sorted(actual)}")
271
+
272
+ # 4) 详细实现标识只在指定 reference。
273
+ for token, reference_name in DETAIL_TOKEN_OWNERS:
274
+ self.assertNotIn(token, entry, f"{token} 属详细流程,应只出现在 references/{reference_name}")
275
+ self.assertIn(
276
+ token, (references_root / reference_name).read_text(encoding="utf-8"),
277
+ f"references/{reference_name} 必须承载 {token}",
278
+ )
279
+
280
+ # 5) 安全与授权门禁必须直接留在入口。
281
+ for token in ENTRY_GATE_TOKENS:
282
+ self.assertIn(token, entry, f"入口必须直接保留门禁标识:{token}")
283
+
284
+ # 6) A–E 五桶及执行归属必须仍可从入口识别。
285
+ for token in ENTRY_BUCKET_TOKENS:
286
+ self.assertIn(token, entry, f"入口必须保留根因桶/归属标识:{token}")
287
+
288
+ def test_skill_routes_gap_analysis_mode(self):
289
+ skill = (SCRIPT_DIR.parent / "SKILL.md").read_text(encoding="utf-8")
290
+ self.assertIn("AI—人工差距分析模式", skill)
291
+ self.assertIn("references/03-AI人工差距分析流程.md", skill)
292
+
293
+ def test_skill_keeps_knowledge_base_manual_only(self):
294
+ files = [
295
+ SCRIPT_DIR.parent / "SKILL.md",
296
+ SCRIPT_DIR.parent / "references" / "03-AI人工差距分析流程.md",
297
+ ]
298
+ text = "\n".join(path.read_text(encoding="utf-8") for path in files if path.is_file())
299
+ self.assertIn("manual_only", text)
300
+ self.assertIn("禁止修改知识库", text)
301
+
302
+ def test_gap_reference_defines_bidirectional_traceability(self):
303
+ reference = SCRIPT_DIR.parent / "references" / "03-AI人工差距分析流程.md"
304
+ text = reference.read_text(encoding="utf-8")
305
+ self.assertIn("sourceGapIds", text)
306
+ self.assertIn("linkedFixIds", text)
307
+ self.assertIn("L-open", text)
308
+ self.assertIn("L-resolved", text)
309
+
310
+ def test_optimize_defers_shared_gate_and_validation_contracts_to_maintainer(self):
311
+ """F-020:共享门禁与校验口径只在 maintainer `04`/`05` 定义一次;optimize 只按完整文件名回指。"""
312
+ docs = {
313
+ name: (SCRIPT_DIR.parent / name).read_text(encoding="utf-8")
314
+ for name in OPTIMIZE_CONTRACT_DOCS
315
+ }
316
+ problems = []
317
+
318
+ # 1) 入口必须写出两个完整 owner 文件名(技能名 + 完整文件名,不用短名)。
319
+ for token in (GATE_OWNER_FILE, VALIDATION_OWNER_FILE):
320
+ if token not in docs["SKILL.md"]:
321
+ problems.append(f"SKILL.md: 缺少完整 owner 文件名「{token}」")
322
+ # 2) references/00 同样必须写出两个完整文件名。
323
+ for token in (GATE_OWNER_FILE, VALIDATION_OWNER_FILE):
324
+ if token not in docs["references/00-优化规范与文件落点.md"]:
325
+ problems.append(f"references/00: 缺少完整 owner 文件名「{token}」")
326
+ # 3) references/01 必须回指 05,并声明命令签名不在本文件重复。
327
+ locate = "references/01-反馈定位与画像流程.md"
328
+ if VALIDATION_OWNER_FILE not in docs[locate]:
329
+ problems.append(f"{locate}: 缺少完整 owner 文件名「{VALIDATION_OWNER_FILE}」")
330
+ for token in ("不复制", "命令"):
331
+ if token not in docs[locate]:
332
+ problems.append(f"{locate}: 必须声明命令签名不在本文件重复(缺「{token}」)")
333
+ # 4) references/02 必须写出两个完整文件名,且不得只写短名。
334
+ template = "references/02-方案模板与确认门禁.md"
335
+ for token in (GATE_OWNER_FILE, VALIDATION_OWNER_FILE):
336
+ if token not in docs[template]:
337
+ problems.append(f"{template}: 缺少完整 owner 文件名「{token}」")
338
+ for match in _SHORT_OWNER_RE.finditer(docs[template]):
339
+ problems.append(
340
+ f"{template}: 仍用短名回指「{match.group(0)}」——必须写完整 owner 文件名"
341
+ )
342
+ # 技能名必须与 owner 文件名同时出现,回指才无歧义。
343
+ for name, text in docs.items():
344
+ if MAINTAINER_SKILL_NAME not in text:
345
+ problems.append(f"{name}: 回指未写明 owner 技能名「{MAINTAINER_SKILL_NAME}」")
346
+
347
+ # 5) 不得保留活跃的共享校验命令行(按命令行形态判定,不禁 `validate` 单词)。
348
+ for name, text in docs.items():
349
+ for line_no, line in enumerate(text.splitlines(), 1):
350
+ stripped = line.strip()
351
+ active = (
352
+ _SHARED_COMMAND_RE.match(stripped) and _SHARED_TOOL_RE.search(line)
353
+ ) or _SHARED_PATH_RE.search(line)
354
+ if active:
355
+ problems.append(
356
+ f"{name}:{line_no}: 仍有活跃的共享校验命令/签名(应只回指 {VALIDATION_OWNER_FILE}):"
357
+ f"{stripped[:88]}"
358
+ )
359
+
360
+ # 6) references/00 不得继续定义第二份验收基线与通过判据。
361
+ spec = "references/00-优化规范与文件落点.md"
362
+ for token, why in (
363
+ ("## 4. 验收基线", "共享验收基线章节"),
364
+ ("通过判据:", "第二份通过判据"),
365
+ ):
366
+ if token in docs[spec]:
367
+ problems.append(f"{spec}: 不得继续维护{why}「{token}」")
368
+ # 7) references/02 不得保留共享 owner 形态的章节。
369
+ for token in ("## 2. 确认门禁", "## 3. 执行后核对清单"):
370
+ if token in docs[template]:
371
+ problems.append(f"{template}: 不得保留共享 owner 形态章节「{token}」")
372
+
373
+ # 8) optimize 特有安全语义不得被去重误删。
374
+ combined = "\n".join(docs.values())
375
+ for token in (
376
+ "FIX-*", "manual_only", "kb_manual", "skill_ai",
377
+ "L-*.linkedFixIds[]", "FIX-*.sourceGapIds[]",
378
+ "禁止修改知识库", "未确认不动",
379
+ ):
380
+ if token not in combined:
381
+ problems.append(f"optimize 文档整体缺少特有授权语义「{token}」")
382
+
383
+ # 9) maintainer 权威正文(源仓存在时必须仍承载;安装副本缺 sibling 时显式 skip)。
384
+ gate_path = MAINTAINER_SKILL / "references" / GATE_OWNER_FILE
385
+ validation_path = MAINTAINER_SKILL / "references" / VALIDATION_OWNER_FILE
386
+ siblings_checked = gate_path.is_file() and validation_path.is_file()
387
+ if siblings_checked:
388
+ gate = gate_path.read_text(encoding="utf-8")
389
+ validation = validation_path.read_text(encoding="utf-8")
390
+ for token in ("两阶段修改门禁", "阶段一:只读方案", "阶段二:确认后执行"):
391
+ if token not in gate:
392
+ problems.append(f"{MAINTAINER_SKILL_NAME}/{GATE_OWNER_FILE}: 权威正文缺少「{token}」")
393
+ for token in ("kb_tool.py validate", "测试", "git diff --check"):
394
+ if token not in validation:
395
+ problems.append(
396
+ f"{MAINTAINER_SKILL_NAME}/{VALIDATION_OWNER_FILE}: 权威正文缺少「{token}」"
397
+ )
398
+ # 从 optimize 迁出的**条件验证判据**必须真正落到 owner 里 —— 只证明文件存在不够,
399
+ # 否则"删掉条件、只留回指"仍会全绿(OPT-010A-R1 的 P1)。
400
+ for token in ("受影响装配", "两跑", "BG8169", "300673", "不劣化"):
401
+ if token not in validation:
402
+ problems.append(
403
+ f"{MAINTAINER_SKILL_NAME}/{VALIDATION_OWNER_FILE}: owner 未承载被迁移的"
404
+ f"验收判据「{token}」(optimize 已回指此处,条件不得只删不迁)"
405
+ )
406
+
407
+ # 一次汇总全部问题,不因首个问题短路。
408
+ if problems:
409
+ self.fail("optimize 共享门禁/校验口径归口不合格:\n " + "\n ".join(problems))
410
+ if not siblings_checked:
411
+ self.skipTest(
412
+ f"安装副本缺少同级 {MAINTAINER_SKILL_NAME} 的 {GATE_OWNER_FILE} / "
413
+ f"{VALIDATION_OWNER_FILE}:显式跳过 sibling 权威正文断言"
414
+ )
415
+
416
+ def test_first_level_classification_and_registry_are_maintainer_owned(self):
417
+ """F-021:一级资产/业务 classification 与 registry 的唯一 owner 是 maintainer;optimize 只诊断与交接。"""
418
+ skill_root = SCRIPT_DIR.parent
419
+ skill = (skill_root / "SKILL.md").read_text(encoding="utf-8")
420
+ routes = (skill_root / "references" / "00-优化规范与文件落点.md").read_text(encoding="utf-8")
421
+ problems = []
422
+
423
+ # 5.1 入口:三个完整文件名 + owner 技能名 + 排他表达,必须在**同一个归口块**内
424
+ # (按空行切块;分别散落在全篇不算)。
425
+ owner_blocks = [
426
+ block for block in re.split(r"\n\s*\n", skill)
427
+ if all(name in block for name in CLASSIFICATION_FILES)
428
+ ]
429
+ if not owner_blocks:
430
+ problems.append(
431
+ "SKILL.md: 缺少在**同一块**内列出三份 classification/registry 完整文件名的归口声明"
432
+ f"({'、'.join(CLASSIFICATION_FILES)})"
433
+ )
434
+ elif len(owner_blocks) > 1:
435
+ # 「唯一 owner」要求入口也只有一块归口声明;允许多块就等于允许一份冲突归口。
436
+ problems.append(
437
+ f"SKILL.md: 存在 {len(owner_blocks)} 个列出三份 classification/registry 完整文件名的"
438
+ f"归口块,唯一 owner 要求**恰好 1 块**(重复 / 冲突归口)"
439
+ )
440
+ for extra in owner_blocks[1:]:
441
+ declared = (
442
+ MAINTAINER_SKILL_NAME if MAINTAINER_SKILL_NAME in extra
443
+ else "crwu-dev-audit-optimize(optimize 自执行,冲突)"
444
+ if "crwu-dev-audit-optimize" in extra else "未声明 owner"
445
+ )
446
+ problems.append(
447
+ f"SKILL.md: 多余归口块的 owner/执行声明 = {declared};"
448
+ f"片段:{extra.strip()[:56]}…"
449
+ )
450
+ else:
451
+ block = owner_blocks[0]
452
+ if MAINTAINER_SKILL_NAME not in block:
453
+ problems.append(
454
+ f"SKILL.md: classification/registry 归口块未写明 owner 技能名「{MAINTAINER_SKILL_NAME}」"
455
+ )
456
+ if not re.search(r"一律交接|一律由|只能由|不得自行修改", block):
457
+ problems.append(
458
+ "SKILL.md: classification/registry 归口块缺少排他性表达(一律交接 / 不得自行修改)"
459
+ )
460
+ # 5.4 同一块必须保留 router 运行时算法仍由本技能(批准后)执行的边界
461
+ for token in ("scope", "method", "overlay", "并集", "运行时算法", "本技能"):
462
+ if token not in block:
463
+ problems.append(
464
+ f"SKILL.md: 归口块未保留 router 运行时算法的本技能执行边界「{token}」"
465
+ )
466
+
467
+ # 5.2 解析 references/00 的「改动类型 | 唯一落点 | 联动 / 红线」表(只取该表)
468
+ lines = routes.splitlines()
469
+ start = next((i for i, l in enumerate(lines) if l.startswith("| 改动类型 | 唯一落点")), None)
470
+ if start is None:
471
+ problems.append("references/00: 未找到「改动类型 → 唯一落点」表")
472
+ rows = []
473
+ else:
474
+ rows = []
475
+ for line in lines[start + 2:]:
476
+ if not line.startswith("|"):
477
+ break
478
+ cells = [c.strip() for c in line.strip().strip("|").split("|")]
479
+ if len(cells) >= 2:
480
+ rows.append(cells)
481
+
482
+ # router 运行时算法行:不得混入 classification/registry
483
+ router_rows = [
484
+ row for row in rows
485
+ if any(k in row[0] for k in ("路由", "分发", "降级", "覆盖层"))
486
+ or re.search(r"router", row[0], re.I)
487
+ ]
488
+ if not router_rows:
489
+ problems.append("references/00: 缺 router 运行时算法的独立落点行")
490
+ for row in router_rows:
491
+ row_text = "|".join(row)
492
+ # 紧凑写法 `` `references/02`/`03`/`04`/`07` `` 既不是 `references/03` 子串、也断开在反引号上,
493
+ # 所以先去掉反引号再做引用编号检查。
494
+ plain = row_text.replace("`", "")
495
+ for token in ("业务分类判定", "references/03", "references/04", "references/07",
496
+ "03-asset-classification", "04-business-classification", "07-skill-registry"):
497
+ if token in plain:
498
+ problems.append(
499
+ f"references/00: router 运行时算法行混入 classification/registry「{token}」"
500
+ f"(改动类型:{row[0][:36]})"
501
+ )
502
+ for number in ("03", "04", "07"):
503
+ if re.search(rf"references/(?:\d\d/)*{number}(?![0-9])", plain):
504
+ problems.append(
505
+ f"references/00: router 运行时算法行仍引用 references/{number}"
506
+ f"(含 `references/02/03/04/07` 这类紧凑写法;改动类型:{row[0][:36]})"
507
+ )
508
+
509
+ # 独立 classification/registry 行:必须由 maintainer **独占且恰好一条**
510
+ owner_rows = [
511
+ row for row in rows
512
+ if re.search(r"classification", row[0], re.I) and re.search(r"registry", row[0], re.I)
513
+ ]
514
+ if not owner_rows:
515
+ problems.append(
516
+ "references/00: 缺独立的「一级资产/业务 classification 与 registry」落点行"
517
+ )
518
+ else:
519
+ # 「唯一 owner」是机器判据:允许多条就等于没有唯一事实源(OPT-010B-R1 堵的绕过孔)。
520
+ if len(owner_rows) > 1:
521
+ problems.append(
522
+ f"references/00: 存在 {len(owner_rows)} 条 classification / registry owner 行,"
523
+ f"唯一事实源要求**恰好 1 条**(第 2 条起为重复 / 冲突归口)"
524
+ )
525
+ for extra in owner_rows[1:]:
526
+ extra_text = "|".join(extra)
527
+ if MAINTAINER_SKILL_NAME in extra_text:
528
+ declared = MAINTAINER_SKILL_NAME
529
+ elif "crwu-dev-audit-optimize" in extra_text:
530
+ declared = "crwu-dev-audit-optimize(optimize 自执行,冲突)"
531
+ else:
532
+ declared = "未声明 owner"
533
+ problems.append(
534
+ f"references/00: 多余 owner 行的 owner/执行声明 = {declared};"
535
+ f"改动类型:{extra[0][:36]}"
536
+ )
537
+ row_text = "|".join(owner_rows[0])
538
+ for token in CLASSIFICATION_FILES:
539
+ if token not in row_text:
540
+ problems.append(
541
+ f"references/00: classification/registry 行缺完整文件名「{token}」"
542
+ )
543
+ if MAINTAINER_SKILL_NAME not in row_text:
544
+ problems.append(
545
+ f"references/00: classification/registry 行未写明 owner「{MAINTAINER_SKILL_NAME}」"
546
+ )
547
+ if not re.search(r"只诊断|只做诊断|交接", row_text):
548
+ problems.append("references/00: classification/registry 行未声明 optimize 只诊断/交接")
549
+ if not re.search(r"不自行修改|不得自行修改|不执行|不得执行", row_text):
550
+ problems.append("references/00: classification/registry 行未声明 optimize 不得自行修改")
551
+
552
+ # 散落绕过:三份文件名不得出现在该表的**其它**行。
553
+ # 只豁免**唯一那条** canonical owner 行(按下标定位,连内容相同的重复行也不放过)。
554
+ canonical_index = rows.index(owner_rows[0]) if owner_rows else None
555
+ for position, row in enumerate(rows):
556
+ if position == canonical_index:
557
+ continue
558
+ row_text = "|".join(row)
559
+ for token in CLASSIFICATION_FILES:
560
+ if token in row_text:
561
+ problems.append(
562
+ f"references/00: 「{token}」散落在非归口行「{row[0][:28]}」——"
563
+ f"必须由 classification/registry 行一处声明"
564
+ )
565
+
566
+ # 5.4 不得误伤 optimize 的合法职责(A 桶 / D 桶 / 交接不继承授权)
567
+ buckets = {}
568
+ for line in skill.splitlines():
569
+ if not line.startswith("|"):
570
+ continue
571
+ cells = [c.strip() for c in line.strip().strip("|").split("|")]
572
+ match = re.match(r"\*\*([A-E])[  ]", cells[0]) if cells else None
573
+ if match:
574
+ buckets[match.group(1)] = "|".join(cells)
575
+ if "知识库" not in buckets.get("A", "") or "人工" not in buckets.get("A", ""):
576
+ problems.append("SKILL.md: A 桶必须仍是知识库人工修复(不得因归口调整被改写)")
577
+ if "本技能执行" not in buckets.get("D", ""):
578
+ problems.append("SKILL.md: D 桶叶子算法与流程必须仍由本技能在批准后执行")
579
+ if not re.search(r"一律交接|交接 maintainer|不得自行修改", buckets.get("C", "")):
580
+ problems.append(
581
+ "SKILL.md: C 桶执行声明未排除 classification / registry"
582
+ "(须写明目标含三份 owner 文件时一律交接 maintainer)"
583
+ )
584
+ if "不继承修改授权" not in skill:
585
+ problems.append("SKILL.md: 必须保留「交接不继承修改授权」")
586
+
587
+ # 5.3 只读 owner 仍真实承载(源仓 sibling 缺失时显式 skip)
588
+ gate_path = MAINTAINER_SKILL / "references" / GATE_OWNER_FILE
589
+ modes_path = MAINTAINER_SKILL / "references" / "00-responsibility-and-modes.md"
590
+ maintainer_skill = MAINTAINER_SKILL / "SKILL.md"
591
+ plugins_agents = SKILLS_LAYER.parents[2] / "AGENTS.md"
592
+ sources = (gate_path, modes_path, maintainer_skill, plugins_agents)
593
+ siblings_checked = all(path.is_file() for path in sources)
594
+ if siblings_checked:
595
+ maintainer_skill_text = maintainer_skill.read_text(encoding="utf-8")
596
+ if not ("分类" in maintainer_skill_text and "registry" in maintainer_skill_text):
597
+ problems.append(
598
+ f"{MAINTAINER_SKILL_NAME}/SKILL.md: 未声明「分类、registry」职责"
599
+ )
600
+ gate_text = gate_path.read_text(encoding="utf-8")
601
+ for name in CLASSIFICATION_FILES:
602
+ if name not in gate_text:
603
+ problems.append(f"{MAINTAINER_SKILL_NAME}/{GATE_OWNER_FILE}: 缺「{name}」")
604
+ modes_text = modes_path.read_text(encoding="utf-8")
605
+ if not all(t in modes_text for t in ("audit", "repair", "classification", "registry")):
606
+ problems.append(
607
+ f"{MAINTAINER_SKILL_NAME}/references/00-responsibility-and-modes.md: "
608
+ "audit/repair 语义未覆盖 classification / registry 不一致"
609
+ )
610
+ agents_text = plugins_agents.read_text(encoding="utf-8")
611
+ if not all(t in agents_text for t in ("一级 Skill", "registry", "classification",
612
+ MAINTAINER_SKILL_NAME)):
613
+ problems.append(
614
+ "plugins/AGENTS.md: 未把一级 Skill / 一级根 / registry / classification 归 maintainer"
615
+ )
616
+
617
+ # 一次汇总全部问题,不因首个问题短路。
618
+ if problems:
619
+ self.fail("classification / registry 归口不合格:\n " + "\n ".join(problems))
620
+ if not siblings_checked:
621
+ self.skipTest(
622
+ f"安装副本缺少同级 {MAINTAINER_SKILL_NAME} 的权威正文或 plugins/AGENTS.md:"
623
+ "显式跳过 sibling 归口断言"
624
+ )
625
+
626
+ def test_plan_and_completion_report_use_source_paths_without_runtime_paths(self):
627
+ """F-022:方案表与完成汇报只列源仓路径;运行时只报部署状态,不留运行时真身路径。"""
628
+ skill_root = SCRIPT_DIR.parent
629
+ ref02 = (skill_root / "references" / "02-方案模板与确认门禁.md").read_text(encoding="utf-8")
630
+ skill = (skill_root / "SKILL.md").read_text(encoding="utf-8")
631
+ problems = []
632
+
633
+ # 4.1 §1 方案必答项:拟改文件表必须是「文件(源仓路径) | 部署说明」分列
634
+ plan = _fenced_template(ref02, "## 1. 方案必答项")
635
+ if not plan:
636
+ problems.append("references/02: 未找到 §1「方案必答项」的 fenced 模板")
637
+ else:
638
+ header = next(
639
+ (line for line in plan.splitlines()
640
+ if line.startswith("|") and "文件" in line and "部署" in line),
641
+ None,
642
+ )
643
+ if header is None:
644
+ problems.append("references/02: §1 拟改文件表缺少「文件 / 部署说明」列头")
645
+ else:
646
+ cells = [c.strip() for c in header.strip().strip("|").split("|")]
647
+ file_cells = [c for c in cells if "文件" in c]
648
+ deploy_cells = [c for c in cells if "部署说明" in c]
649
+ if len(file_cells) != 1:
650
+ problems.append(
651
+ f"references/02: §1 拟改文件表必须恰好一个「文件」列(实际 {len(file_cells)})"
652
+ )
653
+ else:
654
+ if "源仓路径" not in file_cells[0]:
655
+ problems.append(
656
+ f"references/02: §1 文件列必须写明「文件(源仓路径)」,实际「{file_cells[0]}」"
657
+ )
658
+ if "运行时" in file_cells[0]:
659
+ problems.append(
660
+ f"references/02: §1 文件列不得把源仓路径与运行时路径并列:「{file_cells[0]}」"
661
+ )
662
+ if len(deploy_cells) != 1:
663
+ problems.append(
664
+ f"references/02: §1 必须有一个独立的「部署说明」列(实际 {len(deploy_cells)})"
665
+ )
666
+ else:
667
+ for token in ("运行时由用户 skills 管理机制负责", "本列不再列运行时路径"):
668
+ if token not in deploy_cells[0]:
669
+ problems.append(f"references/02: §1 部署说明列缺「{token}」")
670
+ if "库内层级路径寻址键" not in plan:
671
+ problems.append(
672
+ "references/02: §1 知识库人工项必须继续用库内层级路径寻址键"
673
+ "(不得改成源仓或运行时路径)"
674
+ )
675
+
676
+ # 4.2 §4 汇报模板:源仓文件与运行时部署状态必须是两条独立项
677
+ report = _fenced_template(ref02, "## 4. 汇报模板")
678
+ if not report:
679
+ problems.append("references/02: 未找到 §4「汇报模板」的 fenced 模板")
680
+ else:
681
+ items = [line.strip() for line in report.splitlines() if line.strip().startswith("- ")]
682
+ changed = [line for line in items if "已改文件" in line]
683
+ deployed = [line for line in items if "运行时部署" in line or "部署状态" in line]
684
+ if len(changed) != 1:
685
+ problems.append(
686
+ f"references/02: §4 汇报模板必须恰好一条「已改文件」项(实际 {len(changed)})"
687
+ )
688
+ else:
689
+ for token in ("逐文件", "源仓路径"):
690
+ if token not in changed[0]:
691
+ problems.append(f"references/02: §4「已改文件」项缺「{token}」")
692
+ if "运行时" in changed[0]:
693
+ problems.append(
694
+ f"references/02: §4「已改文件」项不得同时含源仓与运行时(混合路径列):{changed[0]}"
695
+ )
696
+ if len(deployed) != 1:
697
+ problems.append(
698
+ f"references/02: §4 汇报模板必须恰好一条独立「运行时部署状态」项(实际 {len(deployed)})"
699
+ )
700
+ else:
701
+ for token in ("用户 skills 管理机制", "未直接写运行时"):
702
+ if token not in deployed[0]:
703
+ problems.append(f"references/02: §4「运行时部署状态」项缺「{token}」")
704
+ # 旧形态:源仓/运行时混合列 或 要求填写运行时真身路径
705
+ for banned in ("源仓/运行时各列", "源仓/运行时各列", "源仓 + 运行时", "源仓+运行时",
706
+ "运行时真身路径"):
707
+ if banned in report:
708
+ problems.append(
709
+ f"references/02: §4 汇报模板不得出现「{banned}」(源仓/运行时混合或要求运行时路径)"
710
+ )
711
+ # 不得要求填写运行时路径;方案表里的否定式「本列不再列运行时路径」是正确契约,故按行判定。
712
+ for line in report.splitlines():
713
+ if "运行时路径" in line and not re.search(r"不列|不再列|不得|禁止|未直接", line):
714
+ problems.append(
715
+ f"references/02: §4 汇报模板要求填写运行时路径:{line.strip()}"
716
+ )
717
+
718
+ # 4.3 入口级部署边界(只读)
719
+ for token in ("不得写运行时技能目录", "运行时部署由用户 skills 管理机制负责"):
720
+ if token not in skill:
721
+ problems.append(f"SKILL.md: 入口级部署边界缺「{token}」")
722
+ if not re.search(r"唯一落点", skill):
723
+ problems.append("SKILL.md: 缺源仓唯一落点语义")
724
+ for banned in ("源仓/运行时各列", "运行时真身路径"):
725
+ if banned in skill:
726
+ problems.append(f"SKILL.md: 入口不得出现「{banned}」")
727
+
728
+ # 一次汇总全部问题,不因首个问题短路。
729
+ self.assertEqual([], problems, "源仓/运行时口径不合格:\n " + "\n ".join(problems))
730
+
731
+ def test_p3_document_debt_is_closed_and_python_runtime_has_one_owner(self):
732
+ """OPT-012:F-027/028/030 文档事实收口,F-031 Python 运行时单一 owner(optimize 只按名回指)。"""
733
+ skill_root = SCRIPT_DIR.parent
734
+ skill = (skill_root / "SKILL.md").read_text(encoding="utf-8")
735
+ spec = (skill_root / "references" / "00-优化规范与文件落点.md").read_text(encoding="utf-8")
736
+ problems = []
737
+
738
+ GOVERNANCE = (
739
+ "RULE 编号与引用完整性规范.md", "同义术语表.md", "回测报告-真实案例覆盖.md",
740
+ "审核模块注册表.md", "时效核查日历.md", "标签词典.md", "校准案例记录.md",
741
+ "知识库规则分层与引用规范.md", "规则时效与适用日期规范.md",
742
+ )
743
+ CONTRACTS = (
744
+ "01-调度器-SKILL总纲.md", "02-防幻觉协议执行细则.md",
745
+ "03-审核统计与台账规范.md", "04-复核规定动作清单.md",
746
+ )
747
+
748
+ # 4.1 F-027:不存在的维护说明文件名不得残留,两处都要改成真实名
749
+ for label, text in (("SKILL.md", skill), ("references/00", spec)):
750
+ if "99-维护说明.md" in text:
751
+ problems.append(f"{label}: 仍引用不存在的 `99-维护说明.md`(F-027)")
752
+ fixed = spec.count("crwu-audit/references/99-maintenance.md")
753
+ if fixed < 2:
754
+ problems.append(
755
+ "references/00: 两处引用都必须改为真实名"
756
+ f"`crwu-audit/references/99-maintenance.md`(当前 {fixed} 处)"
757
+ )
758
+
759
+ # 4.2 F-028:治理目录 9 项 / 执行契约 4 项,并澄清 04 是什么
760
+ if "无 04" in spec:
761
+ problems.append("references/00: 不得再声称执行契约「无 04」(F-028)")
762
+ for name in GOVERNANCE:
763
+ if name not in spec:
764
+ problems.append(f"references/00: 治理目录缺真实文档「{name}」")
765
+ for name in CONTRACTS:
766
+ if name not in spec:
767
+ problems.append(f"references/00: 执行契约缺真实文档「{name}」")
768
+ if not re.search(r"治理[^\n]{0,40}9 个", spec):
769
+ problems.append("references/00: 必须明确治理目录当前为 9 个文档")
770
+ if not re.search(r"执行契约[^\n]{0,40}4 个", spec):
771
+ problems.append("references/00: 必须明确执行契约当前为 4 个文档")
772
+ if "复核规定动作清单" not in spec:
773
+ problems.append("references/00: 必须澄清 04 是《复核规定动作清单》")
774
+ if "03-审核统计与台账规范.md" not in spec:
775
+ problems.append("references/00: 必须澄清统计台账规范仍是 03")
776
+ for line in spec.splitlines():
777
+ if "04-审核统计与台账规范" in line and not re.search(r"不存在|无此|并非|不是", line):
778
+ problems.append(
779
+ f"references/00: 不得声称 `04-审核统计与台账规范` 存在:{line.strip()[:64]}"
780
+ )
781
+
782
+ # 4.3 F-030:NOTICE 必须存在,并在交付资产表里有独立入口
783
+ if not (skill_root / "template" / "NOTICE.echarts.txt").is_file():
784
+ problems.append("template/NOTICE.echarts.txt 不存在(F-030)")
785
+ assets = spec
786
+ if "## 3.1" in assets:
787
+ assets = assets[assets.index("## 3.1"):]
788
+ if "## 4." in assets:
789
+ assets = assets[:assets.index("## 4.")]
790
+ for token in ("template/echarts.min.js", "template/NOTICE.echarts.txt"):
791
+ if token not in assets:
792
+ problems.append(f"references/00 交付资产表: 未登记「{token}」")
793
+ notice_rows = [l for l in assets.splitlines()
794
+ if l.startswith("|") and "NOTICE.echarts.txt" in l]
795
+ if not notice_rows:
796
+ problems.append("references/00 交付资产表: NOTICE 未作为独立表格行登记")
797
+ else:
798
+ row = notice_rows[0]
799
+ if not re.search(r"许可|NOTICE|第三方", row):
800
+ problems.append(f"references/00: NOTICE 行未说明第三方许可/NOTICE:{row[:72]}")
801
+ if not re.search(r"同包|随.{0,6}分发|一并分发", row):
802
+ problems.append(f"references/00: NOTICE 行未说明与 echarts.min.js 同包分发:{row[:72]}")
803
+
804
+ # 4.4 F-031:Python 运行时唯一 owner = maintainer SKILL.md;optimize 只按名回指
805
+ def python_section(text: str) -> str:
806
+ lines = text.splitlines()
807
+ start = next((i for i, l in enumerate(lines)
808
+ if l.startswith("## 脚本运行时(Python)")), None)
809
+ if start is None:
810
+ return ""
811
+ end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("## ")),
812
+ len(lines))
813
+ return "\n".join(lines[start:end])
814
+
815
+ consumer = python_section(skill)
816
+ if not consumer:
817
+ problems.append("SKILL.md: 未找到「## 脚本运行时(Python)」章节")
818
+ else:
819
+ if f"{MAINTAINER_SKILL_NAME}/SKILL.md" not in consumer:
820
+ problems.append("SKILL.md: 运行时段落未写明 owner 技能名(F-031)")
821
+ if not PYTHON_RUNTIME_CONSUMER_UNIQUE_RE.search(consumer):
822
+ problems.append("SKILL.md: 运行时段落未声明「唯一维护」/等价唯一 owner 声明")
823
+ for token in PYTHON_RUNTIME_CONSUMER_REQUIRED:
824
+ if token not in consumer:
825
+ problems.append(f"SKILL.md: 运行时段落缺少指针/门禁语义「{token}」")
826
+ # 与 owner 同一份专属 token 集:consumer **一条都不能含**(部分复述同样被拒)。
827
+ for token in PYTHON_RUNTIME_OWNER_TOKENS:
828
+ if token in consumer:
829
+ problems.append(
830
+ f"SKILL.md: 运行时段落仍复述 owner 专属语义「{token}」(应只留指针与最小失败门禁)"
831
+ )
832
+
833
+ owner_path = MAINTAINER_SKILL / "SKILL.md"
834
+ siblings_checked = owner_path.is_file()
835
+ if siblings_checked:
836
+ owner = python_section(owner_path.read_text(encoding="utf-8"))
837
+ if not owner:
838
+ problems.append(f"{MAINTAINER_SKILL_NAME}/SKILL.md: 未找到运行时段落(owner 缺失)")
839
+ for token in PYTHON_RUNTIME_OWNER_TOKENS:
840
+ if token not in owner:
841
+ problems.append(
842
+ f"owner({MAINTAINER_SKILL_NAME}/SKILL.md)运行时段落缺语义「{token}」"
843
+ )
844
+
845
+ # 一次汇总全部问题,不因首个问题短路。
846
+ if problems:
847
+ self.fail("P3 文档债务未收口:\n " + "\n ".join(problems))
848
+ if not siblings_checked:
849
+ self.skipTest(
850
+ f"安装副本缺少同级 {MAINTAINER_SKILL_NAME}/SKILL.md:显式跳过 owner 段落断言"
851
+ )
852
+
853
+
854
+ if __name__ == "__main__":
855
+ unittest.main()