@ccoalm/ccl-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (561) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +49 -0
  3. package/dist/assets/marketplace/.agents/plugins/marketplace.json +12 -0
  4. package/dist/assets/marketplace/.claude-plugin/marketplace.json +13 -0
  5. package/dist/assets/marketplace/marketplace-manifest.json +12 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/.claude-plugin/marketplace.json +16 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/.claude-plugin/plugin.json +5 -0
  8. package/dist/assets/marketplace/plugins/ccl-skills/.codex-plugin/plugin.json +5 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/.worktree-only +3 -0
  10. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +45 -0
  11. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/subagent-start.md +12 -0
  12. package/dist/assets/marketplace/plugins/ccl-skills/hooks/AGENTS.md +19 -0
  13. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-delegation-owner.sh +125 -0
  14. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-edit-isolation.sh +102 -0
  15. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-merge-authorization.sh +1156 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/hooks/hooks.json +131 -0
  17. package/dist/assets/marketplace/plugins/ccl-skills/hooks/merge-authorization-prompt.sh +142 -0
  18. package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-guard.sh +12 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-stop.sh +13 -0
  20. package/dist/assets/marketplace/plugins/ccl-skills/hooks/remind-post-merge-cleanup.sh +144 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-context.sh +87 -0
  22. package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-start.sh +86 -0
  23. package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-extraction-gate-stop.sh +69 -0
  24. package/dist/assets/marketplace/plugins/ccl-skills/hooks/subagent-start.sh +26 -0
  25. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_delegation_owner.sh +329 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_edit_isolation.sh +322 -0
  27. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_merge_authorization.sh +902 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_merge_authorization_prompt.sh +178 -0
  29. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_remind_post_merge_cleanup.sh +121 -0
  30. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_session_start.sh +170 -0
  31. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/AGENTS.md +17 -0
  32. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +564 -0
  33. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +14 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-update-skills.md +44 -0
  35. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-verify-skills.md +109 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-worktree-check.md +36 -0
  37. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/AGENTS.md +28 -0
  38. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/README.md +276 -0
  39. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.example.json +10 -0
  40. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +1307 -0
  41. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +941 -0
  42. package/dist/assets/marketplace/plugins/ccl-skills/skills/agents-file-coverage-gate/SKILL.md +45 -0
  43. package/dist/assets/marketplace/plugins/ccl-skills/skills/agents-file-coverage-gate/agents/openai.yaml +4 -0
  44. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +188 -0
  45. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/agents/openai.yaml +4 -0
  46. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/android-dev.md +92 -0
  47. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/flutter-dev.md +80 -0
  48. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/ios-dev.md +72 -0
  49. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/kotlin-multiplatform.md +93 -0
  50. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-platform-boundaries.md +77 -0
  51. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +77 -0
  52. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/source-evidence-map.md +64 -0
  53. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +353 -0
  54. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/agents/openai.yaml +4 -0
  55. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +419 -0
  56. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +126 -0
  57. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +197 -0
  58. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +179 -0
  59. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/AGENTS.md +98 -0
  60. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_envelope.py +93 -0
  61. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_timeout_exit.sh +15 -0
  62. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +1438 -0
  63. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +324 -0
  64. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/concern_excerpt.py +295 -0
  65. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/egress_schema.py +214 -0
  66. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/init_policy_matrix.py +642 -0
  67. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_packet_mcp.py +181 -0
  68. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +1165 -0
  69. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +1190 -0
  70. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_cli_review.py +946 -0
  71. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_opencode_review.py +474 -0
  72. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_probe_result.py +1899 -0
  73. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_review_json.py +200 -0
  74. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +2845 -0
  75. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.sh +6 -0
  76. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/run_claude_capture.py +71 -0
  77. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/runtime-surface-verification-design.md +53 -0
  78. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_classify_envelope.sh +68 -0
  79. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +2311 -0
  80. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_cli_review_wrappers.sh +1832 -0
  81. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_code_review_identity.sh +73 -0
  82. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_concern_excerpt.sh +245 -0
  83. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_egress_schema.sh +177 -0
  84. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +272 -0
  85. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_kimi_packet_mcp.py +195 -0
  86. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_opencode_review_concurrency.sh +120 -0
  87. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_opencode_review_retry.sh +1005 -0
  88. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_opencode_review.sh +258 -0
  89. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +574 -0
  90. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_review_json.sh +349 -0
  91. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +434 -0
  92. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_order.sh +264 -0
  93. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +2412 -0
  94. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/verify_native_skill_binding.py +123 -0
  95. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +153 -0
  96. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/agents/openai.yaml +4 -0
  97. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/diagnosis-playbook.md +54 -0
  98. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/prevention-routing.md +36 -0
  99. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/SKILL.md +69 -0
  100. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/agents/openai.yaml +4 -0
  101. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/references/security-review-gate.md +41 -0
  102. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/SKILL.md +165 -0
  103. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/agents/openai.yaml +4 -0
  104. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/api-security-boundaries.md +47 -0
  105. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/architecture-playbook.md +160 -0
  106. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/artifact-generation-architecture.md +37 -0
  107. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/audit-history-architecture.md +29 -0
  108. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/bulk-workflow-architecture.md +33 -0
  109. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/config-rule-routing-architecture.md +34 -0
  110. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/cross-cutting-concerns.md +72 -0
  111. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/data-modeling-and-migrations.md +79 -0
  112. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/data-platform-architecture.md +210 -0
  113. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/dependency-platform.md +105 -0
  114. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/developer-tooling-architecture.md +38 -0
  115. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/error-contract-architecture.md +36 -0
  116. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/event-driven-architecture.md +260 -0
  117. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/http-gateway-architecture.md +74 -0
  118. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/mq-consumer-architecture.md +38 -0
  119. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/multi-tenant-isolation.md +275 -0
  120. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/notification-architecture.md +25 -0
  121. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/ops-checklist.md +57 -0
  122. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/performance-capacity-architecture.md +38 -0
  123. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/protobuf-contract-architecture.md +119 -0
  124. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/redis-cache-coordination.md +93 -0
  125. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/release-runtime-readiness.md +65 -0
  126. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/replay-comparison-architecture.md +26 -0
  127. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/runtime-observability.md +94 -0
  128. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/service-scaffold.md +76 -0
  129. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/source-evidence-map.md +55 -0
  130. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/workflow-state-architecture.md +38 -0
  131. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +159 -0
  132. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/agents/openai.yaml +4 -0
  133. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/artifact-generation-patterns.md +37 -0
  134. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/audit-history-patterns.md +28 -0
  135. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/bulk-import-export-patterns.md +56 -0
  136. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/config-rule-routing-patterns.md +38 -0
  137. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/data-access-patterns.md +55 -0
  138. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/db-schema-and-dal-patterns.md +109 -0
  139. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/dependency-client-patterns.md +130 -0
  140. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/developer-tooling-patterns.md +70 -0
  141. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/domain-feature-patterns.md +78 -0
  142. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/engineering-patterns.md +119 -0
  143. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/error-contract-patterns.md +55 -0
  144. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/feature-playbook.md +61 -0
  145. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/http-gateway-client-patterns.md +76 -0
  146. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/mq-consumer-patterns.md +55 -0
  147. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/notification-patterns.md +42 -0
  148. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/observability-implementation-patterns.md +101 -0
  149. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/performance-capacity-patterns.md +44 -0
  150. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/protobuf-contract-patterns.md +72 -0
  151. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/public-api-integration-patterns.md +56 -0
  152. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/quality-and-testing-patterns.md +91 -0
  153. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/redis-cache-lock-patterns.md +123 -0
  154. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/release-ops-patterns.md +112 -0
  155. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/reliability-patterns.md +83 -0
  156. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/replay-comparison-patterns.md +32 -0
  157. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/scaffold-and-codegen.md +86 -0
  158. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/source-evidence-map.md +54 -0
  159. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/state-machine-task-patterns.md +45 -0
  160. package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/SKILL.md +80 -0
  161. package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/agents/openai.yaml +4 -0
  162. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +117 -0
  163. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/agents/openai.yaml +4 -0
  164. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-approval-auto-reviewer.md +106 -0
  165. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-command-sandbox.md +441 -0
  166. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-context-freshness.md +47 -0
  167. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-credentials-auth.md +13 -0
  168. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-extensions-skills.md +13 -0
  169. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-file-edit-protocol.md +129 -0
  170. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-ide-integration.md +5 -0
  171. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-input-ingestion.md +13 -0
  172. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-instruction-composition.md +13 -0
  173. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-lifecycle-hooks.md +92 -0
  174. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-messaging.md +5 -0
  175. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-runtime-bootstrap.md +5 -0
  176. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-session-persistence.md +448 -0
  177. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-task-orchestration.md +13 -0
  178. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-tool-dispatch.md +123 -0
  179. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-turn-lifecycle.md +131 -0
  180. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/inference-capacity-operations.md +162 -0
  181. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/llm-client-gateway.md +156 -0
  182. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +146 -0
  183. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/retrieval-agent-safety.md +273 -0
  184. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +202 -0
  185. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/agents/openai.yaml +4 -0
  186. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/contracts-and-state.md +62 -0
  187. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/cross-stack-alignment.md +94 -0
  188. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/framework-choice.md +76 -0
  189. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/online-practice-uptake.md +56 -0
  190. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/platform-capabilities.md +91 -0
  191. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/product-page-checklist.md +40 -0
  192. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/qa-release.md +72 -0
  193. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/source-evidence-map.md +82 -0
  194. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/SKILL.md +103 -0
  195. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/agents/openai.yaml +5 -0
  196. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/references/multi-agent-delegation-playbook.md +100 -0
  197. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/SKILL.md +70 -0
  198. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/agents/openai.yaml +4 -0
  199. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-data-acquisition.md +549 -0
  200. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-disclosure-channels.md +97 -0
  201. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/research-prompts.md +66 -0
  202. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/scripts/AGENTS.md +32 -0
  203. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/scripts/test-public-data-acquisition-recipes.sh +379 -0
  204. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +244 -0
  205. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/agents/openai.yaml +4 -0
  206. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +76 -0
  207. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/framework-middleware-checklist.md +142 -0
  208. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/infra-component-deployment.md +268 -0
  209. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/log-correlation-recipe.md +124 -0
  210. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/log-schema-canonical.md +208 -0
  211. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/metrics-conventions.md +105 -0
  212. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/obs-stack-architecture.md +107 -0
  213. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/sli-slo-design.md +95 -0
  214. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/source-register.md +11 -0
  215. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/SKILL.md +303 -0
  216. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/agents/openai.yaml +4 -0
  217. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/canary-and-rollout-strategy.md +163 -0
  218. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/config-center-via-etcd.md +245 -0
  219. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/custom-control-plane-boundary.md +298 -0
  220. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/deploy-cli-concrete-recipe.md +312 -0
  221. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/deploy-pipeline.md +165 -0
  222. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/env-and-lane-matrix.md +126 -0
  223. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/lane-orchestration-control-plane.md +383 -0
  224. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/multi-region-and-cluster.md +135 -0
  225. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/promotion-gate-and-review.md +149 -0
  226. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/python-package-registry-release.md +462 -0
  227. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/rollback-playbook.md +123 -0
  228. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/secret-and-config-management.md +231 -0
  229. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/version-authority-and-deprecation.md +21 -0
  230. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +276 -0
  231. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/agents/openai.yaml +4 -0
  232. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +127 -0
  233. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/framework-middleware.md +143 -0
  234. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +90 -0
  235. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/http-response-envelope-contract.md +24 -0
  236. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +127 -0
  237. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/multi-env-routing.md +192 -0
  238. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/protobuf-http-contract-signals.md +64 -0
  239. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +124 -0
  240. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/rpc-framework-recipe.md +494 -0
  241. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-choice.md +113 -0
  242. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-migration-playbook.md +231 -0
  243. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +131 -0
  244. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +235 -0
  245. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/agents/openai.yaml +4 -0
  246. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/adr-convention.md +146 -0
  247. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-checklist.md +30 -0
  248. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-evaluation-report-template.md +25 -0
  249. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-execution-spec.md +108 -0
  250. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-sop.md +457 -0
  251. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-templates.md +24 -0
  252. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/artifact-egress-confidentiality.md +58 -0
  253. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/code-review-checklist.md +86 -0
  254. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/cross-repo-coordination.md +46 -0
  255. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +192 -0
  256. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-review-gate-mechanics.md +62 -0
  257. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +45 -0
  258. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/diagnostic-spec-match-gate.md +36 -0
  259. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dispatch-owner-skills.md +35 -0
  260. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dormant-code-activation.md +47 -0
  261. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/existing-project-assessment-report.md +223 -0
  262. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/external-skill-augmentation.md +46 -0
  263. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/feature-deprecation-cascade.md +15 -0
  264. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/high-risk-resilience-gates.md +73 -0
  265. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/implementation-completeness-and-minimality.md +120 -0
  266. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/implementation-entry-reentry-gate.md +122 -0
  267. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/modular-monolith-heuristic.md +105 -0
  268. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md +115 -0
  269. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/problem-resolution-and-learning.md +62 -0
  270. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/quality-attributes.md +112 -0
  271. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/quality-remediation-program.md +88 -0
  272. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +27 -0
  273. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/refactoring-discipline.md +52 -0
  274. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/review-reception.md +34 -0
  275. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/shared-gate-artifact-classification.md +76 -0
  276. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/source-evidence-map.md +31 -0
  277. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/status-tracker-sync.md +77 -0
  278. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/sync-spec-repo-contract.md +25 -0
  279. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +34 -0
  280. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/worktree-mechanics.md +55 -0
  281. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/scripts/AGENTS.md +18 -0
  282. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/scripts/check-agent-contract-coverage.sh +213 -0
  283. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +136 -0
  284. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/agents/openai.yaml +9 -0
  285. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/analytics-visualization-interactions.md +206 -0
  286. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +108 -0
  287. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/complex-creation-interactions.md +194 -0
  288. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +214 -0
  289. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +53 -0
  290. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +129 -0
  291. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +97 -0
  292. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +79 -0
  293. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +63 -0
  294. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +146 -0
  295. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +250 -0
  296. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +237 -0
  297. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +65 -0
  298. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +237 -0
  299. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +324 -0
  300. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-web-desktop-patterns.md +456 -0
  301. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +114 -0
  302. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +79 -0
  303. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/resource-management-interactions.md +113 -0
  304. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/scenario-community-patterns.md +133 -0
  305. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +130 -0
  306. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +47 -0
  307. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/trust-sensitive-ai-and-data-patterns.md +96 -0
  308. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +106 -0
  309. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +176 -0
  310. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +111 -0
  311. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/SKILL.md +157 -0
  312. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/agents/openai.yaml +4 -0
  313. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/ai-service-integration-boundaries.md +57 -0
  314. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/api-contract-and-schema.md +62 -0
  315. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/api-security-boundaries.md +39 -0
  316. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/architecture-playbook.md +46 -0
  317. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/async-execution-model.md +24 -0
  318. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/background-jobs-and-scheduling.md +18 -0
  319. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/batch-and-pipeline-architecture.md +11 -0
  320. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/config-secrets-runtime.md +22 -0
  321. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/data-modeling-and-migrations.md +64 -0
  322. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/data-platform-architecture.md +211 -0
  323. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/event-driven-architecture.md +263 -0
  324. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/multi-tenant-isolation.md +281 -0
  325. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/observability-and-ops.md +26 -0
  326. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/packaging-runtime-readiness.md +20 -0
  327. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/redis-cache-coordination.md +41 -0
  328. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/reliability-and-error-contract.md +17 -0
  329. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/source-evidence-map.md +55 -0
  330. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/web-framework-boundaries.md +26 -0
  331. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +143 -0
  332. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/agents/openai.yaml +4 -0
  333. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/ai-service-wiring-patterns.md +16 -0
  334. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/async-and-worker-patterns.md +24 -0
  335. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/background-job-patterns.md +18 -0
  336. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/batch-and-artifact-patterns.md +13 -0
  337. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/dependency-client-patterns.md +39 -0
  338. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/error-handling-patterns.md +26 -0
  339. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/feature-playbook.md +43 -0
  340. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/observability-implementation-patterns.md +31 -0
  341. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/project-structure-and-tooling.md +24 -0
  342. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/public-api-security-patterns.md +52 -0
  343. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/redis-cache-lock-patterns.md +78 -0
  344. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/schema-and-validation-patterns.md +23 -0
  345. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/source-evidence-map.md +56 -0
  346. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/sqlalchemy-and-migrations-patterns.md +99 -0
  347. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/testing-and-quality-patterns.md +61 -0
  348. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/web-framework-patterns.md +35 -0
  349. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/SKILL.md +91 -0
  350. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/agents/openai.yaml +4 -0
  351. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/config-runtime-readback.md +20 -0
  352. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/mr-merge-authorization.md +31 -0
  353. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/post-release-env-reset.md +31 -0
  354. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/release-closeout-evidence.md +20 -0
  355. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/release-scope-confirmation.md +21 -0
  356. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/tag-and-prod-pipeline-gate.md +20 -0
  357. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/test-scope-prompt.md +24 -0
  358. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/watcher-discipline.md +14 -0
  359. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/SKILL.md +64 -0
  360. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/agents/openai.yaml +4 -0
  361. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/comment-safe-release-doc.md +19 -0
  362. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/release-evidence-workflow.md +23 -0
  363. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/release-testing-scope-section.md +15 -0
  364. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/SKILL.md +87 -0
  365. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/agents/openai.yaml +4 -0
  366. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/SKILL.md +130 -0
  367. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/agents/openai.yaml +4 -0
  368. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/prd-composition-contract.md +35 -0
  369. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/requirement-closure-contract.md +86 -0
  370. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/security-four-questions.md +38 -0
  371. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-intent/SKILL.md +91 -0
  372. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-intent/agents/openai.yaml +4 -0
  373. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +88 -0
  374. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/agents/openai.yaml +4 -0
  375. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +337 -0
  376. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/agents/openai.yaml +4 -0
  377. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/analysis-parse-fix-test-challenge-replay.md +47 -0
  378. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attribution-verification.md +69 -0
  379. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/bootstrap-slim-c3-obligation-table.md +112 -0
  380. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/coverage-exhaustion-traps.md +45 -0
  381. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +162 -0
  382. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +507 -0
  383. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +86 -0
  384. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/evidence-card-template.md +51 -0
  385. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/example-domain-preselect.md +79 -0
  386. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +57 -0
  387. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-lifecycle-handoff.md +65 -0
  388. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +194 -0
  389. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/firing-point-placement.md +75 -0
  390. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +286 -0
  391. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/incident-postmortem-extraction.md +190 -0
  392. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/l0-l1-l2-routing.md +114 -0
  393. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/online-skill-review.md +47 -0
  394. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/parallel-stack-references-pattern.md +164 -0
  395. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +90 -0
  396. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/recurring-anti-patterns-checklist.md +320 -0
  397. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/resume-paused-delivery.md +16 -0
  398. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-feedback-mining.md +33 -0
  399. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-finding-standards.md +57 -0
  400. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-rubric.md +40 -0
  401. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +118 -0
  402. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/skill-listing-budget.md +19 -0
  403. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +254 -0
  404. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +658 -0
  405. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/two-source-extraction-pattern.md +167 -0
  406. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +179 -0
  407. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-routing-map.md +51 -0
  408. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +180 -0
  409. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/AGENTS.md +18 -0
  410. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +1452 -0
  411. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-evidence-card-leak.sh +491 -0
  412. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-mr-target-freshness.sh +173 -0
  413. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +488 -0
  414. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-sync-pointers.sh +419 -0
  415. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +197 -0
  416. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-health.rb +327 -0
  417. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +401 -0
  418. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing.rb +248 -0
  419. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/generic-r0-leak-scan.sh +282 -0
  420. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/governing-chain-diff.py +321 -0
  421. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +964 -0
  422. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +708 -0
  423. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +540 -0
  424. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/source-register-lifecycle.rb +51 -0
  425. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/source-register-pending-status.rb +55 -0
  426. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ai_coding_implementation_gates.sh +829 -0
  427. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +1203 -0
  428. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_r0_status.sh +75 -0
  429. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_register_pending_exclusion.sh +137 -0
  430. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +173 -0
  431. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_route_drift.sh +377 -0
  432. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +833 -0
  433. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +491 -0
  434. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_source_register_lifecycle.sh +114 -0
  435. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_mr_target_freshness.sh +261 -0
  436. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_sync_pointers.sh +538 -0
  437. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_controlled_escalation_pins.sh +154 -0
  438. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +190 -0
  439. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_surface_binding.sh +178 -0
  440. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_prose_target.sh +86 -0
  441. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_generic_r0_leak_scan.sh +131 -0
  442. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_git_identity_predicate_gate.sh +243 -0
  443. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_governing_chain_diff.sh +419 -0
  444. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +120 -0
  445. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_resolution.sh +724 -0
  446. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +414 -0
  447. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_regression_runner_registration.sh +34 -0
  448. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_bank_integrity.sh +205 -0
  449. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +194 -0
  450. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_credential_cwd.sh +61 -0
  451. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +111 -0
  452. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_root_depth.sh +53 -0
  453. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +257 -0
  454. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +98 -0
  455. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/agents/openai.yaml +4 -0
  456. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/input-state-machines.md +36 -0
  457. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/streaming-rich-output.md +130 -0
  458. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/terminal-side-channels.md +96 -0
  459. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/SKILL.md +408 -0
  460. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/agents/openai.yaml +4 -0
  461. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/AGENTS.md +18 -0
  462. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/bitable-setup.md +573 -0
  463. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/README.md +120 -0
  464. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/github-actions.yml +119 -0
  465. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/gitlab-ci.yml +76 -0
  466. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/jenkins.Jenkinsfile +106 -0
  467. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/classical-test-design-techniques.md +279 -0
  468. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/gen_report.py +2807 -0
  469. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/makefile-template.md +200 -0
  470. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/report-config-schema.md +272 -0
  471. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/run_pytestless.py +475 -0
  472. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/source-to-case-workflows.md +258 -0
  473. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-marker-conventions.md +316 -0
  474. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-review-and-prioritization.md +145 -0
  475. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/AGENTS.md +16 -0
  476. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.dart +129 -0
  477. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.go +197 -0
  478. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.py +135 -0
  479. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.ts +285 -0
  480. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/test_gen_report.py +2144 -0
  481. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/update-lifecycle.md +62 -0
  482. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +212 -0
  483. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/agents/openai.yaml +4 -0
  484. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +75 -0
  485. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +50 -0
  486. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/data-and-workflow-testing.md +34 -0
  487. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/design-closed-contract-oracles.md +31 -0
  488. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/e2e-real-flow-testing.md +71 -0
  489. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/fitness-functions.md +240 -0
  490. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/integration-contract-testing.md +235 -0
  491. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/non-functional-specialized-scenarios.md +296 -0
  492. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/rd-testing-standard-template.md +126 -0
  493. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/run-killing-mutation-walk.md +43 -0
  494. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/scenario-testing.md +136 -0
  495. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/source-evidence-map.md +59 -0
  496. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/structured-tc-input-translation.md +67 -0
  497. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-code-authoring-patterns.md +392 -0
  498. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-data-and-determinism.md +39 -0
  499. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-topology-and-commands.md +92 -0
  500. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/unit-testing.md +46 -0
  501. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/vendored-contract-drift-checklist.md +64 -0
  502. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/verify-enforcement-mechanisms.md +18 -0
  503. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/AGENTS.md +17 -0
  504. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/client-terminal-ansi-check.py +140 -0
  505. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/client-terminal-ansi-check.test.sh +75 -0
  506. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-ast-check.py +170 -0
  507. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-ast-check.test.sh +87 -0
  508. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-go-check.go +198 -0
  509. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-go-check.test.sh +109 -0
  510. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/test_mutation_backup_recipe.sh +237 -0
  511. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +184 -0
  512. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/agents/openai.yaml +4 -0
  513. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/comment-safe-feishu.md +93 -0
  514. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/cross-model-co-review.md +3 -0
  515. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/delivery-face-closeout.md +60 -0
  516. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/doc-charter-first.md +17 -0
  517. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/session-vantage-leakage.md +58 -0
  518. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +126 -0
  519. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/agents/openai.yaml +4 -0
  520. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +47 -0
  521. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/embedded-h5-in-host.md +87 -0
  522. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/react-architecture.md +194 -0
  523. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/source-evidence-map.md +60 -0
  524. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-quality-release.md +190 -0
  525. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-ui-quality.md +83 -0
  526. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/SKILL.md +179 -0
  527. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/agents/openai.yaml +4 -0
  528. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/references/shared-branch-rebase.md +25 -0
  529. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/AGENTS.md +23 -0
  530. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/test_worktree_status.sh +207 -0
  531. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/test_worktree_sweep.sh +481 -0
  532. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/worktree-status.sh +325 -0
  533. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/worktree-sweep.sh +245 -0
  534. package/dist/assets/release.json +2797 -0
  535. package/dist/claude-adapter.d.ts +9 -0
  536. package/dist/claude-adapter.js +240 -0
  537. package/dist/cli-worker.d.ts +1 -0
  538. package/dist/cli-worker.js +32 -0
  539. package/dist/cli.d.ts +22 -0
  540. package/dist/cli.js +214 -0
  541. package/dist/codex-host.d.ts +30 -0
  542. package/dist/codex-host.js +162 -0
  543. package/dist/fs-safe.d.ts +21 -0
  544. package/dist/fs-safe.js +241 -0
  545. package/dist/index.d.ts +2 -0
  546. package/dist/index.js +1 -0
  547. package/dist/manifest.d.ts +8 -0
  548. package/dist/manifest.js +135 -0
  549. package/dist/opencode-adapter.d.ts +10 -0
  550. package/dist/opencode-adapter.js +416 -0
  551. package/dist/operations.d.ts +3 -0
  552. package/dist/operations.js +956 -0
  553. package/dist/paths.d.ts +20 -0
  554. package/dist/paths.js +4 -0
  555. package/dist/types.d.ts +58 -0
  556. package/dist/types.js +1 -0
  557. package/dist/unified.d.ts +4 -0
  558. package/dist/unified.js +64 -0
  559. package/dist/version.d.ts +2 -0
  560. package/dist/version.js +5 -0
  561. package/package.json +35 -0
@@ -0,0 +1,2144 @@
1
+ """Pytest suite for gen_report.py — covers pure functions that do not touch
2
+ lark-cli/Bitable/Feishu (those need subprocess mocking and live in a follow-up).
3
+
4
+ Run from this directory:
5
+ pytest test_gen_report.py -q
6
+
7
+ Or from the repo root:
8
+ pytest <skill-dir>/references/test_gen_report.py -q
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import json
14
+ import os
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+
20
+ sys.path.insert(0, str(Path(__file__).parent))
21
+ import gen_report as g # noqa: E402
22
+
23
+
24
+ # ── Sidecar ────────────────────────────────────────────────────────────────────
25
+
26
+ def test_load_sidecar_empty(tmp_path):
27
+ assert g.load_sidecar(tmp_path / "missing.jsonl") == {}
28
+
29
+
30
+ def test_load_sidecar_parses_and_normalizes(tmp_path):
31
+ p = tmp_path / "tc-map.jsonl"
32
+ p.write_text(
33
+ '{"test": "tests/auth.py::test_login", "tc_ids": ["TC-SY-001"]}\n'
34
+ '{"test": "TestBulk", "tc_ids": ["TC_SY_002", "TC-SY-003"]}\n'
35
+ '\n' # blank line tolerated
36
+ 'not-json line\n' # skipped
37
+ )
38
+ m = g.load_sidecar(p)
39
+ assert m == {
40
+ "tests/auth.py::test_login": ["TC-SY-001"],
41
+ "TestBulk": ["TC-SY-002", "TC-SY-003"], # underscores normalized
42
+ }
43
+
44
+
45
+ def test_load_sidecar_merges_duplicate_keys(tmp_path):
46
+ """Marker form + in-body form may both register the same test — merge,
47
+ don't overwrite."""
48
+ p = tmp_path / "tc-map.jsonl"
49
+ p.write_text(
50
+ '{"test": "x", "tc_ids": ["TC-A-001"]}\n'
51
+ '{"test": "x", "tc_ids": ["TC-B-002", "TC-A-001"]}\n' # second contains A, B
52
+ '{"test": "x", "tc_ids": ["TC-C-003"]}\n' # adds C
53
+ )
54
+ # Union, first-seen order preserved
55
+ assert g.load_sidecar(p) == {"x": ["TC-A-001", "TC-B-002", "TC-C-003"]}
56
+
57
+
58
+ def test_load_sidecar_merge_normalizes_underscores(tmp_path):
59
+ p = tmp_path / "tc-map.jsonl"
60
+ p.write_text(
61
+ '{"test": "x", "tc_ids": ["TC_A_001"]}\n'
62
+ '{"test": "x", "tc_ids": ["TC-A-001"]}\n' # same after normalisation
63
+ )
64
+ assert g.load_sidecar(p) == {"x": ["TC-A-001"]} # de-duplicated
65
+
66
+
67
+ # ── Truncate 信息流转 ─────────────────────────────────────────────────────────
68
+
69
+ def test_truncate_info_default_keeps_100(monkeypatch):
70
+ monkeypatch.delenv("TC_INFO_KEEP", raising=False)
71
+ info = "\n".join(f"[u {i}] line {i}" for i in range(150))
72
+ out = g._truncate_info(info)
73
+ lines = out.split("\n")
74
+ assert len(lines) == 100
75
+ assert lines[0] == "[u 50] line 50"
76
+ assert lines[-1] == "[u 149] line 149"
77
+
78
+
79
+ def test_truncate_info_unbounded_when_zero(monkeypatch):
80
+ monkeypatch.setenv("TC_INFO_KEEP", "0")
81
+ info = "\n".join(f"line {i}" for i in range(5000))
82
+ out = g._truncate_info(info)
83
+ assert len(out.split("\n")) == 5000 # unbounded
84
+
85
+
86
+ def test_str_handles_rich_text_segments():
87
+ # Bitable rich-text list of segments
88
+ assert g._str([{"type": "text", "text": "Hello "}, {"type": "text", "text": "world"}]) == "Hello world"
89
+
90
+
91
+ def test_str_handles_string_list():
92
+ assert g._str(["alpha", "beta"]) == "alphabeta"
93
+
94
+
95
+ def test_str_handles_select_option_dict():
96
+ assert g._str({"name": "P0"}) == "P0"
97
+
98
+
99
+ def test_str_handles_scalar_and_empty():
100
+ assert g._str("plain") == "plain"
101
+ assert g._str("") == ""
102
+ assert g._str(None) == ""
103
+ assert g._str(42) == "42"
104
+
105
+
106
+ def test_truncate_info_honors_env(monkeypatch):
107
+ monkeypatch.setenv("TC_INFO_KEEP", "5")
108
+ info = "\n".join(f"e{i}" for i in range(10))
109
+ assert g._truncate_info(info).split("\n") == ["e5", "e6", "e7", "e8", "e9"]
110
+
111
+
112
+ def test_truncate_info_short_unchanged(monkeypatch):
113
+ monkeypatch.delenv("TC_INFO_KEEP", raising=False)
114
+ info = "a\nb\nc"
115
+ assert g._truncate_info(info) == "a\nb\nc"
116
+
117
+
118
+ def test_truncate_info_invalid_env_falls_back_to_default(monkeypatch):
119
+ monkeypatch.setenv("TC_INFO_KEEP", "not-a-number")
120
+ info = "\n".join(str(i) for i in range(200))
121
+ assert len(g._truncate_info(info).split("\n")) == 100
122
+
123
+
124
+ # ── JUnit status mapping ──────────────────────────────────────────────────────
125
+
126
+ import xml.etree.ElementTree as ET # noqa: E402
127
+
128
+
129
+ def _testcase(name="t", classname="m", inner_xml=""):
130
+ return ET.fromstring(f'<testcase classname="{classname}" name="{name}">{inner_xml}</testcase>')
131
+
132
+
133
+ @pytest.mark.parametrize("reason,expected", [
134
+ ("requires linux", "阻塞"),
135
+ ("needs docker", "阻塞"),
136
+ ("no device available", "阻塞"),
137
+ ("service unavailable", "阻塞"),
138
+ ("credential not set", "阻塞"),
139
+ ("excluded for this iteration", "跳过"),
140
+ ("deprecated feature", "跳过"),
141
+ ("manually disabled", "跳过"),
142
+ ])
143
+ def test_junit_status_skip_heuristic(reason, expected):
144
+ el = _testcase(inner_xml=f'<skipped message="{reason}"/>')
145
+ assert g._junit_status(el) == expected
146
+
147
+
148
+ def test_junit_status_failure():
149
+ assert g._junit_status(_testcase(inner_xml="<failure>x</failure>")) == "失败"
150
+
151
+
152
+ def test_junit_status_error():
153
+ assert g._junit_status(_testcase(inner_xml="<error>x</error>")) == "失败"
154
+
155
+
156
+ def test_junit_status_pass():
157
+ assert g._junit_status(_testcase()) == "通过"
158
+
159
+
160
+ # ── parse_junit + sidecar join ────────────────────────────────────────────────
161
+
162
+ def test_parse_junit_sidecar_match_by_pytest_nodeid(tmp_path):
163
+ j = tmp_path / "j.xml"
164
+ j.write_text("""<?xml version="1.0"?>
165
+ <testsuites>
166
+ <testsuite name="s">
167
+ <testcase classname="tests.test_auth" name="test_login"/>
168
+ <testcase classname="pkg" name="TestBulk"><failure>x</failure></testcase>
169
+ </testsuite>
170
+ </testsuites>
171
+ """)
172
+ sidecar = {
173
+ "tests/test_auth.py::test_login": ["TC-SY-001"],
174
+ "TestBulk": ["TC-SY-002"],
175
+ }
176
+ out = g.parse_junit(str(j), sidecar)
177
+ assert out == {
178
+ "linked": {"TC-SY-001": "通过", "TC-SY-002": "失败"},
179
+ "untracked": [],
180
+ }
181
+
182
+
183
+ def test_parse_junit_untracked(tmp_path):
184
+ j = tmp_path / "j.xml"
185
+ j.write_text("""<?xml version="1.0"?>
186
+ <testsuite name="s">
187
+ <testcase classname="m" name="orphan_test"/>
188
+ <testcase classname="m" name="another"><failure>x</failure></testcase>
189
+ </testsuite>""")
190
+ out = g.parse_junit(str(j), {})
191
+ assert out["linked"] == {}
192
+ assert {u["name"] for u in out["untracked"]} == {"orphan_test", "another"}
193
+ assert {u["status"] for u in out["untracked"]} == {"通过", "失败"}
194
+
195
+
196
+ def test_parse_junit_pessimistic_merge_within_suite(tmp_path):
197
+ j = tmp_path / "j.xml"
198
+ j.write_text("""<?xml version="1.0"?>
199
+ <testsuite name="s">
200
+ <testcase classname="m" name="a"/>
201
+ <testcase classname="m" name="b"><failure>x</failure></testcase>
202
+ </testsuite>""")
203
+ sidecar = {"a": ["TC-X-001"], "b": ["TC-X-001"]}
204
+ out = g.parse_junit(str(j), sidecar)
205
+ assert out["linked"] == {"TC-X-001": "失败"}
206
+
207
+
208
+ def test_parse_junit_missing_file_returns_empty(capsys):
209
+ out = g.parse_junit("/nonexistent/path.xml", {})
210
+ assert out == {"linked": {}, "untracked": []}
211
+
212
+
213
+ # ── Coverage parsing ──────────────────────────────────────────────────────────
214
+
215
+ def test_parse_coverage_lines(tmp_path):
216
+ p = tmp_path / "c.xml"
217
+ p.write_text('<coverage line-rate="0.85" lines-covered="170" lines-valid="200"/>')
218
+ assert g.parse_coverage(str(p)) == {"covered": 170, "valid": 200, "rate": 0.85}
219
+
220
+
221
+ def test_parse_coverage_rate_only(tmp_path):
222
+ p = tmp_path / "c.xml"
223
+ p.write_text('<coverage line-rate="0.92"/>')
224
+ assert g.parse_coverage(str(p)) == {"rate": 0.92}
225
+
226
+
227
+ def test_parse_coverage_missing_file():
228
+ assert g.parse_coverage("/nonexistent/c.xml") is None
229
+
230
+
231
+ def test_parse_coverage_malformed_xml(tmp_path):
232
+ p = tmp_path / "c.xml"
233
+ p.write_text("<not-xml")
234
+ assert g.parse_coverage(str(p)) is None
235
+
236
+
237
+ def test_collect_coverage_sums_lines(tmp_path):
238
+ c1 = tmp_path / "c1.xml"
239
+ c1.write_text('<coverage lines-covered="100" lines-valid="200"/>')
240
+ c2 = tmp_path / "c2.xml"
241
+ c2.write_text('<coverage lines-covered="80" lines-valid="100"/>')
242
+ suites = [{"coverage_file": str(c1)}, {"coverage_file": str(c2)}]
243
+ agg = g.collect_coverage(suites)
244
+ assert agg["covered"] == 180
245
+ assert agg["valid"] == 300
246
+ assert abs(agg["rate"] - 0.6) < 1e-9
247
+
248
+
249
+ def test_collect_coverage_line_based_wins_over_rate_only(tmp_path):
250
+ c1 = tmp_path / "c1.xml"
251
+ c1.write_text('<coverage lines-covered="50" lines-valid="100"/>')
252
+ c2 = tmp_path / "c2.xml"
253
+ c2.write_text('<coverage line-rate="0.99"/>') # rate-only ignored when lines present
254
+ agg = g.collect_coverage([{"coverage_file": str(c1)}, {"coverage_file": str(c2)}])
255
+ assert agg == {"covered": 50, "valid": 100, "rate": 0.5}
256
+
257
+
258
+ def test_collect_coverage_none_when_no_files():
259
+ assert g.collect_coverage([{}, {"coverage_file": "/nonexistent.xml"}]) is None
260
+
261
+
262
+ # ── md parser ─────────────────────────────────────────────────────────────────
263
+
264
+ SAMPLE_MD = """# Some doc
265
+
266
+ Intro text — should be ignored.
267
+
268
+ | 用例ID | 模块 | 功能点 | 优先级 | 测试层级 | 测试类型 | 前置条件 | 操作步骤 | 预期结果 |
269
+ |---|---|---|---|---|---|---|---|---|
270
+ | TC-SY-001 | 系统 | 登录 | P0 | e2e | ui-automation | 已注册 | 1. 输入<br>2. 提交 | 跳转 |
271
+ | TC-SY-002 | 系统 | 含 \\| 转义 | P1 | contract | api-automation | | | |
272
+
273
+ Trailing text — ignored.
274
+ """
275
+
276
+
277
+ def test_parse_md_table(tmp_path):
278
+ p = tmp_path / "cases.md"
279
+ p.write_text(SAMPLE_MD)
280
+ rows = g.parse_md_table(str(p))
281
+ assert set(rows) == {"TC-SY-001", "TC-SY-002"}
282
+ assert rows["TC-SY-001"]["操作步骤"] == "1. 输入\n2. 提交"
283
+ assert rows["TC-SY-001"]["测试层级"] == "e2e"
284
+ assert rows["TC-SY-002"]["测试类型"] == "api-automation"
285
+ assert rows["TC-SY-002"]["功能点"] == "含 | 转义"
286
+
287
+
288
+ def test_parse_old_md_table_does_not_invent_new_definition_fields(tmp_path):
289
+ p = tmp_path / "old-cases.md"
290
+ p.write_text(
291
+ "| 用例ID | 模块 | 功能点 | 优先级 | 前置条件 | 操作步骤 | 预期结果 |\n"
292
+ "|---|---|---|---|---|---|---|\n"
293
+ "| TC-OLD-001 | 系统 | 登录 | P0 | 已注册 | 提交 | 跳转 |\n"
294
+ )
295
+ rows = g.parse_md_table(str(p))
296
+ assert "测试层级" not in rows["TC-OLD-001"]
297
+ assert "测试类型" not in rows["TC-OLD-001"]
298
+ assert rows.definition_fields_present == {"模块", "功能点", "优先级", "前置条件", "操作步骤", "预期结果"}
299
+
300
+
301
+ def test_parse_md_table_no_header_warns(tmp_path, capsys):
302
+ p = tmp_path / "x.md"
303
+ p.write_text("no tables here, just prose")
304
+ assert g.parse_md_table(str(p)) == {}
305
+ err = capsys.readouterr().err
306
+ assert "no markdown table header" in err
307
+
308
+
309
+ def test_split_md_row_handles_escape():
310
+ cells = g._split_md_row("| a | b \\| c | d |")
311
+ assert cells == ["a", "b | c", "d"]
312
+
313
+
314
+ def test_md_cell_escapes_pipe_and_newline():
315
+ assert g._md_cell("a|b") == "a\\|b"
316
+ assert g._md_cell("a\nb") == "a<br>b"
317
+ assert g._md_cell("a\r\nb") == "a<br>b"
318
+ assert g._md_cell("") == ""
319
+ assert g._md_cell("plain") == "plain"
320
+
321
+
322
+ def test_md_cell_roundtrip_through_parser():
323
+ # Emit with _md_cell, parse with _split_md_row, original pipe survives
324
+ line = "| " + " | ".join(g._md_cell(c) for c in ["a|x", "b", "c"]) + " |"
325
+ assert g._split_md_row(line) == ["a|x", "b", "c"]
326
+
327
+
328
+ # ── diff_md_vs_bitable ────────────────────────────────────────────────────────
329
+
330
+ def _rec(tc_id, status="未测试", **fields):
331
+ f = {"用例ID": tc_id, "状态": status, "模块": "", "功能点": "",
332
+ "优先级": "", "测试层级": "", "测试类型": "", "前置条件": "", "操作步骤": "", "预期结果": ""}
333
+ f.update(fields)
334
+ return {"record_id": f"r-{tc_id}", "fields": f}
335
+
336
+
337
+ def test_diff_added_removed_changed_and_deprecated_exclusion():
338
+ md = {
339
+ "TC-1": {"模块": "M", "功能点": "f1", "优先级": "P0", "前置条件": "", "操作步骤": "step", "预期结果": "ok"},
340
+ "TC-2": {"模块": "M", "功能点": "f2", "优先级": "P1", "前置条件": "", "操作步骤": "step", "预期结果": "ok md"},
341
+ "TC-3": {"模块": "M", "功能点": "f3", "优先级": "P2", "前置条件": "", "操作步骤": "", "预期结果": ""},
342
+ }
343
+ records = [
344
+ _rec("TC-1", 模块="M", 功能点="f1", 优先级="P0", 前置条件="", 操作步骤="step", 预期结果="ok"),
345
+ _rec("TC-2", 模块="M", 功能点="f2", 优先级="P1", 前置条件="", 操作步骤="step", 预期结果="ok BITABLE"),
346
+ # TC-3 missing from Bitable → added
347
+ _rec("TC-4", 模块="M", 功能点="f4", 优先级="P0"), # in Bitable, not md → removed
348
+ _rec("TC-5", status="废弃", 模块="M", 功能点="f5"), # 废弃 → excluded from removed
349
+ ]
350
+ diff = g.diff_md_vs_bitable(md, records)
351
+ assert [a["tc_id"] for a in diff["added"]] == ["TC-3"]
352
+ assert [r["tc_id"] for r in diff["removed"]] == ["TC-4"] # TC-5 excluded
353
+ assert [(c["tc_id"], c["field"]) for c in diff["changed"]] == [("TC-2", "预期结果")]
354
+
355
+
356
+ def test_diff_whitespace_normalized():
357
+ md = {"TC-1": {"模块": "M", "功能点": "f", "优先级": "P0", "前置条件": "", "操作步骤": " a b\n c ", "预期结果": ""}}
358
+ records = [_rec("TC-1", 模块="M", 功能点="f", 优先级="P0", 操作步骤="a b c")]
359
+ diff = g.diff_md_vs_bitable(md, records)
360
+ assert diff["changed"] == [] # whitespace differences ignored
361
+
362
+
363
+ def test_diff_detects_test_layer_and_test_type_changes():
364
+ md = {
365
+ "TC-1": {
366
+ "模块": "M",
367
+ "功能点": "f",
368
+ "优先级": "P0",
369
+ "测试层级": "e2e",
370
+ "测试类型": "ui-automation",
371
+ "前置条件": "",
372
+ "操作步骤": "step",
373
+ "预期结果": "ok",
374
+ }
375
+ }
376
+ records = [
377
+ _rec("TC-1", 模块="M", 功能点="f", 优先级="P0",
378
+ 测试层级="contract", 测试类型="api-automation",
379
+ 前置条件="", 操作步骤="step", 预期结果="ok")
380
+ ]
381
+ diff = g.diff_md_vs_bitable(md, records)
382
+ assert [(c["tc_id"], c["field"]) for c in diff["changed"]] == [
383
+ ("TC-1", "测试层级"),
384
+ ("TC-1", "测试类型"),
385
+ ]
386
+
387
+
388
+ def test_diff_skips_new_definition_fields_when_old_md_lacks_columns():
389
+ md = g.MdTable({
390
+ "TC-1": {
391
+ "模块": "M",
392
+ "功能点": "f",
393
+ "优先级": "P0",
394
+ "前置条件": "",
395
+ "操作步骤": "step",
396
+ "预期结果": "ok",
397
+ }
398
+ }, definition_fields_present={"模块", "功能点", "优先级", "前置条件", "操作步骤", "预期结果"})
399
+ records = [
400
+ _rec("TC-1", 模块="M", 功能点="f", 优先级="P0",
401
+ 测试层级="e2e", 测试类型="ui-automation",
402
+ 前置条件="", 操作步骤="step", 预期结果="ok")
403
+ ]
404
+ diff = g.diff_md_vs_bitable(md, records)
405
+ assert diff["changed"] == []
406
+
407
+
408
+ def test_diff_detects_missing_new_fields_when_md_schema_unknown():
409
+ md = {
410
+ "TC-1": {
411
+ "模块": "M",
412
+ "功能点": "f",
413
+ "优先级": "P0",
414
+ "前置条件": "",
415
+ "操作步骤": "step",
416
+ "预期结果": "ok",
417
+ }
418
+ }
419
+ records = [
420
+ _rec("TC-1", 模块="M", 功能点="f", 优先级="P0",
421
+ 测试层级="e2e", 测试类型="ui-automation",
422
+ 前置条件="", 操作步骤="step", 预期结果="ok")
423
+ ]
424
+ diff = g.diff_md_vs_bitable(md, records)
425
+ assert [(c["tc_id"], c["field"]) for c in diff["changed"]] == [
426
+ ("TC-1", "测试层级"),
427
+ ("TC-1", "测试类型"),
428
+ ]
429
+
430
+
431
+ # ── detect_orphans ────────────────────────────────────────────────────────────
432
+
433
+ def test_detect_orphans():
434
+ records = [
435
+ _rec("TC-A", status="通过"),
436
+ _rec("TC-B", status="废弃"),
437
+ ]
438
+ results = {"TC-A": "通过", "TC-B": "失败", "TC-C": "通过"}
439
+ o = g.detect_orphans(records, results)
440
+ assert o["deprecated_in_tests"] == ["TC-B"]
441
+ assert o["missing_in_bitable"] == ["TC-C"]
442
+
443
+
444
+ def test_detect_orphans_clean():
445
+ records = [_rec("TC-A", status="通过")]
446
+ o = g.detect_orphans(records, {"TC-A": "通过"})
447
+ assert o == {"deprecated_in_tests": [], "missing_in_bitable": []}
448
+
449
+
450
+ # ── aggregate ─────────────────────────────────────────────────────────────────
451
+
452
+ def test_aggregate_excludes_deprecated_from_total():
453
+ records = [
454
+ _rec("TC-1", status="通过", 模块="M", 优先级="P0"),
455
+ _rec("TC-2", status="失败", 模块="M", 优先级="P1"),
456
+ _rec("TC-3", status="废弃", 模块="M", 优先级="P2"),
457
+ ]
458
+ agg = g.aggregate(records)
459
+ assert agg["total"] == 2 # 废弃 not counted
460
+ assert agg["deprecated_count"] == 1
461
+ assert agg["by_status"]["通过"] == 1
462
+ assert agg["by_status"]["失败"] == 1
463
+ assert agg["pass_rate"] == "50.0%"
464
+
465
+
466
+ def test_aggregate_p0_rate():
467
+ records = [
468
+ _rec("TC-1", status="通过", 优先级="P0"),
469
+ _rec("TC-2", status="通过", 优先级="P0"),
470
+ _rec("TC-3", status="失败", 优先级="P0"),
471
+ _rec("TC-4", status="通过", 优先级="P1"),
472
+ ]
473
+ agg = g.aggregate(records)
474
+ assert agg["p0_total"] == 3
475
+ assert agg["p0_pass"] == 2
476
+ assert agg["p0_pass_rate"] == "66.7%"
477
+
478
+
479
+ # ── print_inventory / next_id (capsys) ────────────────────────────────────────
480
+
481
+ def test_print_inventory_human(capsys):
482
+ records = [
483
+ _rec("TC-SY-001", status="通过", 模块="系统", 功能点="登录", 优先级="P0"),
484
+ _rec("TC-SY-002", status="未测试", 模块="系统", 功能点="注销", 优先级="P1"),
485
+ _rec("TC-OLD-001", status="废弃", 模块="系统", 功能点="旧"),
486
+ ]
487
+ g.print_inventory(records, as_md=False)
488
+ out = capsys.readouterr().out
489
+ assert "active: 2" in out
490
+ assert "废弃: 1" in out
491
+ assert "TC-SY-001" in out
492
+ assert "TC-OLD-001" in out # in deprecated section
493
+
494
+
495
+ def test_print_inventory_md(capsys):
496
+ records = [_rec("TC-X-001", status="通过", 模块="X", 功能点="f", 优先级="P0",
497
+ 测试层级="e2e", 测试类型="ui-automation",
498
+ 操作步骤="a\nb")]
499
+ g.print_inventory(records, as_md=True)
500
+ out = capsys.readouterr().out
501
+ assert "| 用例ID | 模块" in out
502
+ assert "TC-X-001 | X | f" in out
503
+ assert "e2e | ui-automation" in out
504
+ assert "a<br>b" in out # newlines collapsed for md cell
505
+
506
+
507
+ def test_aggregate_tracks_layer_and_test_type_breakdowns():
508
+ records = [
509
+ _rec("TC-1", status="通过", 模块="M", 优先级="P0", 测试层级="e2e", 测试类型="ui-automation"),
510
+ _rec("TC-2", status="失败", 模块="M", 优先级="P1", 测试层级="contract", 测试类型="api-automation"),
511
+ _rec("TC-3", status="未测试", 模块="N", 优先级="P2", 测试层级="manual", 测试类型="manual-verification"),
512
+ ]
513
+ agg = g.aggregate(records)
514
+ assert agg["by_layer"]["e2e"]["通过"] == 1
515
+ assert agg["by_layer"]["contract"]["失败"] == 1
516
+ assert agg["by_test_type"]["ui-automation"]["通过"] == 1
517
+ assert agg["by_test_type"]["manual-verification"]["未测试"] == 1
518
+
519
+
520
+ def test_aggregate_omits_layer_and_test_type_sections_when_fields_missing():
521
+ agg = g.aggregate([
522
+ _rec("TC-1", status="通过", 模块="M", 优先级="P0"),
523
+ _rec("TC-2", status="失败", 模块="M", 优先级="P1"),
524
+ ])
525
+ assert agg["by_layer"] == {}
526
+ assert agg["by_test_type"] == {}
527
+ md = g.build_markdown(agg, "alice", "本地", "v1")
528
+ assert "按测试层级统计" not in md
529
+ assert "按测试类型统计" not in md
530
+
531
+
532
+ def test_print_next_ids(capsys):
533
+ records = [
534
+ _rec("TC-SY-005"),
535
+ _rec("TC-AU-010"),
536
+ _rec("weird-id"),
537
+ ]
538
+ g.print_next_ids(records)
539
+ out = capsys.readouterr().out
540
+ assert "TC-AU-011" in out
541
+ assert "TC-SY-006" in out
542
+ assert "weird-id" in out # surfaced as non-canonical
543
+
544
+
545
+ # ── vs-last-run diff ──────────────────────────────────────────────────────────
546
+
547
+ def test_diff_vs_last_run_first_time():
548
+ diff = g.diff_vs_last_run({"TC-A": "通过"}, {})
549
+ assert diff["had_previous"] is False
550
+ assert diff["newly_seen"] == ["TC-A"]
551
+ assert diff["new_failures"] == []
552
+
553
+
554
+ def test_diff_vs_last_run_new_failure():
555
+ prev = {"linked": {"TC-A": "通过", "TC-B": "通过"}}
556
+ cur = {"TC-A": "通过", "TC-B": "失败"}
557
+ diff = g.diff_vs_last_run(cur, prev)
558
+ assert diff["new_failures"] == ["TC-B"]
559
+ assert diff["fixed"] == []
560
+ assert diff["flips"] == []
561
+
562
+
563
+ def test_diff_vs_last_run_fixed():
564
+ prev = {"linked": {"TC-A": "失败"}}
565
+ diff = g.diff_vs_last_run({"TC-A": "通过"}, prev)
566
+ assert diff["fixed"] == ["TC-A"]
567
+
568
+
569
+ def test_diff_vs_last_run_flip_other():
570
+ prev = {"linked": {"TC-A": "通过"}}
571
+ diff = g.diff_vs_last_run({"TC-A": "跳过"}, prev)
572
+ assert diff["new_failures"] == []
573
+ assert diff["fixed"] == []
574
+ assert diff["flips"] == [{"tc_id": "TC-A", "from": "通过", "to": "跳过"}]
575
+
576
+
577
+ def test_diff_vs_last_run_no_longer_seen():
578
+ prev = {"linked": {"TC-A": "通过", "TC-B": "通过"}}
579
+ diff = g.diff_vs_last_run({"TC-A": "通过"}, prev)
580
+ assert diff["no_longer_seen"] == ["TC-B"]
581
+
582
+
583
+ def test_diff_vs_last_run_newly_seen_failing():
584
+ prev = {"linked": {"TC-A": "通过"}}
585
+ diff = g.diff_vs_last_run({"TC-A": "通过", "TC-B": "失败"}, prev)
586
+ assert diff["newly_seen"] == ["TC-B"]
587
+ assert diff["new_failures"] == ["TC-B"] # newly seen AND failing counts as new failure
588
+
589
+
590
+ def test_save_and_load_last_run(tmp_path):
591
+ p = tmp_path / "last.json"
592
+ g.save_last_run(p, {"TC-A": "通过", "TC-B": "失败"})
593
+ assert p.exists()
594
+ loaded = g.load_last_run(p)
595
+ assert loaded["linked"] == {"TC-A": "通过", "TC-B": "失败"}
596
+ assert "timestamp" in loaded
597
+
598
+
599
+ def test_load_last_run_missing_file_returns_empty(tmp_path):
600
+ assert g.load_last_run(tmp_path / "missing.json") == {}
601
+
602
+
603
+ def test_load_last_run_corrupt_file_returns_empty(tmp_path):
604
+ p = tmp_path / "corrupt.json"
605
+ p.write_text("not valid json {")
606
+ assert g.load_last_run(p) == {}
607
+
608
+
609
+ def test_load_last_run_rejects_old_schema(tmp_path):
610
+ """Snapshots without schema_version (or with an older one) are treated as
611
+ cold-start so a schema change (e.g. minimal-mode namespace prefixes) won't
612
+ flood the next report with phantom newly_seen / no_longer_seen entries."""
613
+ p = tmp_path / "old.json"
614
+ # Old format: no schema_version field
615
+ p.write_text(json.dumps({"timestamp": "x", "linked": {"TC-A": "通过"}}))
616
+ assert g.load_last_run(p) == {}
617
+
618
+
619
+ def test_save_last_run_writes_current_schema(tmp_path):
620
+ p = tmp_path / "lr.json"
621
+ g.save_last_run(p, {"TC-A": "通过"})
622
+ snap = json.loads(p.read_text())
623
+ assert snap["schema_version"] == g.LAST_RUN_SCHEMA_VERSION
624
+
625
+
626
+ def test_load_config_rejects_half_populated_bitable(tmp_path):
627
+ p = tmp_path / ".report-config.json"
628
+ p.write_text(json.dumps({"base_token": "BAS123", "table_id": ""}))
629
+ with pytest.raises(ValueError, match="both be set"):
630
+ g.load_config(str(p))
631
+
632
+
633
+ def test_load_config_accepts_minimal_mode(tmp_path):
634
+ p = tmp_path / ".report-config.json"
635
+ p.write_text(json.dumps({"base_token": "", "table_id": "", "test_suites": []}))
636
+ cfg = g.load_config(str(p))
637
+ assert cfg == {"base_token": "", "table_id": "", "test_suites": []}
638
+
639
+
640
+ def test_collect_results_flags_missing_files(tmp_path, monkeypatch):
641
+ """Suites configured but JUnit XML missing → tracked in missing_files for the gate."""
642
+ monkeypatch.setenv("TC_SIDECAR", str(tmp_path / "tc-map.jsonl"))
643
+ suites = [
644
+ {"name": "s1", "results_file": str(tmp_path / "exists.xml")},
645
+ {"name": "s2", "results_file": str(tmp_path / "absent.xml")},
646
+ ]
647
+ (tmp_path / "exists.xml").write_text(
648
+ '<?xml version="1.0"?><testsuite name="s1"><testcase classname="m" name="t1"/></testsuite>'
649
+ )
650
+ r = g.collect_results(suites)
651
+ assert r["parsed_suites"] == 1
652
+ assert r["missing_files"] == [str(tmp_path / "absent.xml")]
653
+
654
+
655
+ def test_residual_risk_section_all_green():
656
+ agg = {"by_module": {}, "by_status": {}, "p0_total": 0, "p0_pass": 0, "p0_blocking": []}
657
+ md = "\n".join(g._residual_risk_section(agg, [], {"missing_files": []}))
658
+ assert "覆盖与残余风险" in md
659
+ assert "所有活跃模块均无失败" in md
660
+
661
+
662
+ def test_residual_risk_section_surfaces_weak_modules():
663
+ agg = {
664
+ "by_module": {"系统": {"_total": 5, "通过": 3, "失败": 1, "未测试": 1}},
665
+ "by_status": {}, "p0_total": 0, "p0_pass": 0, "p0_blocking": [],
666
+ }
667
+ md = "\n".join(g._residual_risk_section(agg, [], {"missing_files": []}))
668
+ assert "模块未达「绿色」标准" in md
669
+ assert "系统" in md
670
+
671
+
672
+ def test_residual_risk_section_calls_out_infra_failure():
673
+ agg = {"by_module": {}, "by_status": {}, "p0_total": 0, "p0_pass": 0, "p0_blocking": []}
674
+ md = "\n".join(g._residual_risk_section(agg, [], {"missing_files": ["/a.xml", "/b.xml"]}))
675
+ assert "🚨 基础设施失败" in md
676
+ assert "/a.xml" in md
677
+ assert "/b.xml" in md
678
+
679
+
680
+ def test_parse_matrix_tcs(tmp_path):
681
+ p = tmp_path / "test-matrix.md"
682
+ p.write_text("""# Test Matrix
683
+
684
+ | 模块 | unit | contract | integration | e2e | manual | blocked |
685
+ |---|---|---|---|---|---|---|
686
+ | 登录 | TC-SY-001 | — | TC-SY-002 | TC-SY-003 | — | — |
687
+ | 支付 | TC-PY-001, TC-PY-002 | TC-PY-006 | — | — | TC-PY-008 | TC-PY-009 (wait sandbox · @alice) |
688
+ """)
689
+ out = g.parse_matrix_tcs(str(p))
690
+ assert "TC-SY-001" in out["unit"]
691
+ assert "TC-PY-001" in out["unit"] and "TC-PY-002" in out["unit"]
692
+ assert out["e2e"] == ["TC-SY-003"]
693
+ assert out["blocked"] == ["TC-PY-009"]
694
+ assert out.get("contract") == ["TC-PY-006"]
695
+
696
+
697
+ def test_validate_matrix_drift(tmp_path):
698
+ p = tmp_path / "matrix.md"
699
+ p.write_text("""
700
+ | 模块 | unit | integration |
701
+ |---|---|---|
702
+ | sys | TC-SY-001, TC-SY-999 | TC-SY-003 |
703
+ """)
704
+ records = [
705
+ _rec("TC-SY-001"), # in matrix + bitable → ok
706
+ _rec("TC-SY-002"), # in bitable, NOT in matrix
707
+ _rec("TC-SY-003", status="废弃"), # in matrix but 废弃
708
+ # TC-SY-999 absent everywhere except matrix
709
+ ]
710
+ r = g.validate_matrix(str(p), records)
711
+ assert r["in_matrix_not_bitable"] == ["TC-SY-999"]
712
+ assert r["in_bitable_not_matrix"] == ["TC-SY-002"]
713
+ assert r["deprecated_in_matrix"] == ["TC-SY-003"]
714
+ assert r["layer_mismatches"] == []
715
+
716
+
717
+ def test_validate_matrix_layer_enforcement_is_optional(tmp_path):
718
+ p = tmp_path / "matrix.md"
719
+ p.write_text("""
720
+ | 模块 | unit | e2e |
721
+ |---|---|---|
722
+ | sys | TC-SY-001 | TC-SY-002 |
723
+ """)
724
+ records = [_rec("TC-SY-001"), _rec("TC-SY-002")]
725
+ r = g.validate_matrix(str(p), records)
726
+ assert r["layer_mismatches"] == []
727
+ assert r["actual_tcs_by_layer"] == {}
728
+ assert r["unverified_layers"] == ["e2e", "unit"]
729
+
730
+
731
+ def test_validate_matrix_configured_layers_require_evidence(tmp_path):
732
+ p = tmp_path / "matrix.md"
733
+ p.write_text("""
734
+ | 模块 | unit |
735
+ |---|---|
736
+ | sys | TC-SY-001 |
737
+ """)
738
+ records = [_rec("TC-SY-001")]
739
+ r = g.validate_matrix(str(p), records, actual_layers={}, configured_layers=["unit"])
740
+ assert r["configured_layers"] == ["unit"]
741
+ assert r["layer_mismatches"] == [
742
+ {"tc_id": "TC-SY-001", "declared": "unit", "actual": []},
743
+ ]
744
+
745
+
746
+ def test_validate_matrix_only_enforces_configured_layers(tmp_path):
747
+ p = tmp_path / "matrix.md"
748
+ p.write_text("""
749
+ | 模块 | unit | e2e |
750
+ |---|---|---|
751
+ | sys | TC-SY-001 | TC-SY-002 |
752
+ """)
753
+ records = [_rec("TC-SY-001"), _rec("TC-SY-002")]
754
+ r = g.validate_matrix(str(p), records, actual_layers={"unit": ["TC-SY-001"]}, configured_layers=["unit"])
755
+ assert r["configured_layers"] == ["unit"]
756
+ assert r["layer_mismatches"] == []
757
+ assert r["unverified_layers"] == ["e2e"]
758
+
759
+
760
+ def test_validate_matrix_layer_mismatch_when_actual_layers_configured(tmp_path):
761
+ p = tmp_path / "matrix.md"
762
+ p.write_text("""
763
+ | 模块 | unit | e2e | manual | blocked |
764
+ |---|---|---|---|---|
765
+ | sys | TC-SY-001 | TC-SY-002 | TC-SY-003 | TC-SY-004 |
766
+ """)
767
+ records = [_rec("TC-SY-001"), _rec("TC-SY-002"), _rec("TC-SY-003"), _rec("TC-SY-004")]
768
+ r = g.validate_matrix(
769
+ str(p),
770
+ records,
771
+ {"unit": ["TC-SY-001"], "contract": ["TC-SY-002"]},
772
+ configured_layers=["unit", "e2e"],
773
+ )
774
+ assert r["layer_mismatches"] == [
775
+ {"tc_id": "TC-SY-002", "declared": "e2e", "actual": ["contract"]},
776
+ ]
777
+
778
+
779
+ def test_validate_matrix_layer_aliases(tmp_path):
780
+ p = tmp_path / "matrix.md"
781
+ p.write_text("""
782
+ | 模块 | contract | e2e |
783
+ |---|---|---|
784
+ | sys | TC-SY-001 | TC-SY-002 |
785
+ """)
786
+ records = [_rec("TC-SY-001"), _rec("TC-SY-002")]
787
+ r = g.validate_matrix(str(p), records, {"api": ["TC-SY-001"], "host-smoke": ["TC-SY-002"]})
788
+ assert r["matrix_tcs_by_layer"] == {"contract": ["TC-SY-001"], "e2e": ["TC-SY-002"]}
789
+ assert r["layer_mismatches"] == []
790
+
791
+
792
+ def test_parse_matrix_tcs_does_not_treat_suite_alias_headers_as_layer_claims(tmp_path):
793
+ p = tmp_path / "matrix.md"
794
+ p.write_text("""
795
+ | 模块 | smoke | host | api |
796
+ |---|---|---|
797
+ | sys | TC-SY-001 | TC-SY-002 | TC-SY-003 |
798
+ """)
799
+ assert g.parse_matrix_tcs(str(p)) == {}
800
+
801
+
802
+ def test_extra_matrix_columns_count_for_presence_not_layer_enforcement(tmp_path):
803
+ p = tmp_path / "matrix.md"
804
+ p.write_text("""
805
+ | 模块 | unit | fuzz |
806
+ |---|---|---|
807
+ | sys | TC-SY-001 | TC-SY-002 |
808
+ """)
809
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-SY-002")])
810
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-SY-001"]}
811
+ assert r["in_bitable_not_matrix"] == []
812
+
813
+
814
+ def test_non_evidence_matrix_columns_do_not_count_for_presence(tmp_path):
815
+ p = tmp_path / "matrix.md"
816
+ p.write_text("""
817
+ | 模块 | unit | 备注 |
818
+ |---|---|---|
819
+ | sys | TC-SY-001 | renamed from TC-SY-OLD |
820
+ """)
821
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001")])
822
+ assert r["in_matrix_not_bitable"] == []
823
+
824
+
825
+ def test_validate_matrix_unparseable_header_is_parse_error_not_total_missing(tmp_path):
826
+ p = tmp_path / "matrix.md"
827
+ p.write_text("""
828
+ | 模块 | 冒烟 | 主机 |
829
+ |---|---|---|
830
+ | sys | TC-SY-001 | TC-SY-002 |
831
+ """)
832
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-SY-002")])
833
+ assert r["parse_error"] == "no canonical matrix layer header found"
834
+ assert r["in_bitable_not_matrix"] == []
835
+ assert g._matrix_validation_has_drift(r)
836
+
837
+
838
+ def test_validate_matrix_row_cell_count_mismatch_is_parse_error(tmp_path):
839
+ p = tmp_path / "matrix.md"
840
+ p.write_text("""
841
+ | 模块 | unit |
842
+ |---|---|
843
+ | sys | TC-SY-001 | TC-SY-002 |
844
+ """)
845
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-SY-002")])
846
+ assert r["parse_error"] == "matrix row 4 has 3 cells; header has 2"
847
+ assert r["in_bitable_not_matrix"] == []
848
+ assert g._matrix_validation_has_drift(r)
849
+
850
+
851
+ def test_validate_matrix_ragged_row_with_missing_trailing_cells_is_valid(tmp_path):
852
+ p = tmp_path / "matrix.md"
853
+ p.write_text("""
854
+ | 模块 | unit | contract | integration | e2e | manual | blocked |
855
+ |---|---|---|---|---|---|---|
856
+ | 登录 | TC-SY-001 |
857
+ """)
858
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001")])
859
+ assert r["parse_error"] == ""
860
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-SY-001"]}
861
+ assert r["in_bitable_not_matrix"] == []
862
+
863
+
864
+ def test_validate_matrix_escaped_pipe_in_cell_does_not_split_column(tmp_path):
865
+ p = tmp_path / "matrix.md"
866
+ p.write_text(r"""
867
+ | 模块 | unit | manual |
868
+ |---|---|---|
869
+ | pay | TC-PY-001 (cert a\|b) | TC-PY-002 |
870
+ """)
871
+ r = g.validate_matrix(str(p), [_rec("TC-PY-001"), _rec("TC-PY-002")])
872
+ assert r["parse_error"] == ""
873
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-PY-001"], "manual": ["TC-PY-002"]}
874
+ assert r["in_bitable_not_matrix"] == []
875
+
876
+
877
+ def test_validate_matrix_multiple_tables_do_not_share_header(tmp_path):
878
+ p = tmp_path / "matrix.md"
879
+ p.write_text("""
880
+ | 模块 | unit |
881
+ |---|---|
882
+ | sys | TC-SY-001 |
883
+
884
+ | 模块 | unit | e2e |
885
+ |---|---|---|
886
+ | pay | TC-PY-001 | TC-PY-002 |
887
+ """)
888
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-PY-001"), _rec("TC-PY-002")])
889
+ assert r["parse_error"] == ""
890
+ assert r["in_bitable_not_matrix"] == []
891
+
892
+
893
+ def test_validate_matrix_adjacent_tables_without_blank_line_do_not_share_header(tmp_path):
894
+ p = tmp_path / "matrix.md"
895
+ p.write_text("""
896
+ | 模块 | unit |
897
+ |---|---|
898
+ | sys | TC-SY-001 |
899
+ | 模块 | e2e |
900
+ |---|---|
901
+ | pay | TC-PY-002 |
902
+ """)
903
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-PY-002")])
904
+ assert r["parse_error"] == ""
905
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-SY-001"], "e2e": ["TC-PY-002"]}
906
+ assert r["in_bitable_not_matrix"] == []
907
+
908
+
909
+ def test_validate_matrix_data_cell_named_like_layer_is_not_header(tmp_path):
910
+ p = tmp_path / "matrix.md"
911
+ p.write_text("""
912
+ | 模块 | unit | 备注 |
913
+ |---|---|---|
914
+ | sys | TC-SY-001 | e2e |
915
+ | pay | TC-PY-001 | manual |
916
+ """)
917
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-PY-001")])
918
+ assert r["parse_error"] == ""
919
+ assert r["in_bitable_not_matrix"] == []
920
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-SY-001", "TC-PY-001"]}
921
+
922
+
923
+ def test_validate_matrix_bare_layer_words_in_data_row_do_not_reset_header(tmp_path):
924
+ p = tmp_path / "matrix.md"
925
+ p.write_text("""
926
+ | 模块 | unit | e2e |
927
+ |---|---|---|
928
+ | pay | manual | blocked |
929
+ | auth | TC-SY-001 | TC-SY-002 |
930
+ """)
931
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-SY-002")])
932
+ assert r["parse_error"] == ""
933
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-SY-001"], "e2e": ["TC-SY-002"]}
934
+ assert r["in_bitable_not_matrix"] == []
935
+
936
+
937
+ def test_validate_matrix_single_layer_header_with_noncanonical_subject(tmp_path):
938
+ p = tmp_path / "matrix.md"
939
+ p.write_text("""
940
+ | 服务 | unit |
941
+ |---|---|
942
+ | auth | TC-SY-001 |
943
+ """)
944
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001")])
945
+ assert r["parse_error"] == ""
946
+ assert r["matrix_tcs_by_layer"] == {"unit": ["TC-SY-001"]}
947
+ assert r["in_bitable_not_matrix"] == []
948
+
949
+
950
+ def test_validate_matrix_empty_declared_layer_is_not_unverified(tmp_path):
951
+ p = tmp_path / "matrix.md"
952
+ p.write_text("""
953
+ | 模块 | unit | e2e |
954
+ |---|---|---|
955
+ | sys | TC-SY-001 | — |
956
+ """)
957
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001")], configured_layers=["unit"])
958
+ assert r["unverified_layers"] == []
959
+
960
+
961
+ def test_validate_matrix_missing_file_is_parse_error(tmp_path):
962
+ r = g.validate_matrix(str(tmp_path / "missing.md"), [_rec("TC-SY-001")])
963
+ assert r["parse_error"] == "matrix file not found"
964
+ assert r["in_bitable_not_matrix"] == []
965
+ assert g._matrix_validation_has_drift(r)
966
+
967
+
968
+ def test_validate_matrix_empty_canonical_matrix_reports_missing_tcs(tmp_path):
969
+ p = tmp_path / "matrix.md"
970
+ p.write_text("""
971
+ | 模块 | unit | e2e |
972
+ |---|---|---|
973
+ """)
974
+ r = g.validate_matrix(str(p), [_rec("TC-SY-001"), _rec("TC-SY-002")])
975
+ assert r["parse_error"] == ""
976
+ assert r["in_bitable_not_matrix"] == ["TC-SY-001", "TC-SY-002"]
977
+
978
+
979
+ def test_validate_matrix_unlabeled_suite_evidence_is_reported(tmp_path):
980
+ p = tmp_path / "matrix.md"
981
+ p.write_text("""
982
+ | 模块 | unit |
983
+ |---|---|
984
+ | sys | TC-SY-001 |
985
+ """)
986
+ records = [_rec("TC-SY-001")]
987
+ r = g.validate_matrix(
988
+ str(p),
989
+ records,
990
+ actual_layers={},
991
+ configured_layers=["unit"],
992
+ unlayered_tcs=["TC-SY-001"],
993
+ )
994
+ assert r["layer_mismatches"] == [
995
+ {"tc_id": "TC-SY-001", "declared": "unit", "actual": ["unlabeled-suite"]},
996
+ ]
997
+
998
+
999
+ def test_matrix_validation_has_drift():
1000
+ assert not g._matrix_validation_has_drift({
1001
+ "in_matrix_not_bitable": [],
1002
+ "in_bitable_not_matrix": [],
1003
+ "deprecated_in_matrix": [],
1004
+ "layer_mismatches": [],
1005
+ })
1006
+ assert g._matrix_validation_has_drift({
1007
+ "in_matrix_not_bitable": [],
1008
+ "in_bitable_not_matrix": [],
1009
+ "deprecated_in_matrix": [],
1010
+ "layer_mismatches": [{"tc_id": "TC-SY-001"}],
1011
+ })
1012
+ assert not g._matrix_validation_has_drift({
1013
+ "in_matrix_not_bitable": [],
1014
+ "in_bitable_not_matrix": [],
1015
+ "deprecated_in_matrix": [],
1016
+ "layer_mismatches": [],
1017
+ "unverified_layers": ["e2e"],
1018
+ })
1019
+
1020
+
1021
+ def test_matrix_gate_should_fail_on_missing_results():
1022
+ report = {
1023
+ "in_matrix_not_bitable": [],
1024
+ "in_bitable_not_matrix": [],
1025
+ "deprecated_in_matrix": [],
1026
+ "layer_mismatches": [],
1027
+ "unverified_layers": ["e2e"],
1028
+ }
1029
+ report["matrix_tcs_by_layer"] = {"e2e": ["TC-SY-001"]}
1030
+ assert g._matrix_gate_should_fail(report, {"missing_layered_layers": ["e2e"], "missing_layered_files": ["missing.xml"]})
1031
+ assert not g._matrix_gate_should_fail(report, {"missing_layered_files": [], "missing_files": []})
1032
+ configured_report = dict(report)
1033
+ configured_report["configured_layers"] = ["unit"]
1034
+ assert g._matrix_gate_should_fail(configured_report, {"missing_layered_files": [], "missing_files": []})
1035
+ clean_report = dict(report)
1036
+ clean_report["unverified_layers"] = []
1037
+ assert g._matrix_gate_should_fail(clean_report, {"missing_layered_files": [], "missing_files": ["other.xml"]})
1038
+
1039
+
1040
+ def test_matrix_gate_does_not_fail_excluded_layer_when_fast_layer_exists():
1041
+ report = {
1042
+ "in_matrix_not_bitable": [],
1043
+ "in_bitable_not_matrix": [],
1044
+ "deprecated_in_matrix": [],
1045
+ "layer_mismatches": [],
1046
+ "matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1047
+ "configured_layers": ["e2e"],
1048
+ }
1049
+ results = {"excluded_layered_layers": ["e2e"], "missing_layered_layers": []}
1050
+ assert not g._matrix_gate_should_fail(report, results)
1051
+
1052
+
1053
+ def test_validate_matrix_empty_configured_layers_disables_layer_enforcement(tmp_path):
1054
+ p = tmp_path / "matrix.md"
1055
+ p.write_text("""
1056
+ | 模块 | e2e |
1057
+ |---|---|
1058
+ | sys | TC-SY-001 |
1059
+ """)
1060
+ records = [_rec("TC-SY-001")]
1061
+ r = g.validate_matrix(
1062
+ str(p),
1063
+ records,
1064
+ actual_layers={"unit": ["TC-SY-001"]},
1065
+ configured_layers=[],
1066
+ )
1067
+ assert r["configured_layers"] == []
1068
+ assert r["actual_tcs_by_layer"] == {"unit": ["TC-SY-001"]}
1069
+ assert r["layer_mismatches"] == []
1070
+ assert r["unverified_layers"] == ["e2e"]
1071
+
1072
+
1073
+ def test_print_matrix_validation_does_not_print_green_when_layers_unverified(capsys):
1074
+ g.print_matrix_validation({
1075
+ "in_matrix_not_bitable": [],
1076
+ "in_bitable_not_matrix": [],
1077
+ "deprecated_in_matrix": [],
1078
+ "layer_mismatches": [],
1079
+ "matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1080
+ "actual_tcs_by_layer": {},
1081
+ "configured_layers": ["unit"],
1082
+ "unverified_layers": ["e2e"],
1083
+ })
1084
+ out = capsys.readouterr().out
1085
+ assert "Unverified matrix layers: e2e" in out
1086
+ assert "✅ test-matrix.md and Bitable are in sync." not in out
1087
+
1088
+
1089
+ def test_print_matrix_validation_warning_when_unverified_layers_are_nonblocking(capsys):
1090
+ g.print_matrix_validation({
1091
+ "in_matrix_not_bitable": [],
1092
+ "in_bitable_not_matrix": [],
1093
+ "deprecated_in_matrix": [],
1094
+ "layer_mismatches": [],
1095
+ "matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1096
+ "actual_tcs_by_layer": {},
1097
+ "configured_layers": [],
1098
+ "unverified_layers": ["e2e"],
1099
+ })
1100
+ out = capsys.readouterr().out
1101
+ assert "Unverified matrix layers: e2e" in out
1102
+ assert "⚠ test-matrix.md and Bitable TC presence are in sync; layer coverage is unverified." in out
1103
+ assert "✅ test-matrix.md and Bitable" not in out
1104
+
1105
+
1106
+ def test_print_matrix_validation_parse_error_has_actionable_message(capsys):
1107
+ g.print_matrix_validation({
1108
+ "in_matrix_not_bitable": [],
1109
+ "in_bitable_not_matrix": [],
1110
+ "deprecated_in_matrix": [],
1111
+ "layer_mismatches": [],
1112
+ "matrix_tcs_by_layer": {},
1113
+ "actual_tcs_by_layer": {},
1114
+ "configured_layers": [],
1115
+ "unverified_layers": [],
1116
+ "parse_error": "no canonical matrix layer header found",
1117
+ })
1118
+ out = capsys.readouterr().out
1119
+ assert "Matrix parse error: no canonical matrix layer header found" in out
1120
+ assert "Use canonical headers: unit, contract, integration, e2e, manual, blocked." in out
1121
+ assert "✅ test-matrix.md and Bitable are in sync." not in out
1122
+
1123
+
1124
+ def test_print_matrix_validation_reports_excluded_claimed_layers(capsys):
1125
+ g.print_matrix_validation({
1126
+ "in_matrix_not_bitable": [],
1127
+ "in_bitable_not_matrix": [],
1128
+ "deprecated_in_matrix": [],
1129
+ "layer_mismatches": [],
1130
+ "matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1131
+ "actual_tcs_by_layer": {},
1132
+ "configured_layers": [],
1133
+ "unverified_layers": ["e2e"],
1134
+ "parse_error": "",
1135
+ }, {"excluded_layered_layers": ["e2e"]})
1136
+ out = capsys.readouterr().out
1137
+ assert "Excluded matrix-gate layers still claimed in matrix: e2e" in out
1138
+ assert "Matrix claims automated layers that are excluded from the matrix gate" in out
1139
+ assert "✅ test-matrix.md and Bitable" not in out
1140
+
1141
+
1142
+ def test_print_matrix_validation_ignores_excluded_layer_when_fast_layer_configured(capsys):
1143
+ g.print_matrix_validation({
1144
+ "in_matrix_not_bitable": [],
1145
+ "in_bitable_not_matrix": [],
1146
+ "deprecated_in_matrix": [],
1147
+ "layer_mismatches": [],
1148
+ "matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1149
+ "actual_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1150
+ "configured_layers": ["e2e"],
1151
+ "unverified_layers": [],
1152
+ "parse_error": "",
1153
+ }, {"excluded_layered_layers": ["e2e"]})
1154
+ out = capsys.readouterr().out
1155
+ assert "Excluded matrix-gate layers still claimed" not in out
1156
+ assert "✅ test-matrix.md and Bitable are in sync." in out
1157
+
1158
+
1159
+ def test_print_matrix_validation_does_not_print_green_when_layered_xml_missing(capsys):
1160
+ g.print_matrix_validation({
1161
+ "in_matrix_not_bitable": [],
1162
+ "in_bitable_not_matrix": [],
1163
+ "deprecated_in_matrix": [],
1164
+ "layer_mismatches": [],
1165
+ "matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]},
1166
+ "actual_tcs_by_layer": {},
1167
+ "configured_layers": ["e2e"],
1168
+ "unverified_layers": [],
1169
+ }, {"missing_layered_files": ["missing.xml"]})
1170
+ out = capsys.readouterr().out
1171
+ assert "Missing layered JUnit XML: 1 file(s)" in out
1172
+ assert "Configured layer suites did not produce JUnit XML" in out
1173
+ assert "✅ test-matrix.md and Bitable are in sync." not in out
1174
+
1175
+
1176
+ def test_print_matrix_validation_does_not_print_green_when_xml_missing(capsys):
1177
+ g.print_matrix_validation({
1178
+ "in_matrix_not_bitable": [],
1179
+ "in_bitable_not_matrix": [],
1180
+ "deprecated_in_matrix": [],
1181
+ "layer_mismatches": [],
1182
+ "matrix_tcs_by_layer": {"unit": ["TC-SY-001"]},
1183
+ "actual_tcs_by_layer": {},
1184
+ "configured_layers": [],
1185
+ "unverified_layers": ["unit"],
1186
+ }, {"missing_files": ["missing.xml"]})
1187
+ out = capsys.readouterr().out
1188
+ assert "Missing JUnit XML: 1 file(s)" in out
1189
+ assert "Configured suites did not produce JUnit XML" in out
1190
+ assert "✅ test-matrix.md and Bitable" not in out
1191
+
1192
+
1193
+ def test_collect_results_records_optional_suite_layers(tmp_path, monkeypatch):
1194
+ sidecar = tmp_path / "tc-map.jsonl"
1195
+ sidecar.write_text(
1196
+ '{"test": "t_unit", "tc_ids": ["TC-SY-001"]}\n'
1197
+ '{"test": "t_e2e", "tc_ids": ["TC-SY-002"]}\n'
1198
+ )
1199
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1200
+ unit = tmp_path / "unit.xml"
1201
+ unit.write_text('<testsuite name="s"><testcase classname="m" name="t_unit"/></testsuite>')
1202
+ e2e = tmp_path / "e2e.xml"
1203
+ e2e.write_text('<testsuite name="s"><testcase classname="m" name="t_e2e"/></testsuite>')
1204
+ r = g.collect_results([
1205
+ {"name": "unit", "layer": "unit", "results_file": str(unit)},
1206
+ {"name": "browser", "layer": "browser", "results_file": str(e2e)},
1207
+ ])
1208
+ assert r["tc_layers"] == {"e2e": ["TC-SY-002"], "unit": ["TC-SY-001"]}
1209
+ assert r["configured_layers"] == ["e2e", "unit"]
1210
+ assert r["unlayered_tcs"] == []
1211
+
1212
+
1213
+ def test_collect_results_failed_tc_is_not_layer_evidence(tmp_path, monkeypatch):
1214
+ sidecar = tmp_path / "tc-map.jsonl"
1215
+ sidecar.write_text('{"test": "t_e2e", "tc_ids": ["TC-SY-001"]}\n')
1216
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1217
+ e2e = tmp_path / "e2e.xml"
1218
+ e2e.write_text(
1219
+ '<testsuite name="s"><testcase classname="m" name="t_e2e">'
1220
+ '<failure message="boom"/></testcase></testsuite>'
1221
+ )
1222
+ r = g.collect_results([
1223
+ {"name": "browser", "layer": "e2e", "results_file": str(e2e)},
1224
+ ])
1225
+ assert r["linked"] == {"TC-SY-001": "失败"}
1226
+ assert r["tc_layers"] == {}
1227
+ assert r["configured_layers"] == ["e2e"]
1228
+ assert r["unlayered_tcs"] == []
1229
+ matrix = tmp_path / "matrix.md"
1230
+ matrix.write_text("""
1231
+ | 模块 | e2e |
1232
+ |---|---|
1233
+ | sys | TC-SY-001 |
1234
+ """)
1235
+ validation = g.validate_matrix(
1236
+ str(matrix),
1237
+ [_rec("TC-SY-001")],
1238
+ r["tc_layers"],
1239
+ r["configured_layers"],
1240
+ r["unlayered_tcs"],
1241
+ )
1242
+ assert validation["layer_mismatches"] == [
1243
+ {"tc_id": "TC-SY-001", "declared": "e2e", "actual": []},
1244
+ ]
1245
+
1246
+
1247
+ def test_collect_results_missing_layered_suite_is_configured_missing_evidence(tmp_path, monkeypatch):
1248
+ monkeypatch.setenv("TC_SIDECAR", str(tmp_path / "missing-sidecar.jsonl"))
1249
+ r = g.collect_results([
1250
+ {"name": "browser", "layer": "e2e", "results_file": str(tmp_path / "missing.xml")},
1251
+ ])
1252
+ assert r["tc_layers"] == {}
1253
+ assert r["configured_layers"] == ["e2e"]
1254
+ assert r["missing_files"] == [str(tmp_path / "missing.xml")]
1255
+ assert r["missing_layered_files"] == [str(tmp_path / "missing.xml")]
1256
+ assert r["missing_layered_layers"] == ["e2e"]
1257
+ matrix = tmp_path / "matrix.md"
1258
+ matrix.write_text("""
1259
+ | 模块 | e2e |
1260
+ |---|---|
1261
+ | sys | TC-SY-001 |
1262
+ """)
1263
+ validation = g.validate_matrix(
1264
+ str(matrix),
1265
+ [_rec("TC-SY-001")],
1266
+ r["tc_layers"],
1267
+ r["configured_layers"],
1268
+ r["unlayered_tcs"],
1269
+ )
1270
+ assert validation["layer_mismatches"] == [
1271
+ {"tc_id": "TC-SY-001", "declared": "e2e", "actual": []},
1272
+ ]
1273
+ assert validation["unverified_layers"] == []
1274
+
1275
+
1276
+ def test_collect_results_matrix_gate_false_excludes_slow_layer_from_gate(tmp_path, monkeypatch):
1277
+ monkeypatch.setenv("TC_SIDECAR", str(tmp_path / "missing-sidecar.jsonl"))
1278
+ r = g.collect_results([
1279
+ {"name": "device", "layer": "e2e", "matrix_gate": False, "results_file": str(tmp_path / "missing.xml")},
1280
+ ])
1281
+ assert r["missing_files"] == []
1282
+ assert r["excluded_missing_files"] == [str(tmp_path / "missing.xml")]
1283
+ assert r["missing_layered_files"] == []
1284
+ assert r["missing_layered_layers"] == []
1285
+
1286
+
1287
+ def test_collect_results_matrix_gate_string_false_excludes_slow_layer(tmp_path, monkeypatch):
1288
+ monkeypatch.setenv("TC_SIDECAR", str(tmp_path / "missing-sidecar.jsonl"))
1289
+ r = g.collect_results([
1290
+ {"name": "device", "layer": "e2e", "matrix_gate": "false", "results_file": str(tmp_path / "missing.xml")},
1291
+ {"name": "host", "layer": "e2e", "matrix_gate": 0, "results_file": str(tmp_path / "missing2.xml")},
1292
+ ])
1293
+ assert r["missing_files"] == []
1294
+ assert sorted(r["excluded_missing_files"]) == [str(tmp_path / "missing.xml"), str(tmp_path / "missing2.xml")]
1295
+ assert r["missing_layered_files"] == []
1296
+ assert r["missing_layered_layers"] == []
1297
+
1298
+
1299
+ def test_collect_results_matrix_gate_null_defaults_to_enabled(tmp_path, monkeypatch):
1300
+ monkeypatch.setenv("TC_SIDECAR", str(tmp_path / "missing-sidecar.jsonl"))
1301
+ missing = tmp_path / "missing.xml"
1302
+ r = g.collect_results([
1303
+ {"name": "unit", "layer": "unit", "matrix_gate": None, "results_file": str(missing)},
1304
+ ])
1305
+ assert r["missing_files"] == [str(missing)]
1306
+ assert r["missing_layered_layers"] == ["unit"]
1307
+
1308
+
1309
+ def test_clear_suite_result_files_removes_configured_xml(tmp_path):
1310
+ xml = tmp_path / "unit.xml"
1311
+ xml.write_text('<testsuite name="s"></testsuite>')
1312
+
1313
+ backups = g._clear_suite_result_files([
1314
+ {"name": "unit", "results_file": str(xml)},
1315
+ {"name": "duplicate", "results_file": str(xml)},
1316
+ ])
1317
+
1318
+ assert not xml.exists()
1319
+ assert list(tmp_path.glob("unit.xml.pre-gate-bak-*"))
1320
+ assert backups[str(xml)].exists()
1321
+
1322
+
1323
+ def test_clear_suite_result_files_only_uses_neutral_backup_suffix(tmp_path):
1324
+ xml = tmp_path / "unit.xml"
1325
+ user_backup = tmp_path / "unit.xml.pre-gate-user"
1326
+ user_backup.write_text("keep")
1327
+ xml.write_text("<testsuite name='s'></testsuite>")
1328
+ g._clear_suite_result_files([
1329
+ {"name": "unit", "results_file": str(xml)},
1330
+ ])
1331
+
1332
+ assert user_backup.exists()
1333
+ assert list(tmp_path.glob("unit.xml.pre-gate-bak-*"))
1334
+
1335
+
1336
+ def test_clear_suite_result_files_dedupes_same_real_path(tmp_path):
1337
+ xml = tmp_path / "unit.xml"
1338
+ alias = tmp_path / "alias.xml"
1339
+ xml.write_text("<testsuite name='s'></testsuite>")
1340
+ alias.symlink_to(xml)
1341
+
1342
+ backups = g._clear_suite_result_files([
1343
+ {"name": "unit", "results_file": str(xml)},
1344
+ {"name": "alias", "results_file": str(alias)},
1345
+ ])
1346
+
1347
+ assert list(backups) == [str(xml)]
1348
+ assert len(list(tmp_path.glob("unit.xml.pre-gate-bak-*"))) == 1
1349
+
1350
+
1351
+ def test_restore_missing_suite_results_restores_backup(tmp_path):
1352
+ xml = tmp_path / "unit.xml"
1353
+ xml.write_text("<testsuite name='s'></testsuite>")
1354
+ backups = g._clear_suite_result_files([
1355
+ {"name": "unit", "results_file": str(xml)},
1356
+ ])
1357
+
1358
+ restored = g._restore_missing_suite_results(backups)
1359
+
1360
+ assert restored == [str(xml)]
1361
+ assert xml.exists()
1362
+
1363
+
1364
+ def test_restore_missing_suite_results_removes_backup_when_xml_regenerated(tmp_path):
1365
+ xml = tmp_path / "unit.xml"
1366
+ xml.write_text("<testsuite name='old'></testsuite>")
1367
+ backups = g._clear_suite_result_files([
1368
+ {"name": "unit", "results_file": str(xml)},
1369
+ ])
1370
+ backup = backups[str(xml)]
1371
+ xml.write_text("<testsuite name='new'></testsuite>")
1372
+
1373
+ restored = g._restore_missing_suite_results(backups)
1374
+
1375
+ assert restored == []
1376
+ assert xml.read_text() == "<testsuite name='new'></testsuite>"
1377
+ assert not backup.exists()
1378
+
1379
+
1380
+ def test_collect_results_treats_restored_stale_xml_as_missing(tmp_path, monkeypatch):
1381
+ sidecar = tmp_path / "tc-map.jsonl"
1382
+ sidecar.write_text('{"test": "t_unit", "tc_ids": ["TC-SY-001"]}\n')
1383
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1384
+ xml = tmp_path / "unit.xml"
1385
+ xml.write_text('<testsuite name="s"><testcase classname="m" name="t_unit"/></testsuite>')
1386
+
1387
+ r = g.collect_results([
1388
+ {"name": "unit", "layer": "unit", "results_file": str(xml)},
1389
+ ], stale_result_files={str(xml)})
1390
+
1391
+ assert r["linked"] == {}
1392
+ assert r["missing_files"] == [str(xml)]
1393
+ assert r["missing_layered_layers"] == ["unit"]
1394
+
1395
+
1396
+ def test_clear_suite_result_files_rejects_directory(tmp_path):
1397
+ with pytest.raises(RuntimeError, match="results_file is not a file"):
1398
+ g._clear_suite_result_files([
1399
+ {"name": "bad", "results_file": str(tmp_path)},
1400
+ ])
1401
+
1402
+
1403
+ def test_clear_suite_result_files_validates_all_paths_before_moving(tmp_path):
1404
+ xml = tmp_path / "unit.xml"
1405
+ xml.write_text("<testsuite name='s'></testsuite>")
1406
+ bad_dir = tmp_path / "bad"
1407
+ bad_dir.mkdir()
1408
+
1409
+ with pytest.raises(RuntimeError, match="results_file is not a file"):
1410
+ g._clear_suite_result_files([
1411
+ {"name": "unit", "results_file": str(xml)},
1412
+ {"name": "bad", "results_file": str(bad_dir)},
1413
+ ])
1414
+
1415
+ assert xml.exists()
1416
+ assert not list(tmp_path.glob("unit.xml.pre-gate-bak-*"))
1417
+
1418
+
1419
+ def test_restore_missing_suite_results_accepts_byte_identical_regeneration(tmp_path):
1420
+ xml = tmp_path / "unit.xml"
1421
+ xml.write_text("<testsuite name='old'></testsuite>")
1422
+ backups = g._clear_suite_result_files([
1423
+ {"name": "unit", "results_file": str(xml)},
1424
+ ])
1425
+ backup = backups[str(xml)]
1426
+ xml.write_text("<testsuite name='old'></testsuite>")
1427
+
1428
+ restored = g._restore_missing_suite_results(backups)
1429
+
1430
+ assert restored == []
1431
+ assert xml.read_text() == "<testsuite name='old'></testsuite>"
1432
+ assert not backup.exists()
1433
+
1434
+
1435
+ def test_restore_missing_suite_results_accepts_regeneration_with_old_mtime(tmp_path):
1436
+ xml = tmp_path / "unit.xml"
1437
+ xml.write_text("<testsuite name='old'></testsuite>")
1438
+ old_time = 1_700_000_000
1439
+ os.utime(xml, (old_time, old_time))
1440
+ backups = g._clear_suite_result_files([
1441
+ {"name": "unit", "results_file": str(xml)},
1442
+ ])
1443
+ backup = backups[str(xml)]
1444
+ xml.write_text("<testsuite name='new'></testsuite>")
1445
+ os.utime(xml, (old_time - 1, old_time - 1))
1446
+
1447
+ restored = g._restore_missing_suite_results(backups)
1448
+
1449
+ assert restored == []
1450
+ assert xml.read_text() == "<testsuite name='new'></testsuite>"
1451
+ assert not backup.exists()
1452
+
1453
+
1454
+ def test_run_suites_and_restore_backups_restores_on_exception(tmp_path, monkeypatch):
1455
+ xml = tmp_path / "unit.xml"
1456
+ xml.write_text("<testsuite name='old'></testsuite>")
1457
+ backups = g._clear_suite_result_files([
1458
+ {"name": "unit", "results_file": str(xml)},
1459
+ ])
1460
+
1461
+ def boom(_suite):
1462
+ raise OSError("runner crashed")
1463
+
1464
+ monkeypatch.setattr(g, "run_suite", boom)
1465
+ with pytest.raises(OSError, match="runner crashed"):
1466
+ g._run_suites_and_restore_backups([{"name": "unit"}], backups)
1467
+
1468
+ assert xml.exists()
1469
+ assert xml.read_text() == "<testsuite name='old'></testsuite>"
1470
+ assert not list(tmp_path.glob("unit.xml.pre-gate-bak-*"))
1471
+
1472
+
1473
+ def test_sweep_pre_gate_backups_restores_missing_original(tmp_path):
1474
+ xml = tmp_path / "unit.xml"
1475
+ old = tmp_path / "unit.xml.pre-gate-bak-20200101000000000000"
1476
+ new = tmp_path / "unit.xml.pre-gate-bak-20200102000000000000"
1477
+ old.write_text("<testsuite name='old'></testsuite>")
1478
+ new.write_text("<testsuite name='new'></testsuite>")
1479
+
1480
+ restored = g._sweep_pre_gate_backups([
1481
+ {"name": "unit", "results_file": str(xml)},
1482
+ ])
1483
+
1484
+ assert restored == [str(xml)]
1485
+ assert xml.read_text() == "<testsuite name='new'></testsuite>"
1486
+ assert not old.exists()
1487
+ assert not new.exists()
1488
+
1489
+
1490
+ def test_sweep_pre_gate_backups_removes_orphans_when_original_exists(tmp_path):
1491
+ xml = tmp_path / "unit.xml"
1492
+ xml.write_text("<testsuite name='current'></testsuite>")
1493
+ backup = tmp_path / "unit.xml.pre-gate-bak-20200101000000000000"
1494
+ backup.write_text("<testsuite name='backup'></testsuite>")
1495
+
1496
+ restored = g._sweep_pre_gate_backups([
1497
+ {"name": "unit", "results_file": str(xml)},
1498
+ ])
1499
+
1500
+ assert restored == []
1501
+ assert xml.read_text() == "<testsuite name='current'></testsuite>"
1502
+ assert not backup.exists()
1503
+
1504
+
1505
+ def test_collect_results_matrix_gate_false_excludes_positive_layer_evidence(tmp_path, monkeypatch):
1506
+ sidecar = tmp_path / "tc-map.jsonl"
1507
+ sidecar.write_text('{"test": "t_device", "tc_ids": ["TC-SY-001"]}\n')
1508
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1509
+ device = tmp_path / "device.xml"
1510
+ device.write_text('<testsuite name="s"><testcase classname="m" name="t_device"/></testsuite>')
1511
+ r = g.collect_results([
1512
+ {"name": "device", "layer": "e2e", "matrix_gate": False, "results_file": str(device)},
1513
+ ])
1514
+ assert r["linked"] == {"TC-SY-001": "通过"}
1515
+ assert r["tc_layers"] == {}
1516
+ assert r["configured_layers"] == []
1517
+ assert r["unlayered_tcs"] == []
1518
+ assert r["excluded_layered_layers"] == ["e2e"]
1519
+ assert g._matrix_gate_should_fail(
1520
+ {"matrix_tcs_by_layer": {"e2e": ["TC-SY-001"]}},
1521
+ r,
1522
+ )
1523
+
1524
+
1525
+ def test_collect_results_can_skip_excluded_stale_xml(tmp_path, monkeypatch):
1526
+ sidecar = tmp_path / "tc-map.jsonl"
1527
+ sidecar.write_text('{"test": "t_device", "tc_ids": ["TC-SY-001"]}\n')
1528
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1529
+ device = tmp_path / "device.xml"
1530
+ device.write_text('<testsuite name="s"><testcase classname="m" name="t_device"/></testsuite>')
1531
+
1532
+ r = g.collect_results([
1533
+ {"name": "device", "layer": "e2e", "matrix_gate": False, "results_file": str(device)},
1534
+ ], parse_excluded=False)
1535
+
1536
+ assert r["linked"] == {}
1537
+ assert r["tc_layers"] == {}
1538
+ assert r["excluded_layered_layers"] == ["e2e"]
1539
+
1540
+
1541
+ def test_collect_results_invalid_layer_config_fails_gate(tmp_path, monkeypatch):
1542
+ sidecar = tmp_path / "tc-map.jsonl"
1543
+ sidecar.write_text('{"test": "t_unit", "tc_ids": ["TC-SY-001"]}\n')
1544
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1545
+ xml = tmp_path / "unit.xml"
1546
+ xml.write_text('<testsuite name="s"><testcase classname="m" name="t_unit"/></testsuite>')
1547
+
1548
+ r = g.collect_results([
1549
+ {"name": "typo", "layer": "uint", "results_file": str(xml)},
1550
+ ])
1551
+
1552
+ assert r["invalid_layer_suites"] == [{"suite": "typo", "layer": "uint"}]
1553
+ assert g._matrix_gate_should_fail({"matrix_tcs_by_layer": {}}, r)
1554
+
1555
+
1556
+ def test_collect_results_layered_suite_without_results_file_is_missing_evidence(monkeypatch, tmp_path):
1557
+ monkeypatch.setenv("TC_SIDECAR", str(tmp_path / "missing-sidecar.jsonl"))
1558
+ r = g.collect_results([
1559
+ {"name": "unit", "layer": "unit"},
1560
+ ])
1561
+ assert r["configured_layers"] == ["unit"]
1562
+ assert r["missing_files"] == ["unit: results_file missing"]
1563
+ assert r["missing_layered_files"] == ["unit: results_file missing"]
1564
+ assert r["missing_layered_layers"] == ["unit"]
1565
+ assert r["invalid_layer_suites"] == []
1566
+ assert g._matrix_gate_should_fail({"matrix_tcs_by_layer": {"unit": ["TC-SY-001"]}}, r)
1567
+
1568
+
1569
+ def test_collect_results_blank_layer_is_unlabeled(tmp_path, monkeypatch):
1570
+ sidecar = tmp_path / "tc-map.jsonl"
1571
+ sidecar.write_text('{"test": "t_unit", "tc_ids": ["TC-SY-001"]}\n')
1572
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1573
+ xml = tmp_path / "unit.xml"
1574
+ xml.write_text('<testsuite name="s"><testcase classname="m" name="t_unit"/></testsuite>')
1575
+
1576
+ r = g.collect_results([
1577
+ {"name": "blank", "layer": " ", "results_file": str(xml)},
1578
+ ])
1579
+
1580
+ assert r["invalid_layer_suites"] == []
1581
+ assert r["unlayered_tcs"] == ["TC-SY-001"]
1582
+
1583
+
1584
+ def test_collect_results_records_unlayered_tcs(tmp_path, monkeypatch):
1585
+ sidecar = tmp_path / "tc-map.jsonl"
1586
+ sidecar.write_text('{"test": "t_unit", "tc_ids": ["TC-SY-001"]}\n')
1587
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1588
+ unit = tmp_path / "unit.xml"
1589
+ unit.write_text('<testsuite name="s"><testcase classname="m" name="t_unit"/></testsuite>')
1590
+ r = g.collect_results([
1591
+ {"name": "unlabeled", "results_file": str(unit)},
1592
+ ])
1593
+ assert r["tc_layers"] == {}
1594
+ assert r["configured_layers"] == []
1595
+ assert r["unlayered_tcs"] == ["TC-SY-001"]
1596
+
1597
+
1598
+ def test_collect_results_failed_unlayered_tc_is_not_evidence(tmp_path, monkeypatch):
1599
+ sidecar = tmp_path / "tc-map.jsonl"
1600
+ sidecar.write_text('{"test": "t_unit", "tc_ids": ["TC-SY-001"]}\n')
1601
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1602
+ unit = tmp_path / "unit.xml"
1603
+ unit.write_text(
1604
+ '<testsuite name="s"><testcase classname="m" name="t_unit">'
1605
+ '<failure message="boom"/></testcase></testsuite>'
1606
+ )
1607
+ r = g.collect_results([
1608
+ {"name": "unlabeled", "results_file": str(unit)},
1609
+ ])
1610
+ assert r["linked"] == {"TC-SY-001": "失败"}
1611
+ assert r["unlayered_tcs"] == []
1612
+
1613
+
1614
+ def test_collect_results_manual_layer_is_not_unlabeled_evidence(tmp_path, monkeypatch):
1615
+ sidecar = tmp_path / "tc-map.jsonl"
1616
+ sidecar.write_text('{"test": "t_manual", "tc_ids": ["TC-SY-001"]}\n')
1617
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1618
+ manual = tmp_path / "manual.xml"
1619
+ manual.write_text('<testsuite name="s"><testcase classname="m" name="t_manual"/></testsuite>')
1620
+ r = g.collect_results([
1621
+ {"name": "manual", "layer": "manual", "results_file": str(manual)},
1622
+ ])
1623
+ assert r["linked"] == {"TC-SY-001": "通过"}
1624
+ assert r["unlayered_tcs"] == []
1625
+
1626
+
1627
+ def test_collect_results_tracks_test_type_breakdown(tmp_path, monkeypatch):
1628
+ sidecar = tmp_path / "tc-map.jsonl"
1629
+ sidecar.write_text('{"test": "t_api", "tc_ids": ["TC-SY-001"]}\n')
1630
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1631
+ api = tmp_path / "api.xml"
1632
+ api.write_text(
1633
+ '<testsuite name="api">'
1634
+ '<testcase classname="svc" name="t_api"/>'
1635
+ '<testcase classname="svc" name="t_untracked"><failure message="boom"/></testcase>'
1636
+ '</testsuite>'
1637
+ )
1638
+ r = g.collect_results([
1639
+ {"name": "api", "test_type": "api-automation", "results_file": str(api)},
1640
+ ])
1641
+ assert r["linked"] == {"TC-SY-001": "通过"}
1642
+ assert r["linked_test_types"] == {"TC-SY-001": "api-automation"}
1643
+ assert r["by_test_type"]["api-automation"]["通过"] == 1
1644
+ assert "失败" not in r["by_test_type"]["api-automation"]
1645
+ assert r["by_test_type"]["api-automation"]["_total"] == 1
1646
+ assert r["untracked"][0]["test_type"] == "api-automation"
1647
+
1648
+
1649
+ def test_collect_results_conflicting_test_types_are_not_normal_buckets(tmp_path, monkeypatch):
1650
+ sidecar = tmp_path / "tc-map.jsonl"
1651
+ sidecar.write_text('{"test": "t_shared", "tc_ids": ["TC-SY-001"]}\n')
1652
+ monkeypatch.setenv("TC_SIDECAR", str(sidecar))
1653
+ api = tmp_path / "api.xml"
1654
+ api.write_text('<testsuite name="api"><testcase classname="svc" name="t_shared"/></testsuite>')
1655
+ ui = tmp_path / "ui.xml"
1656
+ ui.write_text('<testsuite name="ui"><testcase classname="web" name="t_shared"/></testsuite>')
1657
+ r = g.collect_results([
1658
+ {"name": "api", "test_type": "api-automation", "results_file": str(api)},
1659
+ {"name": "ui", "test_type": "ui-automation", "results_file": str(ui)},
1660
+ ])
1661
+ assert r["linked"] == {"TC-SY-001": "通过"}
1662
+ assert r["linked_test_types"] == {"TC-SY-001": "(多种测试类型)"}
1663
+ assert r["test_type_conflicts"] == ["TC-SY-001"]
1664
+ assert "(多种测试类型)" not in r["by_test_type"]
1665
+ assert r["by_test_type"] == {}
1666
+
1667
+
1668
+ def test_residual_risk_section_lists_failed_untracked_tests():
1669
+ agg = {"by_module": {}, "by_status": {}, "p0_total": 0, "p0_pass": 0, "p0_blocking": []}
1670
+ untracked = [{"classname": "m", "name": "t_a", "status": "失败"},
1671
+ {"classname": "m", "name": "t_b", "status": "通过"}]
1672
+ md = "\n".join(g._residual_risk_section(agg, untracked, {"missing_files": []}))
1673
+ assert "未链接 TC 但失败的测试" in md
1674
+ assert "t_a" in md
1675
+ # t_b is passing → not listed
1676
+ assert "t_b" not in md
1677
+
1678
+
1679
+ def test_compute_exit_code_fails_on_missing_files():
1680
+ r = {"linked": {}, "untracked": [], "missing_files": ["/x.xml"], "parsed_suites": 0}
1681
+ assert g._compute_exit_code("any", False, r) == 1
1682
+ assert g._compute_exit_code("suite-only", False, r) == 1
1683
+ assert g._compute_exit_code("tc-failures", False, r) == 0 # tc-failures policy ignores infra
1684
+ assert g._compute_exit_code("never", False, r) == 0
1685
+
1686
+
1687
+ def test_last_run_section_empty_when_no_previous():
1688
+ diff = {"had_previous": False, "new_failures": [], "fixed": [],
1689
+ "flips": [], "newly_seen": [], "no_longer_seen": []}
1690
+ assert g._last_run_section(diff) == []
1691
+
1692
+
1693
+ def test_build_pr_summary_first_time():
1694
+ results = {"linked": {"TC-A": "通过", "TC-B": "失败"}, "untracked": []}
1695
+ summary = g.build_pr_summary(results, None, None)
1696
+ assert "🧪 自动化测试结果" in summary
1697
+ assert "通过 **1**" in summary
1698
+ assert "失败 **1**" in summary
1699
+ # no last-run section since no diff provided
1700
+ assert "新增失败" not in summary
1701
+
1702
+
1703
+ def test_build_pr_summary_with_regression():
1704
+ results = {"linked": {"TC-A": "失败"}, "untracked": []}
1705
+ diff = {"had_previous": True, "new_failures": ["TC-A"], "fixed": [],
1706
+ "flips": [], "newly_seen": [], "no_longer_seen": []}
1707
+ summary = g.build_pr_summary(results, None, diff)
1708
+ assert "新增失败 / 阻塞(1)" in summary
1709
+ assert "TC-A" in summary
1710
+
1711
+
1712
+ def test_build_pr_summary_clean_run_with_previous():
1713
+ results = {"linked": {"TC-A": "通过"}, "untracked": []}
1714
+ diff = {"had_previous": True, "new_failures": [], "fixed": [],
1715
+ "flips": [], "newly_seen": [], "no_longer_seen": []}
1716
+ summary = g.build_pr_summary(results, None, diff)
1717
+ assert "无新增失败或修复" in summary
1718
+
1719
+
1720
+ def test_build_pr_summary_truncates_long_lists():
1721
+ results = {"linked": {f"TC-{i}": "失败" for i in range(30)}, "untracked": []}
1722
+ diff = {"had_previous": True,
1723
+ "new_failures": [f"TC-{i}" for i in range(30)],
1724
+ "fixed": [], "flips": [], "newly_seen": [], "no_longer_seen": []}
1725
+ summary = g.build_pr_summary(results, None, diff)
1726
+ assert "另外 10 个" in summary # 30 total, shows 20, "另外 10"
1727
+
1728
+
1729
+ def test_build_pr_summary_includes_coverage():
1730
+ results = {"linked": {"TC-A": "通过"}, "untracked": []}
1731
+ cov = {"covered": 80, "valid": 100, "rate": 0.8}
1732
+ summary = g.build_pr_summary(results, cov, None)
1733
+ assert "覆盖率 80.0%" in summary
1734
+
1735
+
1736
+ def test_last_run_section_renders_failures_and_fixes():
1737
+ diff = {"had_previous": True,
1738
+ "new_failures": ["TC-A"], "fixed": ["TC-B"],
1739
+ "flips": [{"tc_id": "TC-C", "from": "通过", "to": "跳过"}],
1740
+ "newly_seen": [], "no_longer_seen": ["TC-X"]}
1741
+ md = "\n".join(g._last_run_section(diff))
1742
+ assert "新增失败" in md and "TC-A" in md
1743
+ assert "修复" in md and "TC-B" in md
1744
+ assert "状态翻转" in md and "TC-C" in md
1745
+ assert "上次有结果但本次未跑" in md and "TC-X" in md
1746
+
1747
+
1748
+ # ── build_minimal_markdown smoke ──────────────────────────────────────────────
1749
+
1750
+ # ── CI exit gate ──────────────────────────────────────────────────────────────
1751
+
1752
+ @pytest.mark.parametrize("fail_on,suite_failed,results,expected", [
1753
+ ("any", False, {"linked": {"TC-A": "通过"}, "untracked": []}, 0),
1754
+ ("any", True, {"linked": {"TC-A": "通过"}, "untracked": []}, 1), # suite failed
1755
+ ("any", False, {"linked": {"TC-A": "失败"}, "untracked": []}, 1), # TC failed
1756
+ ("any", False, {"linked": {"TC-A": "阻塞"}, "untracked": []}, 1), # TC blocked
1757
+ ("any", False, {"linked": {}, "untracked": [{"status": "失败"}]}, 1), # untracked failed
1758
+ ("tc-failures", True, {"linked": {"TC-A": "通过"}, "untracked": []}, 0), # suite failed but ignored
1759
+ ("tc-failures", False, {"linked": {"TC-A": "失败"}, "untracked": []}, 1),
1760
+ ("suite-only", False, {"linked": {"TC-A": "失败"}, "untracked": []}, 0), # TC failure ignored
1761
+ ("suite-only", True, {"linked": {"TC-A": "通过"}, "untracked": []}, 1),
1762
+ ("never", True, {"linked": {"TC-A": "失败"}, "untracked": []}, 0),
1763
+ ])
1764
+ def test_compute_exit_code(fail_on, suite_failed, results, expected):
1765
+ assert g._compute_exit_code(fail_on, suite_failed, results) == expected
1766
+
1767
+
1768
+ def test_exit_with_gate_saves_when_results_present(tmp_path, monkeypatch):
1769
+ """Non-dry-run + suite produced parseable XML → save_last_run runs.
1770
+ Independent of --run-tests (Makefile chain may run tests externally)."""
1771
+ monkeypatch.setenv("TC_LAST_RUN", str(tmp_path / "lr.json"))
1772
+ args = type("Args", (), {"run_tests": False, "dry_run": False, "fail_on": "any"})
1773
+ snapshot = {"TC-A": "通过"}
1774
+ results = {"linked": snapshot, "untracked": [], "missing_files": [], "parsed_suites": 1}
1775
+ with pytest.raises(SystemExit) as ei:
1776
+ g._exit_with_gate(args, False, results, snapshot)
1777
+ assert ei.value.code == 0
1778
+ assert (tmp_path / "lr.json").exists()
1779
+
1780
+
1781
+ def test_exit_with_gate_preserves_baseline_on_infra_failure(tmp_path, monkeypatch, capsys):
1782
+ """All configured suites had no JUnit XML → infra hard-fail → preserve baseline."""
1783
+ monkeypatch.setenv("TC_LAST_RUN", str(tmp_path / "lr.json"))
1784
+ (tmp_path / "lr.json").write_text(json.dumps({
1785
+ "schema_version": g.LAST_RUN_SCHEMA_VERSION,
1786
+ "timestamp": "x", "linked": {"TC-OLD": "通过"},
1787
+ }))
1788
+ args = type("Args", (), {"run_tests": False, "dry_run": False, "fail_on": "any"})
1789
+ results = {"linked": {}, "untracked": [], "missing_files": ["/x.xml"], "parsed_suites": 0}
1790
+ with pytest.raises(SystemExit) as ei:
1791
+ g._exit_with_gate(args, False, results, {})
1792
+ assert ei.value.code == 1 # infra failure flags as exit 1
1793
+ saved = json.loads((tmp_path / "lr.json").read_text())
1794
+ assert saved["linked"] == {"TC-OLD": "通过"} # untouched
1795
+ assert "infra failure" in capsys.readouterr().err
1796
+
1797
+
1798
+ def test_exit_with_gate_saves_when_parsed_but_no_tcs(tmp_path, monkeypatch):
1799
+ """JUnit parsed OK but project has no markers yet → empty `linked` is a valid
1800
+ baseline; should save (distinguishable from infra failure via parsed_suites > 0)."""
1801
+ monkeypatch.setenv("TC_LAST_RUN", str(tmp_path / "lr.json"))
1802
+ args = type("Args", (), {"run_tests": False, "dry_run": False, "fail_on": "any"})
1803
+ results = {"linked": {}, "untracked": [{"name": "t1", "classname": "m", "status": "通过"}],
1804
+ "missing_files": [], "parsed_suites": 1}
1805
+ with pytest.raises(SystemExit) as ei:
1806
+ g._exit_with_gate(args, False, results, {})
1807
+ assert ei.value.code == 0
1808
+ assert (tmp_path / "lr.json").exists() # empty snapshot saved (legitimate)
1809
+
1810
+
1811
+ def test_exit_with_gate_dry_run_never_saves(tmp_path, monkeypatch):
1812
+ monkeypatch.setenv("TC_LAST_RUN", str(tmp_path / "lr.json"))
1813
+ args = type("Args", (), {"run_tests": False, "dry_run": True, "fail_on": "any"})
1814
+ results = {"linked": {"TC-A": "通过"}, "untracked": [], "missing_files": [], "parsed_suites": 1}
1815
+ with pytest.raises(SystemExit):
1816
+ g._exit_with_gate(args, False, results, {"TC-A": "通过"})
1817
+ assert not (tmp_path / "lr.json").exists()
1818
+
1819
+
1820
+ def test_compute_exit_code_handles_none_results():
1821
+ assert g._compute_exit_code("any", False, None) == 0
1822
+ assert g._compute_exit_code("any", True, None) == 1
1823
+
1824
+
1825
+ # ── subprocess-mocked lark-cli wrappers ──────────────────────────────────────
1826
+
1827
+ from unittest.mock import MagicMock # noqa: E402
1828
+
1829
+
1830
+ def _fake_run(returncode=0, stdout="", stderr=""):
1831
+ """Build a subprocess.run-like return value."""
1832
+ m = MagicMock()
1833
+ m.returncode = returncode
1834
+ m.stdout = stdout
1835
+ m.stderr = stderr
1836
+ return m
1837
+
1838
+
1839
+ def test_parse_bitable_url_with_table():
1840
+ base, table = g.parse_bitable_url("https://xxx.feishu.cn/base/BAS01234567abc?table=tblXYZ123")
1841
+ assert base == "BAS01234567abc"
1842
+ assert table == "tblXYZ123"
1843
+
1844
+
1845
+ def test_parse_bitable_url_without_table():
1846
+ base, table = g.parse_bitable_url("https://xxx.feishu.cn/base/BAS01234567abc")
1847
+ assert base == "BAS01234567abc"
1848
+ assert table == ""
1849
+
1850
+
1851
+ def test_parse_bitable_url_invalid_raises():
1852
+ with pytest.raises(ValueError, match="Could not parse base_token"):
1853
+ g.parse_bitable_url("https://xxx.feishu.cn/base/short")
1854
+
1855
+
1856
+ def test_fetch_first_table_id_success(monkeypatch):
1857
+ payload = json.dumps({"data": {"tables": [
1858
+ {"id": "tblFIRST", "name": "数据表"}, {"id": "tblOTHER", "name": "其他"}]}})
1859
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run(stdout=payload))
1860
+ assert g.fetch_first_table_id("base", "user") == "tblFIRST"
1861
+
1862
+
1863
+ def test_fetch_first_table_id_no_tables(monkeypatch):
1864
+ payload = json.dumps({"data": {"tables": []}})
1865
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run(stdout=payload))
1866
+ with pytest.raises(RuntimeError, match="No tables found"):
1867
+ g.fetch_first_table_id("base", "user")
1868
+
1869
+
1870
+ def test_fetch_first_table_id_cli_failure(monkeypatch):
1871
+ monkeypatch.setattr(g.subprocess, "run",
1872
+ lambda *a, **k: _fake_run(returncode=1, stderr="auth failed"))
1873
+ with pytest.raises(RuntimeError, match="\\+table-list failed"):
1874
+ g.fetch_first_table_id("base", "user")
1875
+
1876
+
1877
+ def test_record_list_raises_on_failure(monkeypatch):
1878
+ monkeypatch.setattr(g.subprocess, "run",
1879
+ lambda *a, **k: _fake_run(returncode=1, stderr="scope denied"))
1880
+ with pytest.raises(RuntimeError, match="\\+record-list failed"):
1881
+ g.record_list("base", "tbl", "user")
1882
+
1883
+
1884
+ def test_record_list_columnar_schema(monkeypatch):
1885
+ """lark-cli ~1.0.44 returns columnar data.data + fields + record_id_list;
1886
+ record_list must normalise it to [{record_id, fields:{name: value}}]."""
1887
+ payload = json.dumps({"data": {
1888
+ "fields": ["用例ID", "状态", "信息流转"],
1889
+ "data": [["TC-X-001", ["通过"], "init"],
1890
+ ["TC-X-002", ["未测试"], "init2"]],
1891
+ "record_id_list": ["recA", "recB"],
1892
+ "has_more": False,
1893
+ }})
1894
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run(stdout=payload))
1895
+ recs = g.record_list("base", "tbl", "user")
1896
+ assert len(recs) == 2
1897
+ assert recs[0]["record_id"] == "recA"
1898
+ assert g._str(recs[0]["fields"]["用例ID"]) == "TC-X-001"
1899
+ assert g._str(recs[0]["fields"]["状态"]) == "通过"
1900
+ assert recs[1]["record_id"] == "recB"
1901
+ assert g._str(recs[1]["fields"]["状态"]) == "未测试"
1902
+
1903
+
1904
+ def test_record_list_columnar_paginates_via_has_more(monkeypatch):
1905
+ """Columnar pagination is driven by has_more, advancing offset by page size."""
1906
+ def fake(cmd, capture_output, text):
1907
+ offset = int(cmd[cmd.index("--offset") + 1])
1908
+ if offset == 0:
1909
+ rows = [[f"TC-X-{i:03d}", ["通过"]] for i in range(200)]
1910
+ rids = [f"r{i}" for i in range(200)]
1911
+ has_more = True
1912
+ else:
1913
+ rows = [[f"TC-X-{i:03d}", ["通过"]] for i in range(200, 230)]
1914
+ rids = [f"r{i}" for i in range(200, 230)]
1915
+ has_more = False
1916
+ return _fake_run(stdout=json.dumps({"data": {
1917
+ "fields": ["用例ID", "状态"], "data": rows,
1918
+ "record_id_list": rids, "has_more": has_more}}))
1919
+ monkeypatch.setattr(g.subprocess, "run", fake)
1920
+ recs = g.record_list("base", "tbl", "user")
1921
+ assert len(recs) == 230
1922
+ assert g._str(recs[229]["fields"]["用例ID"]) == "TC-X-229"
1923
+
1924
+
1925
+ def test_save_doc_url_round_trip(tmp_path):
1926
+ p = tmp_path / ".report-config.json"
1927
+ p.write_text(json.dumps({"base_token": "b", "table_id": "t"}))
1928
+ g.save_doc_url(str(p), "https://feishu.cn/docx/X1234")
1929
+ saved = json.loads(p.read_text())
1930
+ assert saved["report_doc_url"] == "https://feishu.cn/docx/X1234"
1931
+ # other fields preserved
1932
+ assert saved["base_token"] == "b"
1933
+
1934
+
1935
+ def test_init_minimal_config_creates_skeleton(tmp_path):
1936
+ p = tmp_path / ".report-config.json"
1937
+ g.init_minimal_config(str(p))
1938
+ assert p.exists()
1939
+ cfg = json.loads(p.read_text())
1940
+ assert cfg["base_token"] == ""
1941
+ assert cfg["table_id"] == ""
1942
+ assert cfg["test_suites"] == []
1943
+
1944
+
1945
+ def test_init_minimal_config_preserves_existing_fields(tmp_path):
1946
+ p = tmp_path / ".report-config.json"
1947
+ p.write_text(json.dumps({"test_suites": [{"name": "x"}], "custom_key": "keep_me"}))
1948
+ g.init_minimal_config(str(p))
1949
+ cfg = json.loads(p.read_text())
1950
+ assert cfg["test_suites"] == [{"name": "x"}]
1951
+ assert cfg["custom_key"] == "keep_me"
1952
+ assert cfg["base_token"] == "" # added
1953
+
1954
+
1955
+ def test_init_config_with_table_in_url(tmp_path, monkeypatch):
1956
+ """When table_id is in the URL, no +table-list call needed."""
1957
+ called = []
1958
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: called.append(a) or _fake_run())
1959
+ p = tmp_path / ".report-config.json"
1960
+ g.init_config(str(p), "https://x.feishu.cn/base/BAS01234567abc?table=tblXYZ", "user")
1961
+ assert called == [] # no subprocess call
1962
+ cfg = json.loads(p.read_text())
1963
+ assert cfg["base_token"] == "BAS01234567abc"
1964
+ assert cfg["table_id"] == "tblXYZ"
1965
+
1966
+
1967
+ def test_init_config_fetches_table_when_missing(tmp_path, monkeypatch):
1968
+ """When URL has no ?table=, falls back to +table-list."""
1969
+ payload = json.dumps({"data": {"tables": [{"id": "tblFROM_LIST", "name": "数据表"}]}})
1970
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run(stdout=payload))
1971
+ p = tmp_path / ".report-config.json"
1972
+ g.init_config(str(p), "https://x.feishu.cn/base/BAS01234567abc", "user")
1973
+ cfg = json.loads(p.read_text())
1974
+ assert cfg["table_id"] == "tblFROM_LIST"
1975
+
1976
+
1977
+ def test_create_doc_success(monkeypatch):
1978
+ """Markdown URL returned on stdout."""
1979
+ monkeypatch.setattr(g.subprocess, "run",
1980
+ lambda *a, **k: _fake_run(stdout="https://feishu.cn/docx/X12345\n"))
1981
+ url = g.create_doc("Title", "# md", "", "user")
1982
+ assert url == "https://feishu.cn/docx/X12345"
1983
+
1984
+
1985
+ def test_create_doc_json_output(monkeypatch):
1986
+ monkeypatch.setattr(g.subprocess, "run",
1987
+ lambda *a, **k: _fake_run(stdout=json.dumps({"data": {"url": "https://feishu.cn/docx/J999"}})))
1988
+ url = g.create_doc("Title", "# md", "folder", "user")
1989
+ assert url == "https://feishu.cn/docx/J999"
1990
+
1991
+
1992
+ def test_create_doc_failure_raises(monkeypatch):
1993
+ monkeypatch.setattr(g.subprocess, "run",
1994
+ lambda *a, **k: _fake_run(returncode=1, stderr="scope denied"))
1995
+ with pytest.raises(RuntimeError, match="docs \\+create failed"):
1996
+ g.create_doc("t", "m", "", "user")
1997
+
1998
+
1999
+ def test_create_doc_empty_url_raises(monkeypatch):
2000
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run(stdout=""))
2001
+ with pytest.raises(RuntimeError, match="no URL found"):
2002
+ g.create_doc("t", "m", "", "user")
2003
+
2004
+
2005
+ def test_update_doc_success(monkeypatch):
2006
+ calls = []
2007
+ def fake(cmd, capture_output, text):
2008
+ calls.append(list(cmd))
2009
+ return _fake_run()
2010
+ monkeypatch.setattr(g.subprocess, "run", fake)
2011
+ g.update_doc("https://feishu.cn/docx/X1", "# md", "user")
2012
+ assert "+update" in calls[0]
2013
+ assert "replace_all" in calls[0]
2014
+
2015
+
2016
+ def test_update_doc_failure_raises(monkeypatch):
2017
+ monkeypatch.setattr(g.subprocess, "run",
2018
+ lambda *a, **k: _fake_run(returncode=1, stderr="not found"))
2019
+ with pytest.raises(RuntimeError, match="docs \\+update failed"):
2020
+ g.update_doc("https://x", "m", "user")
2021
+
2022
+
2023
+ def test_sync_results_with_append_skips_deprecated(monkeypatch):
2024
+ """A TC marked 废弃 in Bitable must NOT be overwritten by automation."""
2025
+ calls = []
2026
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: calls.append(a) or _fake_run())
2027
+ records = [_rec("TC-A", status="废弃")]
2028
+ n = g.sync_results_with_append("b", "t", "user", records, {"TC-A": "失败"}, "alice")
2029
+ assert n == 0
2030
+ assert calls == [] # no +record-upsert call made
2031
+
2032
+
2033
+ def test_sync_results_with_append_skips_unchanged(monkeypatch):
2034
+ calls = []
2035
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: calls.append(a) or _fake_run())
2036
+ records = [_rec("TC-A", status="通过")]
2037
+ n = g.sync_results_with_append("b", "t", "user", records, {"TC-A": "通过"}, "alice")
2038
+ assert n == 0
2039
+ assert calls == []
2040
+
2041
+
2042
+ def test_sync_results_with_append_updates_and_appends_info(monkeypatch):
2043
+ captured: list[dict] = []
2044
+ def fake(cmd, capture_output, text):
2045
+ # capture --json payload
2046
+ idx = cmd.index("--json") + 1
2047
+ captured.append(json.loads(cmd[idx]))
2048
+ return _fake_run()
2049
+ monkeypatch.setattr(g.subprocess, "run", fake)
2050
+ records = [
2051
+ {"record_id": "rA", "fields": {"用例ID": "TC-A", "状态": "通过",
2052
+ "信息流转": "[prev 2026-01-01] 之前的事"}},
2053
+ ]
2054
+ n = g.sync_results_with_append("b", "t", "user", records, {"TC-A": "失败"}, "alice")
2055
+ assert n == 1
2056
+ assert captured[0]["状态"] == "失败"
2057
+ assert "自动化测试:失败" in captured[0]["信息流转"]
2058
+ assert "[prev 2026-01-01] 之前的事" in captured[0]["信息流转"] # preserved
2059
+
2060
+
2061
+ def test_sync_results_with_append_skips_missing_tc(monkeypatch, capsys):
2062
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run())
2063
+ records = [_rec("TC-A")]
2064
+ n = g.sync_results_with_append("b", "t", "user", records, {"TC-MISSING": "失败"}, "alice")
2065
+ assert n == 0
2066
+ assert "TC-MISSING not found in Bitable" in capsys.readouterr().err
2067
+
2068
+
2069
+ def test_sync_results_continues_on_single_failure(monkeypatch):
2070
+ """One failure should not abort the rest."""
2071
+ call_count = [0]
2072
+ def fake(cmd, capture_output, text):
2073
+ call_count[0] += 1
2074
+ # First call fails, others succeed
2075
+ return _fake_run(returncode=1 if call_count[0] == 1 else 0,
2076
+ stderr="api error" if call_count[0] == 1 else "")
2077
+ monkeypatch.setattr(g.subprocess, "run", fake)
2078
+ records = [_rec("TC-A", status="未测试"), _rec("TC-B", status="未测试")]
2079
+ n = g.sync_results_with_append("b", "t", "user", records,
2080
+ {"TC-A": "失败", "TC-B": "通过"}, "alice")
2081
+ assert n == 1 # only TC-B succeeded
2082
+ assert call_count[0] == 2
2083
+
2084
+
2085
+ def test_unfreeze_tc_only_affects_deprecated(monkeypatch):
2086
+ captured = []
2087
+ def fake(cmd, capture_output, text):
2088
+ idx = cmd.index("--json") + 1
2089
+ captured.append(json.loads(cmd[idx]))
2090
+ return _fake_run()
2091
+ monkeypatch.setattr(g.subprocess, "run", fake)
2092
+ records = [
2093
+ _rec("TC-A", status="废弃"),
2094
+ _rec("TC-B", status="通过"), # not 废弃 → skipped
2095
+ ]
2096
+ n = g.unfreeze_tc("b", "t", "user", records, ["TC-A", "TC-B"], "alice", reason="误标")
2097
+ assert n == 1
2098
+ assert len(captured) == 1
2099
+ assert captured[0]["状态"] == "未测试"
2100
+ assert "误标" in captured[0]["信息流转"]
2101
+ assert "废弃 → 未测试" in captured[0]["信息流转"]
2102
+
2103
+
2104
+ def test_unfreeze_tc_skips_missing(monkeypatch, capsys):
2105
+ monkeypatch.setattr(g.subprocess, "run", lambda *a, **k: _fake_run())
2106
+ records = [_rec("TC-A", status="废弃")]
2107
+ n = g.unfreeze_tc("b", "t", "user", records, ["TC-MISSING"], "alice")
2108
+ assert n == 0
2109
+ assert "TC-MISSING not found" in capsys.readouterr().err
2110
+
2111
+
2112
+ def test_build_minimal_markdown_smoke():
2113
+ results = {
2114
+ "linked": {"TC-X-001": "通过"},
2115
+ "untracked": [
2116
+ {"name": "t_a", "classname": "m", "status": "通过", "suite": "unit"},
2117
+ {"name": "t_b", "classname": "m", "status": "失败", "suite": "unit"},
2118
+ ],
2119
+ "by_test_type": {
2120
+ "api-automation": {"_total": 2, "通过": 1, "失败": 1},
2121
+ },
2122
+ }
2123
+ md = g.build_minimal_markdown(results, "alice", "本地", "v1", coverage={"covered": 80, "valid": 100, "rate": 0.8})
2124
+ assert "代码覆盖率 | 80.0%(80 / 100 行)" in md
2125
+ assert "按测试类型统计" in md
2126
+ assert "api-automation" in md
2127
+ assert "失败 / 阻塞明细" in md
2128
+ assert "t_b" in md
2129
+ assert "需先解决再发布" in md
2130
+
2131
+
2132
+ def test_build_markdown_renders_layer_and_test_type_sections():
2133
+ agg = g.aggregate([
2134
+ _rec("TC-1", status="通过", 模块="登录", 功能点="主链路", 优先级="P0",
2135
+ 测试层级="e2e", 测试类型="ui-automation"),
2136
+ _rec("TC-2", status="失败", 模块="接口", 功能点="契约校验", 优先级="P1",
2137
+ 测试层级="contract", 测试类型="api-automation"),
2138
+ ])
2139
+ md = g.build_markdown(agg, "alice", "本地", "v1")
2140
+ assert "## 按测试层级统计" in md
2141
+ assert "## 按测试类型统计" in md
2142
+ assert "| 测试层级 | 总数 | 通过 | 失败 | 阻塞 | 跳过 | 未测试 | 通过率 |" in md
2143
+ assert "ui-automation" in md
2144
+ assert "api-automation" in md