@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.
- package/LICENSE +201 -0
- package/README.md +49 -0
- package/dist/assets/marketplace/.agents/plugins/marketplace.json +12 -0
- package/dist/assets/marketplace/.claude-plugin/marketplace.json +13 -0
- package/dist/assets/marketplace/marketplace-manifest.json +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/.claude-plugin/marketplace.json +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/.claude-plugin/plugin.json +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/.codex-plugin/plugin.json +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/.worktree-only +3 -0
- package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +45 -0
- package/dist/assets/marketplace/plugins/ccl-skills/agent-context/subagent-start.md +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/AGENTS.md +19 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-delegation-owner.sh +125 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-edit-isolation.sh +102 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-merge-authorization.sh +1156 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/hooks.json +131 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/merge-authorization-prompt.sh +142 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-guard.sh +12 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-stop.sh +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/remind-post-merge-cleanup.sh +144 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-context.sh +87 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-start.sh +86 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-extraction-gate-stop.sh +69 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/subagent-start.sh +26 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_delegation_owner.sh +329 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_edit_isolation.sh +322 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_merge_authorization.sh +902 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_merge_authorization_prompt.sh +178 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_remind_post_merge_cleanup.sh +121 -0
- package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_session_start.sh +170 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/AGENTS.md +17 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +564 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +14 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-update-skills.md +44 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-verify-skills.md +109 -0
- package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-worktree-check.md +36 -0
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/AGENTS.md +28 -0
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/README.md +276 -0
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.example.json +10 -0
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +1307 -0
- package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +941 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/agents-file-coverage-gate/SKILL.md +45 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/agents-file-coverage-gate/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +188 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/android-dev.md +92 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/flutter-dev.md +80 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/ios-dev.md +72 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/kotlin-multiplatform.md +93 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-platform-boundaries.md +77 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +77 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/source-evidence-map.md +64 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +353 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +419 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +126 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +197 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +179 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/AGENTS.md +98 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_envelope.py +93 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_timeout_exit.sh +15 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +1438 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +324 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/concern_excerpt.py +295 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/egress_schema.py +214 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/init_policy_matrix.py +642 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_packet_mcp.py +181 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +1165 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +1190 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_cli_review.py +946 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_opencode_review.py +474 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_probe_result.py +1899 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_review_json.py +200 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +2845 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.sh +6 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/run_claude_capture.py +71 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/runtime-surface-verification-design.md +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_classify_envelope.sh +68 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +2311 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_cli_review_wrappers.sh +1832 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_code_review_identity.sh +73 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_concern_excerpt.sh +245 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_egress_schema.sh +177 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +272 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_kimi_packet_mcp.py +195 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_opencode_review_concurrency.sh +120 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_opencode_review_retry.sh +1005 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_opencode_review.sh +258 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +574 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_review_json.sh +349 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +434 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_order.sh +264 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +2412 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/verify_native_skill_binding.py +123 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +153 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/diagnosis-playbook.md +54 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/prevention-routing.md +36 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/SKILL.md +69 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/references/security-review-gate.md +41 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/SKILL.md +165 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/api-security-boundaries.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/architecture-playbook.md +160 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/artifact-generation-architecture.md +37 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/audit-history-architecture.md +29 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/bulk-workflow-architecture.md +33 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/config-rule-routing-architecture.md +34 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/cross-cutting-concerns.md +72 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/data-modeling-and-migrations.md +79 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/data-platform-architecture.md +210 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/dependency-platform.md +105 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/developer-tooling-architecture.md +38 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/error-contract-architecture.md +36 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/event-driven-architecture.md +260 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/http-gateway-architecture.md +74 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/mq-consumer-architecture.md +38 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/multi-tenant-isolation.md +275 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/notification-architecture.md +25 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/ops-checklist.md +57 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/performance-capacity-architecture.md +38 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/protobuf-contract-architecture.md +119 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/redis-cache-coordination.md +93 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/release-runtime-readiness.md +65 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/replay-comparison-architecture.md +26 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/runtime-observability.md +94 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/service-scaffold.md +76 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/source-evidence-map.md +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/workflow-state-architecture.md +38 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +159 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/artifact-generation-patterns.md +37 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/audit-history-patterns.md +28 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/bulk-import-export-patterns.md +56 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/config-rule-routing-patterns.md +38 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/data-access-patterns.md +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/db-schema-and-dal-patterns.md +109 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/dependency-client-patterns.md +130 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/developer-tooling-patterns.md +70 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/domain-feature-patterns.md +78 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/engineering-patterns.md +119 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/error-contract-patterns.md +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/feature-playbook.md +61 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/http-gateway-client-patterns.md +76 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/mq-consumer-patterns.md +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/notification-patterns.md +42 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/observability-implementation-patterns.md +101 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/performance-capacity-patterns.md +44 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/protobuf-contract-patterns.md +72 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/public-api-integration-patterns.md +56 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/quality-and-testing-patterns.md +91 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/redis-cache-lock-patterns.md +123 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/release-ops-patterns.md +112 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/reliability-patterns.md +83 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/replay-comparison-patterns.md +32 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/scaffold-and-codegen.md +86 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/source-evidence-map.md +54 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/state-machine-task-patterns.md +45 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/SKILL.md +80 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +117 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-approval-auto-reviewer.md +106 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-command-sandbox.md +441 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-context-freshness.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-credentials-auth.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-extensions-skills.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-file-edit-protocol.md +129 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-ide-integration.md +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-input-ingestion.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-instruction-composition.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-lifecycle-hooks.md +92 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-messaging.md +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-runtime-bootstrap.md +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-session-persistence.md +448 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-task-orchestration.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-tool-dispatch.md +123 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-turn-lifecycle.md +131 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/inference-capacity-operations.md +162 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/llm-client-gateway.md +156 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +146 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/retrieval-agent-safety.md +273 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +202 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/contracts-and-state.md +62 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/cross-stack-alignment.md +94 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/framework-choice.md +76 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/online-practice-uptake.md +56 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/platform-capabilities.md +91 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/product-page-checklist.md +40 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/qa-release.md +72 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/source-evidence-map.md +82 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/SKILL.md +103 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/agents/openai.yaml +5 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/references/multi-agent-delegation-playbook.md +100 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/SKILL.md +70 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-data-acquisition.md +549 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-disclosure-channels.md +97 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/research-prompts.md +66 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/scripts/AGENTS.md +32 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/scripts/test-public-data-acquisition-recipes.sh +379 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +244 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +76 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/framework-middleware-checklist.md +142 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/infra-component-deployment.md +268 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/log-correlation-recipe.md +124 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/log-schema-canonical.md +208 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/metrics-conventions.md +105 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/obs-stack-architecture.md +107 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/sli-slo-design.md +95 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/source-register.md +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/SKILL.md +303 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/canary-and-rollout-strategy.md +163 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/config-center-via-etcd.md +245 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/custom-control-plane-boundary.md +298 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/deploy-cli-concrete-recipe.md +312 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/deploy-pipeline.md +165 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/env-and-lane-matrix.md +126 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/lane-orchestration-control-plane.md +383 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/multi-region-and-cluster.md +135 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/promotion-gate-and-review.md +149 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/python-package-registry-release.md +462 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/rollback-playbook.md +123 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/secret-and-config-management.md +231 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/version-authority-and-deprecation.md +21 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +276 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +127 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/framework-middleware.md +143 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +90 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/http-response-envelope-contract.md +24 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +127 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/multi-env-routing.md +192 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/protobuf-http-contract-signals.md +64 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +124 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/rpc-framework-recipe.md +494 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-choice.md +113 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-migration-playbook.md +231 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +131 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +235 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/adr-convention.md +146 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-checklist.md +30 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-evaluation-report-template.md +25 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-execution-spec.md +108 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-sop.md +457 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-templates.md +24 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/artifact-egress-confidentiality.md +58 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/code-review-checklist.md +86 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/cross-repo-coordination.md +46 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +192 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-review-gate-mechanics.md +62 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +45 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/diagnostic-spec-match-gate.md +36 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dispatch-owner-skills.md +35 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dormant-code-activation.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/existing-project-assessment-report.md +223 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/external-skill-augmentation.md +46 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/feature-deprecation-cascade.md +15 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/high-risk-resilience-gates.md +73 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/implementation-completeness-and-minimality.md +120 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/implementation-entry-reentry-gate.md +122 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/modular-monolith-heuristic.md +105 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md +115 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/problem-resolution-and-learning.md +62 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/quality-attributes.md +112 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/quality-remediation-program.md +88 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +27 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/refactoring-discipline.md +52 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/review-reception.md +34 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/shared-gate-artifact-classification.md +76 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/source-evidence-map.md +31 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/status-tracker-sync.md +77 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/sync-spec-repo-contract.md +25 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +34 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/worktree-mechanics.md +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/scripts/AGENTS.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/scripts/check-agent-contract-coverage.sh +213 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +136 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/agents/openai.yaml +9 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/analytics-visualization-interactions.md +206 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +108 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/complex-creation-interactions.md +194 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +214 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +129 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +97 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +79 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +63 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +146 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +250 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +237 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +65 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +237 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +324 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-web-desktop-patterns.md +456 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +114 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +79 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/resource-management-interactions.md +113 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/scenario-community-patterns.md +133 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +130 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/trust-sensitive-ai-and-data-patterns.md +96 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +106 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +176 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +111 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/SKILL.md +157 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/ai-service-integration-boundaries.md +57 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/api-contract-and-schema.md +62 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/api-security-boundaries.md +39 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/architecture-playbook.md +46 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/async-execution-model.md +24 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/background-jobs-and-scheduling.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/batch-and-pipeline-architecture.md +11 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/config-secrets-runtime.md +22 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/data-modeling-and-migrations.md +64 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/data-platform-architecture.md +211 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/event-driven-architecture.md +263 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/multi-tenant-isolation.md +281 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/observability-and-ops.md +26 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/packaging-runtime-readiness.md +20 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/redis-cache-coordination.md +41 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/reliability-and-error-contract.md +17 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/source-evidence-map.md +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/web-framework-boundaries.md +26 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +143 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/ai-service-wiring-patterns.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/async-and-worker-patterns.md +24 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/background-job-patterns.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/batch-and-artifact-patterns.md +13 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/dependency-client-patterns.md +39 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/error-handling-patterns.md +26 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/feature-playbook.md +43 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/observability-implementation-patterns.md +31 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/project-structure-and-tooling.md +24 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/public-api-security-patterns.md +52 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/redis-cache-lock-patterns.md +78 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/schema-and-validation-patterns.md +23 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/source-evidence-map.md +56 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/sqlalchemy-and-migrations-patterns.md +99 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/testing-and-quality-patterns.md +61 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/web-framework-patterns.md +35 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/SKILL.md +91 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/config-runtime-readback.md +20 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/mr-merge-authorization.md +31 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/post-release-env-reset.md +31 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/release-closeout-evidence.md +20 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/release-scope-confirmation.md +21 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/tag-and-prod-pipeline-gate.md +20 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/test-scope-prompt.md +24 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/watcher-discipline.md +14 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/SKILL.md +64 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/comment-safe-release-doc.md +19 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/release-evidence-workflow.md +23 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/release-testing-scope-section.md +15 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/SKILL.md +87 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/SKILL.md +130 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/prd-composition-contract.md +35 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/requirement-closure-contract.md +86 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/security-four-questions.md +38 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-intent/SKILL.md +91 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-intent/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +88 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +337 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/analysis-parse-fix-test-challenge-replay.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attribution-verification.md +69 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/bootstrap-slim-c3-obligation-table.md +112 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/coverage-exhaustion-traps.md +45 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +162 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +507 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +86 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/evidence-card-template.md +51 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/example-domain-preselect.md +79 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +57 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-lifecycle-handoff.md +65 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +194 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/firing-point-placement.md +75 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +286 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/incident-postmortem-extraction.md +190 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/l0-l1-l2-routing.md +114 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/online-skill-review.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/parallel-stack-references-pattern.md +164 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +90 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/recurring-anti-patterns-checklist.md +320 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/resume-paused-delivery.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-feedback-mining.md +33 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-finding-standards.md +57 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-rubric.md +40 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +118 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/skill-listing-budget.md +19 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +254 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +658 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/two-source-extraction-pattern.md +167 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +179 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-routing-map.md +51 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +180 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/AGENTS.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +1452 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-evidence-card-leak.sh +491 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-mr-target-freshness.sh +173 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +488 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-sync-pointers.sh +419 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +197 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-health.rb +327 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +401 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing.rb +248 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/generic-r0-leak-scan.sh +282 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/governing-chain-diff.py +321 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +964 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +708 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +540 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/source-register-lifecycle.rb +51 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/source-register-pending-status.rb +55 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ai_coding_implementation_gates.sh +829 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +1203 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_r0_status.sh +75 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_register_pending_exclusion.sh +137 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +173 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_route_drift.sh +377 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +833 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +491 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_source_register_lifecycle.sh +114 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_mr_target_freshness.sh +261 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_sync_pointers.sh +538 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_controlled_escalation_pins.sh +154 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +190 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_surface_binding.sh +178 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_prose_target.sh +86 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_generic_r0_leak_scan.sh +131 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_git_identity_predicate_gate.sh +243 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_governing_chain_diff.sh +419 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +120 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_resolution.sh +724 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +414 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_regression_runner_registration.sh +34 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_bank_integrity.sh +205 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +194 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_credential_cwd.sh +61 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +111 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_root_depth.sh +53 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +257 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +98 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/input-state-machines.md +36 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/streaming-rich-output.md +130 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/terminal-side-channels.md +96 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/SKILL.md +408 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/AGENTS.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/bitable-setup.md +573 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/README.md +120 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/github-actions.yml +119 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/gitlab-ci.yml +76 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/jenkins.Jenkinsfile +106 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/classical-test-design-techniques.md +279 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/gen_report.py +2807 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/makefile-template.md +200 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/report-config-schema.md +272 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/run_pytestless.py +475 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/source-to-case-workflows.md +258 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-marker-conventions.md +316 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-review-and-prioritization.md +145 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/AGENTS.md +16 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.dart +129 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.go +197 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.py +135 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.ts +285 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/test_gen_report.py +2144 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/update-lifecycle.md +62 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +212 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +75 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +50 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/data-and-workflow-testing.md +34 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/design-closed-contract-oracles.md +31 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/e2e-real-flow-testing.md +71 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/fitness-functions.md +240 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/integration-contract-testing.md +235 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/non-functional-specialized-scenarios.md +296 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/rd-testing-standard-template.md +126 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/run-killing-mutation-walk.md +43 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/scenario-testing.md +136 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/source-evidence-map.md +59 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/structured-tc-input-translation.md +67 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-code-authoring-patterns.md +392 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-data-and-determinism.md +39 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-topology-and-commands.md +92 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/unit-testing.md +46 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/vendored-contract-drift-checklist.md +64 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/verify-enforcement-mechanisms.md +18 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/AGENTS.md +17 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/client-terminal-ansi-check.py +140 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/client-terminal-ansi-check.test.sh +75 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-ast-check.py +170 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-ast-check.test.sh +87 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-go-check.go +198 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-go-check.test.sh +109 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/test_mutation_backup_recipe.sh +237 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +184 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/comment-safe-feishu.md +93 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/cross-model-co-review.md +3 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/delivery-face-closeout.md +60 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/doc-charter-first.md +17 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/session-vantage-leakage.md +58 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +126 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +47 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/embedded-h5-in-host.md +87 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/react-architecture.md +194 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/source-evidence-map.md +60 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-quality-release.md +190 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-ui-quality.md +83 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/SKILL.md +179 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/agents/openai.yaml +4 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/references/shared-branch-rebase.md +25 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/AGENTS.md +23 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/test_worktree_status.sh +207 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/test_worktree_sweep.sh +481 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/worktree-status.sh +325 -0
- package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/worktree-sweep.sh +245 -0
- package/dist/assets/release.json +2797 -0
- package/dist/claude-adapter.d.ts +9 -0
- package/dist/claude-adapter.js +240 -0
- package/dist/cli-worker.d.ts +1 -0
- package/dist/cli-worker.js +32 -0
- package/dist/cli.d.ts +22 -0
- package/dist/cli.js +214 -0
- package/dist/codex-host.d.ts +30 -0
- package/dist/codex-host.js +162 -0
- package/dist/fs-safe.d.ts +21 -0
- package/dist/fs-safe.js +241 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/manifest.d.ts +8 -0
- package/dist/manifest.js +135 -0
- package/dist/opencode-adapter.d.ts +10 -0
- package/dist/opencode-adapter.js +416 -0
- package/dist/operations.d.ts +3 -0
- package/dist/operations.js +956 -0
- package/dist/paths.d.ts +20 -0
- package/dist/paths.js +4 -0
- package/dist/types.d.ts +58 -0
- package/dist/types.js +1 -0
- package/dist/unified.d.ts +4 -0
- package/dist/unified.js +64 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +5 -0
- package/package.json +35 -0
package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_probe_result.py
ADDED
|
@@ -0,0 +1,1899 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
NOT_LOGGED_IN = re.compile(
|
|
10
|
+
r"(^|[^a-z])not logged in([^a-z]|$)|please run /login|invalid api key|"
|
|
11
|
+
r"authentication (failed|required)|session expired|oauth token expired|"
|
|
12
|
+
r"token expired|401 unauthorized|session has ended",
|
|
13
|
+
re.I,
|
|
14
|
+
)
|
|
15
|
+
QUOTA = re.compile(
|
|
16
|
+
r"rate limit exceeded|quota exceeded|hit your limit|credit balance too low|"
|
|
17
|
+
r"usage limit reached|too_many_requests",
|
|
18
|
+
re.I,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def normalize(text: str) -> str:
|
|
23
|
+
return text.replace("\r", "").strip()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_ok_token(text: str) -> bool:
|
|
27
|
+
return normalize(text).lower() == "ok"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def last_non_empty_line(text: str) -> str:
|
|
31
|
+
for line in reversed(text.replace("\r", "").splitlines()):
|
|
32
|
+
if line.strip():
|
|
33
|
+
return line.strip()
|
|
34
|
+
return ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def has_ok_line(text: str) -> bool:
|
|
38
|
+
return any(is_ok_token(line) for line in text.replace("\r", "").splitlines())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def has_tool_available_marker(text: str) -> bool:
|
|
42
|
+
normalized = text.replace("\r", "").lower()
|
|
43
|
+
if re.search(r"\btool_enabled\b", normalized):
|
|
44
|
+
return True
|
|
45
|
+
for match in re.finditer(r"\bavailable\s+tools?\s*:\s*([\s\S]{0,500})", normalized):
|
|
46
|
+
listed = match.group(1).strip()
|
|
47
|
+
if (
|
|
48
|
+
re.match(r"\bnone\b(?:[\s.!,;:()-]|$)", listed)
|
|
49
|
+
and (not re.search(r"\bbash\b", listed) or has_no_tool_refusal_marker(listed))
|
|
50
|
+
and not re.search(r"\bbash(?:\s+tool)?\b.*\b(?:is|are|was|were|be)\s+available\b", listed)
|
|
51
|
+
):
|
|
52
|
+
continue
|
|
53
|
+
if re.search(r"\bbash\b", listed):
|
|
54
|
+
return True
|
|
55
|
+
for raw_line in text.replace("\r", "").splitlines():
|
|
56
|
+
line = raw_line.strip().lower()
|
|
57
|
+
if not line:
|
|
58
|
+
continue
|
|
59
|
+
if "bash" not in line:
|
|
60
|
+
continue
|
|
61
|
+
bash_unavailable = re.search(
|
|
62
|
+
r"\bbash(?:\s+tool)?\b.*\b(?:(?:is|was)\s+not|isn't|wasn't|not)\s+available\b|\bbash(?:\s+tool)?\b.*\bunavailable\b",
|
|
63
|
+
line,
|
|
64
|
+
)
|
|
65
|
+
if re.search(r"\bbash\s+tool\b.*\benabled\b", line):
|
|
66
|
+
return True
|
|
67
|
+
if re.search(r"\bbash\s+tool\b.*\b(?:is|are|was|were|be|available\s*:)\s+available\b", line):
|
|
68
|
+
return True
|
|
69
|
+
if re.search(r"\bbash\b.*\b(?:is|are|was|were|be)\s+available\b", line):
|
|
70
|
+
return True
|
|
71
|
+
if re.search(r"\bbash\s+tool\b.*\bavailable\b", line) and not bash_unavailable:
|
|
72
|
+
return True
|
|
73
|
+
if re.search(r"\b(?:can|could|will|would|able\s+to)\b.*\b(?:use|run|execute|call)\b.*\bbash\s+tool\b", line):
|
|
74
|
+
return True
|
|
75
|
+
if re.search(r"\bbash\s+tool\b.*\b(?:can|could|will|would|able\s+to)\b.*\b(?:use|run|execute|call)\b", line):
|
|
76
|
+
return True
|
|
77
|
+
if re.search(r"\b(?:can|could|will|would|able\s+to)\b.*\b(?:use|run|execute|call)\b.*\bbash\b", line):
|
|
78
|
+
return True
|
|
79
|
+
if re.search(r"\bbash\b.*\b(?:can|could|will|would|able\s+to)\b.*\b(?:use|run|execute|call)\b", line):
|
|
80
|
+
return True
|
|
81
|
+
if re.search(r"\b(?:use|using|used|run|ran|execute|executed|call|called)\b.*\bbash\b", line):
|
|
82
|
+
return True
|
|
83
|
+
if re.search(r"\bbash\b.*\b(?:using|used|ran|executed|called)\b", line):
|
|
84
|
+
return True
|
|
85
|
+
if re.search(r"\b(?:using|used|ran|executed|called)\b.*\bbash\s+tool\b", line):
|
|
86
|
+
return True
|
|
87
|
+
if re.search(r"\bbash\s+tool\b.*\b(?:using|used|ran|executed|called)\b", line):
|
|
88
|
+
return True
|
|
89
|
+
if has_no_tool_refusal_marker(line):
|
|
90
|
+
continue
|
|
91
|
+
if re.search(r"\b(?:use|using|used|run|ran|execute|executed|call|called)\b.*\bbash\s+tool\b", line):
|
|
92
|
+
return True
|
|
93
|
+
if re.search(r"\bbash\s+tool\b.*\b(?:use|using|used|run|ran|execute|executed|call|called)\b", line):
|
|
94
|
+
return True
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def has_no_tool_refusal_marker(text: str) -> bool:
|
|
99
|
+
normalized = text.replace("\r", "").lower()
|
|
100
|
+
patterns = (
|
|
101
|
+
r"\bbash\b.{0,120}\b(?:is\s+not|isn't|was\s+not|wasn't|not)\s+available\b",
|
|
102
|
+
r"\bbash\b.{0,120}\bunavailable\b",
|
|
103
|
+
r"\bbash\s+tool\b.{0,120}\b(?:is\s+not|isn't|was\s+not|wasn't|not)\s+available\b",
|
|
104
|
+
r"\bbash\s+tool\b.{0,120}\bunavailable\b",
|
|
105
|
+
r"\bbash\s+tool\b.{0,120}\b(?:not\s+allowed|refus(?:ed|al|ing)|denied|disabled|blocked)\b",
|
|
106
|
+
r"\b(?:cannot|can't|can\s+not|unable\s+to)\b.{0,120}\b(?:use|run|execute|call)\b.{0,120}\bbash\s+tool\b",
|
|
107
|
+
r"\bbash\s+tool\b.{0,120}\b(?:cannot|can't|can\s+not|unable\s+to)\b.{0,120}\b(?:use|run|execute|call)\b",
|
|
108
|
+
r"\bbash\s+tool\b.{0,120}\b(?:cannot|can't|can\s+not|unable\s+to)\s+run\s+that\s+command\b",
|
|
109
|
+
)
|
|
110
|
+
return any(re.search(pattern, normalized) for pattern in patterns)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def is_command_output_like_line(text: str) -> bool:
|
|
114
|
+
line = text.strip()
|
|
115
|
+
if not line:
|
|
116
|
+
return False
|
|
117
|
+
if re.search(r"\b[A-Za-z0-9_.-]+\.(?:md|py|sh|go|js|ts|tsx|json|yaml|yml|toml|txt)\b", line):
|
|
118
|
+
return True
|
|
119
|
+
if re.search(r"\b(?:README|scripts|src|bin|lib|dist|node_modules|go\.mod|package\.json)\b", line):
|
|
120
|
+
return True
|
|
121
|
+
if re.match(r"^[A-Za-z0-9_./-]+$", line):
|
|
122
|
+
return True
|
|
123
|
+
if re.match(r"^[-*]\s+[A-Za-z0-9_./-]+$", line):
|
|
124
|
+
return True
|
|
125
|
+
if re.match(r"^(?:total\s+\d+|drwx|[-r][-wxsStT])", line):
|
|
126
|
+
return True
|
|
127
|
+
if line.startswith(("{", "[", "$", ">")):
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def is_allowed_probe_explanation_line(text: str) -> bool:
|
|
133
|
+
normalized = normalize(text)
|
|
134
|
+
if not normalized:
|
|
135
|
+
return True
|
|
136
|
+
if is_ok_token(normalized):
|
|
137
|
+
return True
|
|
138
|
+
if is_command_output_like_line(normalized):
|
|
139
|
+
return False
|
|
140
|
+
if has_no_tool_refusal_marker(normalized):
|
|
141
|
+
return True
|
|
142
|
+
return bool(re.search(r"\b(?:comply|explicit instructions?)\b", normalized, re.I))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def is_safe_ok_tail_value(text: str) -> bool:
|
|
146
|
+
normalized = normalize(text)
|
|
147
|
+
if is_ok_token(normalized):
|
|
148
|
+
return True
|
|
149
|
+
lines = [line.strip() for line in normalized.splitlines() if line.strip()]
|
|
150
|
+
return (
|
|
151
|
+
bool(lines)
|
|
152
|
+
and is_ok_token(lines[-1])
|
|
153
|
+
and any(has_no_tool_refusal_marker(line) for line in lines[:-1])
|
|
154
|
+
and not has_tool_available_marker(normalized)
|
|
155
|
+
and all(is_allowed_probe_explanation_line(line) for line in lines[:-1])
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def result_text(env: object) -> str:
|
|
160
|
+
return "\n".join(envelope_text_values(env))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def envelope_text_values(env: object) -> list[str]:
|
|
164
|
+
if not isinstance(env, dict):
|
|
165
|
+
return []
|
|
166
|
+
values: list[str] = []
|
|
167
|
+
structured = env.get("structured_output")
|
|
168
|
+
if isinstance(structured, str):
|
|
169
|
+
values.append(structured)
|
|
170
|
+
result = env.get("result")
|
|
171
|
+
if isinstance(result, str):
|
|
172
|
+
values.append(result)
|
|
173
|
+
return values
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def all_string_values(value: object) -> list[str]:
|
|
177
|
+
if isinstance(value, str):
|
|
178
|
+
return [value]
|
|
179
|
+
if isinstance(value, dict):
|
|
180
|
+
values: list[str] = []
|
|
181
|
+
for key, child in value.items():
|
|
182
|
+
if key in {"type", "subtype", "stop_reason", "terminal_reason", "session_id", "uuid"}:
|
|
183
|
+
continue
|
|
184
|
+
values.extend(all_string_values(child))
|
|
185
|
+
return values
|
|
186
|
+
if isinstance(value, list):
|
|
187
|
+
values: list[str] = []
|
|
188
|
+
for child in value:
|
|
189
|
+
values.extend(all_string_values(child))
|
|
190
|
+
return values
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def envelope_error_values(env: object) -> list[str]:
|
|
195
|
+
if not isinstance(env, dict):
|
|
196
|
+
return []
|
|
197
|
+
values: list[str] = []
|
|
198
|
+
for key in ("error", "message", "errors"):
|
|
199
|
+
value = env.get(key)
|
|
200
|
+
if isinstance(value, str):
|
|
201
|
+
values.append(value)
|
|
202
|
+
elif isinstance(value, (dict, list)) and value:
|
|
203
|
+
values.append(json.dumps(value, ensure_ascii=False))
|
|
204
|
+
return values
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def classification_surface(env: object, stdout: str, stderr: str) -> str:
|
|
208
|
+
values: list[str] = []
|
|
209
|
+
if isinstance(env, dict):
|
|
210
|
+
values.extend(envelope_text_values(env))
|
|
211
|
+
values.extend(envelope_error_values(env))
|
|
212
|
+
if env.get("type") != "result":
|
|
213
|
+
values.append(stdout)
|
|
214
|
+
else:
|
|
215
|
+
values.append(stdout)
|
|
216
|
+
values.append(stderr)
|
|
217
|
+
return "\n".join(value for value in values if value)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def has_auth_marker(text: str) -> bool:
|
|
221
|
+
return any(NOT_LOGGED_IN.search(line.strip()) for line in text.splitlines())
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def sanitize_snippet(text: str) -> str:
|
|
225
|
+
text = re.sub(r"~[\\/][^\s\"']+", "<path>", text)
|
|
226
|
+
text = re.sub(r"\bUsers[\\/][^\s\"']+", "<path>", text)
|
|
227
|
+
text = re.sub(r"\b[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+)*/\.[^\s\"']+", "<path>", text)
|
|
228
|
+
text = re.sub(r"/[A-Za-z0-9._-]+(?:/[^\s\"']+)+", "<path>", text)
|
|
229
|
+
text = re.sub(r"(^|[\s:\"'(])/[A-Za-z0-9._-]+(?=$|[\s\"')])", r"\1<path>", text)
|
|
230
|
+
text = re.sub(r"\b[A-Za-z0-9._-]+(?:/[A-Za-z0-9._-]+){2,}\b", "<path>", text)
|
|
231
|
+
text = re.sub(r"([A-Za-z]:\\[^\s]+|\\\\[^\s]+)", "<path>", text)
|
|
232
|
+
text = re.sub(r"\bsk-[A-Za-z0-9_-]{8,}\b", "<secret>", text)
|
|
233
|
+
text = re.sub(r'"[^"]*(?:oauth|token|key|secret)[^"]*"\s*:\s*"[^"]+"', '"<secret>":"<secret>"', text, flags=re.I)
|
|
234
|
+
text = re.sub(r"\b(?:oauth|token|key|secret)[=:][A-Za-z0-9_./-]{8,}\b", "<secret>", text, flags=re.I)
|
|
235
|
+
text = re.sub(r"\b[A-Fa-f0-9]{32,}\b", "<secret>", text)
|
|
236
|
+
snippet = " ".join(text.split())[:200]
|
|
237
|
+
if snippet == "<path>":
|
|
238
|
+
return "path-only diagnostic redacted; inspect local probe stderr for details"
|
|
239
|
+
return snippet
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def is_truthy_error(value: object) -> bool:
|
|
243
|
+
if isinstance(value, bool):
|
|
244
|
+
return value
|
|
245
|
+
if isinstance(value, int):
|
|
246
|
+
return value != 0
|
|
247
|
+
if isinstance(value, str):
|
|
248
|
+
return value.strip().lower() not in {"", "0", "false", "no", "none", "null"}
|
|
249
|
+
if isinstance(value, (dict, list)):
|
|
250
|
+
return bool(value)
|
|
251
|
+
return value is not None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def api_status_int(env: object) -> int | None:
|
|
255
|
+
if not isinstance(env, dict):
|
|
256
|
+
return None
|
|
257
|
+
status = env.get("api_error_status")
|
|
258
|
+
if status is None:
|
|
259
|
+
return None
|
|
260
|
+
try:
|
|
261
|
+
return int(status)
|
|
262
|
+
except (TypeError, ValueError):
|
|
263
|
+
return None
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _strict_object_pairs(pairs: list[tuple[str, object]]) -> dict:
|
|
267
|
+
"""Reject duplicate object keys: json.loads silently keeps the LAST value,
|
|
268
|
+
which would let `"capabilities":["bad"],"capabilities":[]` masquerade as
|
|
269
|
+
empty. A duplicate-key record is malformed evidence and must fail closed."""
|
|
270
|
+
obj: dict = {}
|
|
271
|
+
for key, value in pairs:
|
|
272
|
+
if key in obj:
|
|
273
|
+
raise ValueError(f"duplicate JSON object key: {key!r}")
|
|
274
|
+
obj[key] = value
|
|
275
|
+
return obj
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def loads_strict(text: str) -> object:
|
|
279
|
+
return json.loads(text, object_pairs_hook=_strict_object_pairs)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def parse_json_object(text: str) -> object | None:
|
|
283
|
+
stripped = normalize(text)
|
|
284
|
+
if not stripped:
|
|
285
|
+
return None
|
|
286
|
+
try:
|
|
287
|
+
return loads_strict(stripped)
|
|
288
|
+
except ValueError:
|
|
289
|
+
pass
|
|
290
|
+
parsed: list[object] = []
|
|
291
|
+
for line in stripped.splitlines():
|
|
292
|
+
candidate = line.strip()
|
|
293
|
+
if not candidate.startswith("{"):
|
|
294
|
+
continue
|
|
295
|
+
try:
|
|
296
|
+
parsed.append(loads_strict(candidate))
|
|
297
|
+
except ValueError:
|
|
298
|
+
pass
|
|
299
|
+
decoder = json.JSONDecoder(object_pairs_hook=_strict_object_pairs)
|
|
300
|
+
start = stripped.find("{")
|
|
301
|
+
while start != -1:
|
|
302
|
+
line_start = stripped.rfind("\n", 0, start) + 1
|
|
303
|
+
if stripped[line_start:start].strip():
|
|
304
|
+
start = stripped.find("{", start + 1)
|
|
305
|
+
continue
|
|
306
|
+
candidate = stripped[start:]
|
|
307
|
+
try:
|
|
308
|
+
value, end = decoder.raw_decode(candidate)
|
|
309
|
+
parsed.append(value)
|
|
310
|
+
start = stripped.find("{", start + end)
|
|
311
|
+
continue
|
|
312
|
+
except ValueError:
|
|
313
|
+
pass
|
|
314
|
+
start = stripped.find("{", start + 1)
|
|
315
|
+
result_values = [value for value in parsed if isinstance(value, dict) and value.get("type") == "result"]
|
|
316
|
+
for value in result_values:
|
|
317
|
+
if (
|
|
318
|
+
is_truthy_error(value.get("is_error"))
|
|
319
|
+
or value.get("api_error_status") is not None
|
|
320
|
+
or value.get("subtype") not in {None, "success"}
|
|
321
|
+
or value.get("permission_denials")
|
|
322
|
+
or envelope_error_values(value)
|
|
323
|
+
):
|
|
324
|
+
return value
|
|
325
|
+
if result_values:
|
|
326
|
+
return result_values[-1]
|
|
327
|
+
if parsed:
|
|
328
|
+
return parsed[0]
|
|
329
|
+
return None
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# The prompt attempts Bash, but the gate is broader: stream-json init data must
|
|
333
|
+
# prove that every declared tool/customization surface is empty, and no tool_use
|
|
334
|
+
# block may occur. Ground truth comes from the stream, not the model's reply
|
|
335
|
+
# token, which can hallucinate TOOL_ENABLED even when no tool exists.
|
|
336
|
+
PROBE_TOOL = "bash"
|
|
337
|
+
REQUIRED_EMPTY_INIT_FIELDS = (
|
|
338
|
+
"mcp_servers",
|
|
339
|
+
"slash_commands",
|
|
340
|
+
"skills",
|
|
341
|
+
"plugins",
|
|
342
|
+
)
|
|
343
|
+
# Fields that must be PRESENT (not merely empty). `permissionMode` is the only
|
|
344
|
+
# report of the reviewer's authority, and authority is scalar-shaped — which is
|
|
345
|
+
# why it is value-pinned rather than drift-tolerated. Without this, a CLI that
|
|
346
|
+
# renames the knob makes the old name simply vanish; absence is not "empty", and
|
|
347
|
+
# the drift policy below would wave the new scalar through as benign metadata.
|
|
348
|
+
# An init that no longer states its authority is unverifiable, not permissive.
|
|
349
|
+
REQUIRED_PRESENT_INIT_FIELDS = ("permissionMode",)
|
|
350
|
+
# Best-effort, deliberately NOT a boundary: name fragments that mark a scalar as
|
|
351
|
+
# authority-widening. The drift policy tolerates unknown scalars because they
|
|
352
|
+
# cannot ENUMERATE a surface — but a scalar can still GRANT one, so this catches
|
|
353
|
+
# the plausible spellings of a newly-added bypass knob
|
|
354
|
+
# (`dangerously_skip_permissions`, `bypass_sandbox`, ...).
|
|
355
|
+
#
|
|
356
|
+
# Honest limitation: a name-matching denylist over an open vocabulary cannot be
|
|
357
|
+
# complete, and this one is not. It is defense in depth on top of the complete
|
|
358
|
+
# check above (a renamed knob is caught by absence), chosen over reverting the
|
|
359
|
+
# whole relaxation — which would restore the outage this change exists to fix.
|
|
360
|
+
# It fails safe: a false positive refuses the lane as fallback-eligible drift,
|
|
361
|
+
# which now costs a reviewer-client switch rather than the review.
|
|
362
|
+
AUTHORITY_NAME_STEMS = (
|
|
363
|
+
"permission",
|
|
364
|
+
"bypass",
|
|
365
|
+
"dangerous",
|
|
366
|
+
"sandbox",
|
|
367
|
+
"privileg",
|
|
368
|
+
"escalat",
|
|
369
|
+
"elevat",
|
|
370
|
+
"unrestricted",
|
|
371
|
+
"authorit",
|
|
372
|
+
"admin",
|
|
373
|
+
"superuser",
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def is_authority_name(name: str) -> bool:
|
|
378
|
+
"""True when a field NAME reads as an authority knob.
|
|
379
|
+
|
|
380
|
+
Segment-wise, not raw substring. A substring test over short generic tokens
|
|
381
|
+
is a false-positive engine pointed straight back at the outage this change
|
|
382
|
+
removes: `auth` hits `author`, `root` hits `workspace_root`, and a benign
|
|
383
|
+
future metadata field would take every lane inconclusive on a perfectly
|
|
384
|
+
isolated run. Names are split on separators and camelCase humps, and a
|
|
385
|
+
segment must START with a stem — so `authorityLevel` matches while `author`
|
|
386
|
+
does not, and `adminMode` matches while `root_dir` does not.
|
|
387
|
+
"""
|
|
388
|
+
segments = re.split(r"[_.\-]+|(?<=[a-z0-9])(?=[A-Z])", name)
|
|
389
|
+
return any(
|
|
390
|
+
segment.lower().startswith(stem)
|
|
391
|
+
for segment in segments
|
|
392
|
+
for stem in AUTHORITY_NAME_STEMS
|
|
393
|
+
)
|
|
394
|
+
# This list is CLOSED to incremental extension. It is a denylist over an open
|
|
395
|
+
# vocabulary, so it can always be extended by one more plausible word, and
|
|
396
|
+
# chasing that is a loop with no end state — the completeness argument belongs
|
|
397
|
+
# to the required-presence check above, which is complete by construction. The
|
|
398
|
+
# remaining exposure is an added authority knob whose name matches none of these
|
|
399
|
+
# fragments; it is accepted, recorded, and owned, not patched round by round.
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def safe_identifier(name: object) -> str:
|
|
403
|
+
"""Render a CLI-supplied init field name safe to embed in a reason string.
|
|
404
|
+
|
|
405
|
+
The wrapper routes on the reason TEXT, and JSON object keys are arbitrary
|
|
406
|
+
strings — including ones containing spaces. Interpolating a raw field name
|
|
407
|
+
lets the inspected CLI choose which routing arm matches: a field literally
|
|
408
|
+
named `unrecognized surface-shaped init field` would drag a breach reason
|
|
409
|
+
into the fallback-eligible class. Restricting to an identifier charset makes
|
|
410
|
+
every multi-word routing phrase unrepresentable, and the length cap keeps a
|
|
411
|
+
pathological key from burying the rest of the reason.
|
|
412
|
+
"""
|
|
413
|
+
text = name if isinstance(name, str) else repr(name)
|
|
414
|
+
# `:` is kept: namespaced identifiers (`ccl-skills:testing-strategy`)
|
|
415
|
+
# stay readable in diagnostics and, containing no whitespace, still cannot
|
|
416
|
+
# spell a multi-word routing phrase.
|
|
417
|
+
cleaned = "".join(char if char.isalnum() or char in "_.-:" else "_" for char in text)
|
|
418
|
+
return (cleaned[:60] + "...") if len(cleaned) > 60 else (cleaned or "_")
|
|
419
|
+
# Explicitly known non-capability metadata from the installed stream-json init
|
|
420
|
+
# schema. `agents` is non-empty in safe mode, but cannot be invoked unless a
|
|
421
|
+
# corresponding tool appears in the separately checked `tools` allowlist.
|
|
422
|
+
#
|
|
423
|
+
# Drift policy (deliberate): the CLI adds scalar metadata fields nearly every
|
|
424
|
+
# release (`fast_mode_state`, `fast_mode_disabled_reason`, ...). Pinning field
|
|
425
|
+
# *names* would make every CLI upgrade a hard lane outage while proving nothing
|
|
426
|
+
# — a scalar cannot enumerate an invocable surface. So an unknown field fails
|
|
427
|
+
# closed only when its value is a NON-EMPTY list/dict, i.e. the only shape that
|
|
428
|
+
# could carry a new tool/skill/plugin/command surface. Invocability itself stays
|
|
429
|
+
# proven by the exact `tools` allowlist plus the tool_use scan, which no init
|
|
430
|
+
# field can bypass.
|
|
431
|
+
KNOWN_SAFE_INIT_METADATA_FIELDS = {
|
|
432
|
+
"type",
|
|
433
|
+
"subtype",
|
|
434
|
+
"cwd",
|
|
435
|
+
"session_id",
|
|
436
|
+
"model",
|
|
437
|
+
"permissionMode",
|
|
438
|
+
"apiKeySource",
|
|
439
|
+
"claude_code_version",
|
|
440
|
+
"output_style",
|
|
441
|
+
"agents",
|
|
442
|
+
"analytics_disabled",
|
|
443
|
+
"product_feedback_disabled",
|
|
444
|
+
"uuid",
|
|
445
|
+
"fast_mode_state",
|
|
446
|
+
}
|
|
447
|
+
KNOWN_SAFE_INIT_LIST_METADATA_FIELDS = {
|
|
448
|
+
# Claude Code 2.1.233 added this classification-only subset of the already
|
|
449
|
+
# declared slash_commands surface. It is safe only when every entry is a
|
|
450
|
+
# whole plain string already present in slash_commands; otherwise the new
|
|
451
|
+
# schema remains unverifiable and the lane fails closed.
|
|
452
|
+
"terminal_slash_commands",
|
|
453
|
+
}
|
|
454
|
+
KNOWN_SAFE_PERMISSION_MODES = {"default", "plan"}
|
|
455
|
+
KNOWN_SAFE_BUILTIN_SKILLS = {
|
|
456
|
+
"batch",
|
|
457
|
+
"claude-api",
|
|
458
|
+
"code-review",
|
|
459
|
+
"dataviz",
|
|
460
|
+
"debug",
|
|
461
|
+
"deep-research",
|
|
462
|
+
"design-sync",
|
|
463
|
+
"doctor",
|
|
464
|
+
"fewer-permission-prompts",
|
|
465
|
+
"loop",
|
|
466
|
+
"run",
|
|
467
|
+
"run-skill-generator",
|
|
468
|
+
"schedule",
|
|
469
|
+
"simplify",
|
|
470
|
+
"update-config",
|
|
471
|
+
"verify",
|
|
472
|
+
}
|
|
473
|
+
KNOWN_SAFE_BUILTIN_SLASH_COMMANDS = {
|
|
474
|
+
"__remote-workflow",
|
|
475
|
+
"agents",
|
|
476
|
+
"batch",
|
|
477
|
+
"claude-api",
|
|
478
|
+
"clear",
|
|
479
|
+
"code-review",
|
|
480
|
+
"color",
|
|
481
|
+
"compact",
|
|
482
|
+
"config",
|
|
483
|
+
"context",
|
|
484
|
+
"dataviz",
|
|
485
|
+
"debug",
|
|
486
|
+
"deep-research",
|
|
487
|
+
"design",
|
|
488
|
+
"design-consent",
|
|
489
|
+
"design-revoke",
|
|
490
|
+
"design-sync",
|
|
491
|
+
"doctor",
|
|
492
|
+
"effort",
|
|
493
|
+
"extra-usage",
|
|
494
|
+
"fast",
|
|
495
|
+
"fewer-permission-prompts",
|
|
496
|
+
"goal",
|
|
497
|
+
"heapdump",
|
|
498
|
+
# `/import` arrived in a CLI release after this snapshot was written, which
|
|
499
|
+
# is how the snapshot's cost was discovered: an unrecognised name used to be
|
|
500
|
+
# classified as a proven customization -- terminal and non-cascadable -- so
|
|
501
|
+
# one new built-in took out the whole reviewer lane. That is fixed at the
|
|
502
|
+
# CLASS level now (see is_bare_host_identifier): a bare name this list does
|
|
503
|
+
# not know is unverifiable and cascades. This list therefore no longer
|
|
504
|
+
# decides whether review is POSSIBLE, only whether the Claude lane is the
|
|
505
|
+
# one that serves it, so a missing name costs a client switch instead of the
|
|
506
|
+
# capability. Keep it accurate where it is cheap; do not treat adding a name
|
|
507
|
+
# as the fix for a lane outage.
|
|
508
|
+
"import",
|
|
509
|
+
"init",
|
|
510
|
+
"insights",
|
|
511
|
+
"loop",
|
|
512
|
+
"mcp",
|
|
513
|
+
"model",
|
|
514
|
+
"recap",
|
|
515
|
+
"reload-skills",
|
|
516
|
+
"rename",
|
|
517
|
+
"review",
|
|
518
|
+
"run",
|
|
519
|
+
"run-skill-generator",
|
|
520
|
+
"schedule",
|
|
521
|
+
"security-review",
|
|
522
|
+
"simplify",
|
|
523
|
+
"team-onboarding",
|
|
524
|
+
"ultrareview",
|
|
525
|
+
"update-config",
|
|
526
|
+
"usage",
|
|
527
|
+
"usage-credits",
|
|
528
|
+
"verify",
|
|
529
|
+
"workflow-launch-exec",
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def stream_events(text: str) -> list[dict]:
|
|
534
|
+
"""Parse newline-delimited JSON (stream-json) into a list of event objects.
|
|
535
|
+
Non-JSON or non-object lines are skipped; a single result envelope still
|
|
536
|
+
parses as a one-element list."""
|
|
537
|
+
events: list[dict] = []
|
|
538
|
+
for line in text.replace("\r", "").splitlines():
|
|
539
|
+
line = line.strip()
|
|
540
|
+
if not line.startswith("{"):
|
|
541
|
+
continue
|
|
542
|
+
try:
|
|
543
|
+
parsed = loads_strict(line)
|
|
544
|
+
except ValueError:
|
|
545
|
+
continue
|
|
546
|
+
if isinstance(parsed, dict):
|
|
547
|
+
events.append(parsed)
|
|
548
|
+
return events
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def stream_has_malformed_record(text: str) -> bool:
|
|
552
|
+
"""A line that looks like a stream-json record (starts with '{') but does not
|
|
553
|
+
parse as standalone JSON. In a complete (rc==0) stream-json capture every
|
|
554
|
+
event is one parseable line, so such a record means a tool declaration or
|
|
555
|
+
tool_use may have been silently dropped -> the ground-truth is untrustworthy.
|
|
556
|
+
(Pretty-printed single envelopes are handled by the text fallback, which never
|
|
557
|
+
reaches the stream ground-truth path; see main.)"""
|
|
558
|
+
for line in text.replace("\r", "").splitlines():
|
|
559
|
+
line = line.strip()
|
|
560
|
+
if not line.startswith("{"):
|
|
561
|
+
continue
|
|
562
|
+
try:
|
|
563
|
+
loads_strict(line)
|
|
564
|
+
except ValueError:
|
|
565
|
+
return True
|
|
566
|
+
return False
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# Event types that only appear in a stream-json (--verbose) capture, never in a
|
|
570
|
+
# plain single result envelope. Their presence means the parser MUST have the
|
|
571
|
+
# init ground-truth; absent init in such a capture = unverifiable -> fail closed.
|
|
572
|
+
STREAM_EVENT_TYPES = {"system", "assistant", "user"}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def is_stream_json_capture(events: list[dict]) -> bool:
|
|
576
|
+
return any(ev.get("type") in STREAM_EVENT_TYPES for ev in events)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def stream_has_nonjson_line(text: str) -> bool:
|
|
580
|
+
"""Any non-empty line that is not standalone JSON. A confirmed stream-json
|
|
581
|
+
capture is pure JSONL (verified: real captures have zero non-JSON lines), so
|
|
582
|
+
such a line is a corrupt/dropped record — including one that lost its leading
|
|
583
|
+
'{' and so slips past the '{'-prefixed malformed check."""
|
|
584
|
+
for line in text.replace("\r", "").splitlines():
|
|
585
|
+
stripped = line.strip()
|
|
586
|
+
if not stripped:
|
|
587
|
+
continue
|
|
588
|
+
try:
|
|
589
|
+
loads_strict(stripped)
|
|
590
|
+
except ValueError:
|
|
591
|
+
return True
|
|
592
|
+
return False
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def stream_nonjson_surface(text: str) -> str:
|
|
596
|
+
"""Return only non-JSON stream lines for transport/auth classification.
|
|
597
|
+
|
|
598
|
+
In runtime-surface mode, model reply prose lives inside JSON records and is
|
|
599
|
+
intentionally ignored. A CLI banner or startup diagnostic outside those
|
|
600
|
+
records is host-controlled evidence and must still classify auth/quota.
|
|
601
|
+
"""
|
|
602
|
+
lines: list[str] = []
|
|
603
|
+
for line in text.replace("\r", "").splitlines():
|
|
604
|
+
stripped = line.strip()
|
|
605
|
+
if not stripped:
|
|
606
|
+
continue
|
|
607
|
+
try:
|
|
608
|
+
loads_strict(stripped)
|
|
609
|
+
except ValueError:
|
|
610
|
+
lines.append(stripped)
|
|
611
|
+
return "\n".join(lines)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def stream_classification(text: str, events: list[dict]) -> tuple[bool, bool]:
|
|
615
|
+
"""Classify a capture as (is_stream, is_corrupt).
|
|
616
|
+
|
|
617
|
+
is_stream: a stream-only event type parsed (system/assistant/user), OR a
|
|
618
|
+
standalone record coexists with a '{'-prefixed malformed line (JSONL whose
|
|
619
|
+
init/tool_use line was dropped). A pretty-printed single envelope or plain
|
|
620
|
+
text yields neither.
|
|
621
|
+
is_corrupt: a '{'-prefixed unparseable line, OR — once we know it is a real
|
|
622
|
+
stream — any non-JSON line at all."""
|
|
623
|
+
confirmed = is_stream_json_capture(events)
|
|
624
|
+
brace_malformed = stream_has_malformed_record(text)
|
|
625
|
+
is_stream = confirmed or (bool(events) and brace_malformed)
|
|
626
|
+
is_corrupt = brace_malformed or (confirmed and stream_has_nonjson_line(text))
|
|
627
|
+
return is_stream, is_corrupt
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def init_declared_tools(events: list[dict]) -> set[str] | None:
|
|
631
|
+
"""Tools the session exposes, aggregated across ALL stream-json system/init
|
|
632
|
+
events (a later init must not be masked by an earlier empty one). Lowercased
|
|
633
|
+
union, or None when no init event with a tools list is present (ground-truth
|
|
634
|
+
unavailable -> caller falls back to text-based parsing)."""
|
|
635
|
+
declared: set[str] | None = None
|
|
636
|
+
for ev in events:
|
|
637
|
+
if ev.get("type") == "system" and ev.get("subtype") == "init":
|
|
638
|
+
tools = ev.get("tools")
|
|
639
|
+
if isinstance(tools, list):
|
|
640
|
+
names = {str(t).strip().lower() for t in tools if isinstance(t, str)}
|
|
641
|
+
declared = names if declared is None else declared | names
|
|
642
|
+
return declared
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def init_surface_state(
|
|
646
|
+
events: list[dict],
|
|
647
|
+
expected_tools: set[str] | None = None,
|
|
648
|
+
expected_native_skills: set[str] | None = None,
|
|
649
|
+
required_native_skills: set[str] | None = None,
|
|
650
|
+
host_init_baseline: dict[str, object] | None = None,
|
|
651
|
+
) -> tuple[
|
|
652
|
+
set[str], set[str], set[str], set[str], set[str], set[str], set[str]
|
|
653
|
+
] | None:
|
|
654
|
+
"""Return init isolation state across all init events.
|
|
655
|
+
|
|
656
|
+
Result fields: missing/invalid required fields, non-empty customization
|
|
657
|
+
fields, declared tool names, unrecognized surface-shaped fields, fields
|
|
658
|
+
declaring an unsafe value, fields whose authority is unverifiable, and
|
|
659
|
+
unclassifiable host-vocabulary identifiers. A missing or wrongly typed
|
|
660
|
+
required field is not treated as empty, because that would turn a CLI schema
|
|
661
|
+
change into a silent gate bypass. Unknown *scalar* metadata is tolerated on
|
|
662
|
+
purpose — see the drift policy on KNOWN_SAFE_INIT_METADATA_FIELDS.
|
|
663
|
+
|
|
664
|
+
Unsafe *values* are kept apart from unrecognized *shapes* on purpose: an
|
|
665
|
+
unknown schema addition means this lane cannot verify isolation (report it,
|
|
666
|
+
fall back to another reviewer), while a known field carrying an unsafe
|
|
667
|
+
value — `permissionMode: "bypassPermissions"` — is a breached boundary and
|
|
668
|
+
must terminate the lane. Merging them would let a real privilege escalation
|
|
669
|
+
inherit the drift class's softer next_action.
|
|
670
|
+
|
|
671
|
+
Unrecognized *identifiers* split the same way, and for the same reason: a
|
|
672
|
+
bare command/skill name outside the built-in snapshot cannot be shown to be
|
|
673
|
+
a customization, because the snapshot is of a vocabulary the host owns, so
|
|
674
|
+
it reports as unverifiable. A namespaced, path-shaped, duplicated or
|
|
675
|
+
unparseable entry IS a customization and stays in `nonempty`.
|
|
676
|
+
"""
|
|
677
|
+
expected_native_skills = expected_native_skills or set()
|
|
678
|
+
required_native_skills = {
|
|
679
|
+
name.lower() for name in (required_native_skills or set())
|
|
680
|
+
}
|
|
681
|
+
baseline_commands = (
|
|
682
|
+
set(host_init_baseline.get("slash_commands", ()))
|
|
683
|
+
if host_init_baseline
|
|
684
|
+
else set()
|
|
685
|
+
)
|
|
686
|
+
baseline_skills = (
|
|
687
|
+
set(host_init_baseline.get("skills", ()))
|
|
688
|
+
if host_init_baseline
|
|
689
|
+
else set()
|
|
690
|
+
)
|
|
691
|
+
baseline_version = (
|
|
692
|
+
host_init_baseline.get("claude_code_version")
|
|
693
|
+
if host_init_baseline
|
|
694
|
+
else None
|
|
695
|
+
)
|
|
696
|
+
saw_init = False
|
|
697
|
+
missing_or_invalid: set[str] = set()
|
|
698
|
+
nonempty: set[str] = set()
|
|
699
|
+
declared_tools: set[str] = set()
|
|
700
|
+
unknown_fields: set[str] = set()
|
|
701
|
+
unsafe_values: set[str] = set()
|
|
702
|
+
# Unverifiable authority is NOT a proven breach: we cannot show the reviewer
|
|
703
|
+
# was elevated, only that we can no longer show it was not. It therefore
|
|
704
|
+
# reports in the fallback-eligible class like schema drift — routing a
|
|
705
|
+
# renamed knob to the terminal class would reproduce the total outage this
|
|
706
|
+
# change exists to remove. A KNOWN field carrying a KNOWN-unsafe value stays
|
|
707
|
+
# terminal in `unsafe_values`, because that one is proven.
|
|
708
|
+
unverifiable_authority: set[str] = set()
|
|
709
|
+
# Host vocabulary this snapshot does not recognise. Kept apart from
|
|
710
|
+
# `nonempty` for the same reason `unverifiable_authority` is: reporting an
|
|
711
|
+
# unverifiable thing in the proven-breach class is what turned a routine CLI
|
|
712
|
+
# release into a total review outage. Bare identifiers only — see
|
|
713
|
+
# is_bare_host_identifier for what stays terminal.
|
|
714
|
+
unclassifiable_vocabulary: set[str] = set()
|
|
715
|
+
declared_customizations: dict[str, set[str]] = {
|
|
716
|
+
"slash_commands": set(),
|
|
717
|
+
"skills": set(),
|
|
718
|
+
"plugins": set(),
|
|
719
|
+
}
|
|
720
|
+
known_fields = {
|
|
721
|
+
"tools",
|
|
722
|
+
"capabilities",
|
|
723
|
+
*REQUIRED_EMPTY_INIT_FIELDS,
|
|
724
|
+
*KNOWN_SAFE_INIT_METADATA_FIELDS,
|
|
725
|
+
*KNOWN_SAFE_INIT_LIST_METADATA_FIELDS,
|
|
726
|
+
}
|
|
727
|
+
for ev in events:
|
|
728
|
+
if ev.get("type") != "system" or ev.get("subtype") != "init":
|
|
729
|
+
continue
|
|
730
|
+
saw_init = True
|
|
731
|
+
tools = ev.get("tools")
|
|
732
|
+
if not isinstance(tools, list):
|
|
733
|
+
missing_or_invalid.add("tools")
|
|
734
|
+
elif any(not isinstance(tool, str) for tool in tools):
|
|
735
|
+
# Do not turn a future descriptor/object schema into an empty set.
|
|
736
|
+
# The installed stream contract is a list of names; any other
|
|
737
|
+
# element shape is unverifiable and must fail closed.
|
|
738
|
+
missing_or_invalid.add("tools")
|
|
739
|
+
else:
|
|
740
|
+
declared_tools.update(
|
|
741
|
+
tool.strip().lower() for tool in tools
|
|
742
|
+
)
|
|
743
|
+
for field in REQUIRED_EMPTY_INIT_FIELDS:
|
|
744
|
+
value = ev.get(field)
|
|
745
|
+
if not isinstance(value, list):
|
|
746
|
+
missing_or_invalid.add(field)
|
|
747
|
+
elif field in declared_customizations:
|
|
748
|
+
identifiers = [
|
|
749
|
+
customization_entry_identifier(entry) for entry in value
|
|
750
|
+
]
|
|
751
|
+
declared_customizations[field].update(identifiers)
|
|
752
|
+
if len(identifiers) != len(set(identifiers)):
|
|
753
|
+
nonempty.add(field)
|
|
754
|
+
if value and expected_native_skills:
|
|
755
|
+
for entry in value:
|
|
756
|
+
identifier = customization_entry_identifier(entry)
|
|
757
|
+
if field in HOST_VOCABULARY_FIELDS and (
|
|
758
|
+
not host_entry_is_whole(entry, identifier)
|
|
759
|
+
):
|
|
760
|
+
# Nothing may be discarded to reach a verdict on
|
|
761
|
+
# these fields — see host_entry_is_whole. Runs BEFORE
|
|
762
|
+
# the allowlist, because the allowlist consumes the
|
|
763
|
+
# same lossy identifier and would clear the entry
|
|
764
|
+
# outright on a truncated or dict-supplied name.
|
|
765
|
+
# Legitimate namespaced entries are unaffected: their
|
|
766
|
+
# whole value IS the identifier.
|
|
767
|
+
nonempty.add(field)
|
|
768
|
+
continue
|
|
769
|
+
if customization_entry_allowed(
|
|
770
|
+
field,
|
|
771
|
+
entry,
|
|
772
|
+
expected_native_skills,
|
|
773
|
+
baseline_commands,
|
|
774
|
+
baseline_skills,
|
|
775
|
+
):
|
|
776
|
+
continue
|
|
777
|
+
if field in HOST_VOCABULARY_FIELDS and (
|
|
778
|
+
is_bare_host_identifier(identifier)
|
|
779
|
+
):
|
|
780
|
+
# Cannot be shown to be a customization: the
|
|
781
|
+
# allowlist it failed is a snapshot of a vocabulary
|
|
782
|
+
# the host owns. Refuse, but stay cascadable.
|
|
783
|
+
unclassifiable_vocabulary.add(
|
|
784
|
+
f"{field}:{identifier}"
|
|
785
|
+
)
|
|
786
|
+
else:
|
|
787
|
+
nonempty.add(field)
|
|
788
|
+
elif value:
|
|
789
|
+
nonempty.add(field)
|
|
790
|
+
elif value:
|
|
791
|
+
nonempty.add(field)
|
|
792
|
+
# Per EVENT, not on the union across events: a later init that drops or
|
|
793
|
+
# renames the authority field must not be masked by an earlier one that
|
|
794
|
+
# stated it. Every other required-field check in this loop is per event;
|
|
795
|
+
# this one was not, which is exactly the masking shape the existing
|
|
796
|
+
# later-init-declares-Bash fixture guards against for tools.
|
|
797
|
+
for required in REQUIRED_PRESENT_INIT_FIELDS:
|
|
798
|
+
if required not in ev:
|
|
799
|
+
unverifiable_authority.add(required)
|
|
800
|
+
for field, value in ev.items():
|
|
801
|
+
if field not in known_fields and is_authority_name(field):
|
|
802
|
+
# Authority is scalar-shaped, so the container test below
|
|
803
|
+
# cannot see it. Flagged regardless of value shape: a
|
|
804
|
+
# newly-added authority knob is unreviewed either way.
|
|
805
|
+
unverifiable_authority.add(field)
|
|
806
|
+
elif field not in known_fields:
|
|
807
|
+
# Version-drift tolerance: only a non-empty container under an
|
|
808
|
+
# unknown name can enumerate a new invocable surface. A scalar
|
|
809
|
+
# (or an empty list/dict) is metadata and stays allowed, so a
|
|
810
|
+
# routine CLI release does not take the reviewer lane down.
|
|
811
|
+
if isinstance(value, (list, dict)) and value:
|
|
812
|
+
unknown_fields.add(field)
|
|
813
|
+
elif field in ("agents", "capabilities"):
|
|
814
|
+
# Shape-only, no value vocabulary: `agents` needs the Agent tool
|
|
815
|
+
# and `capabilities` announces host stream-protocol features —
|
|
816
|
+
# neither is invocable unless a tool shows up in the separately
|
|
817
|
+
# checked `tools` allowlist, so pinning their member names only
|
|
818
|
+
# buys CLI-version brittleness. A non-string member means an
|
|
819
|
+
# unverifiable descriptor schema and still fails closed.
|
|
820
|
+
if not isinstance(value, list) or any(
|
|
821
|
+
not isinstance(item, str) for item in value
|
|
822
|
+
):
|
|
823
|
+
unknown_fields.add(field)
|
|
824
|
+
elif field == "permissionMode":
|
|
825
|
+
if value not in KNOWN_SAFE_PERMISSION_MODES:
|
|
826
|
+
unsafe_values.add(field)
|
|
827
|
+
elif field in KNOWN_SAFE_INIT_LIST_METADATA_FIELDS:
|
|
828
|
+
if not isinstance(value, list) or any(
|
|
829
|
+
not isinstance(item, str) for item in value
|
|
830
|
+
):
|
|
831
|
+
unknown_fields.add(field)
|
|
832
|
+
else:
|
|
833
|
+
identifiers = [
|
|
834
|
+
customization_entry_identifier(item) for item in value
|
|
835
|
+
]
|
|
836
|
+
if (
|
|
837
|
+
len(identifiers) != len(set(identifiers))
|
|
838
|
+
or any(
|
|
839
|
+
not host_entry_is_whole(item, identifier)
|
|
840
|
+
for item, identifier in zip(value, identifiers)
|
|
841
|
+
)
|
|
842
|
+
or not set(identifiers).issubset(
|
|
843
|
+
declared_customizations["slash_commands"]
|
|
844
|
+
)
|
|
845
|
+
):
|
|
846
|
+
unknown_fields.add(field)
|
|
847
|
+
elif field in KNOWN_SAFE_INIT_METADATA_FIELDS and isinstance(
|
|
848
|
+
value, (list, dict)
|
|
849
|
+
):
|
|
850
|
+
unknown_fields.add(field)
|
|
851
|
+
if baseline_version is not None and ev.get("claude_code_version") != baseline_version:
|
|
852
|
+
# The two invocations no longer prove one same-version host
|
|
853
|
+
# vocabulary snapshot. Refuse this lane, but treat the mismatch as
|
|
854
|
+
# capability drift rather than a proven tool/authority breach so a
|
|
855
|
+
# different reviewer may continue.
|
|
856
|
+
unknown_fields.add("claude_code_version:host-baseline-mismatch")
|
|
857
|
+
if not saw_init:
|
|
858
|
+
return None
|
|
859
|
+
if expected_native_skills:
|
|
860
|
+
if "ccl-skills" not in declared_customizations["plugins"]:
|
|
861
|
+
missing_or_invalid.add("plugins:ccl-skills")
|
|
862
|
+
ambiguous_required_skills = (
|
|
863
|
+
required_native_skills & KNOWN_SAFE_BUILTIN_SKILLS
|
|
864
|
+
)
|
|
865
|
+
for skill_name in ambiguous_required_skills:
|
|
866
|
+
missing_or_invalid.add(f"skills:ambiguous-selected-owner:{skill_name}")
|
|
867
|
+
expected_ccl_skill_identifiers = {
|
|
868
|
+
identifier
|
|
869
|
+
for skill_name in expected_native_skills
|
|
870
|
+
for identifier in (skill_name, f"ccl-skills:{skill_name}")
|
|
871
|
+
}
|
|
872
|
+
declared_ccl_skills = (
|
|
873
|
+
declared_customizations["skills"]
|
|
874
|
+
& expected_ccl_skill_identifiers
|
|
875
|
+
) - KNOWN_SAFE_BUILTIN_SKILLS
|
|
876
|
+
if declared_ccl_skills:
|
|
877
|
+
for skill_name in required_native_skills:
|
|
878
|
+
namespaced = f"ccl-skills:{skill_name}"
|
|
879
|
+
if not {
|
|
880
|
+
skill_name,
|
|
881
|
+
namespaced,
|
|
882
|
+
} & declared_customizations["skills"]:
|
|
883
|
+
missing_or_invalid.add(f"skills:{skill_name}")
|
|
884
|
+
declared_ccl_commands = {
|
|
885
|
+
identifier
|
|
886
|
+
for identifier in declared_customizations["slash_commands"]
|
|
887
|
+
if identifier.startswith("ccl-skills:")
|
|
888
|
+
}
|
|
889
|
+
if declared_ccl_commands:
|
|
890
|
+
for skill_name in required_native_skills:
|
|
891
|
+
namespaced = f"ccl-skills:{skill_name}"
|
|
892
|
+
if namespaced not in declared_customizations["slash_commands"]:
|
|
893
|
+
missing_or_invalid.add(f"slash_commands:{namespaced}")
|
|
894
|
+
return (
|
|
895
|
+
missing_or_invalid,
|
|
896
|
+
nonempty,
|
|
897
|
+
declared_tools,
|
|
898
|
+
unknown_fields,
|
|
899
|
+
unsafe_values,
|
|
900
|
+
unverifiable_authority,
|
|
901
|
+
unclassifiable_vocabulary,
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def customization_entry_identifier(entry: object) -> str:
|
|
906
|
+
raw_identifier = ""
|
|
907
|
+
if isinstance(entry, str):
|
|
908
|
+
raw_identifier = entry.strip().split(maxsplit=1)[0]
|
|
909
|
+
elif isinstance(entry, dict):
|
|
910
|
+
for key in ("name", "command", "id"):
|
|
911
|
+
candidate = entry.get(key)
|
|
912
|
+
if isinstance(candidate, str) and candidate.strip():
|
|
913
|
+
raw_identifier = candidate.strip().split(maxsplit=1)[0]
|
|
914
|
+
break
|
|
915
|
+
if not re.fullmatch(r"[A-Za-z0-9_./:@+-]{1,120}", raw_identifier):
|
|
916
|
+
return "<unidentified>"
|
|
917
|
+
return raw_identifier.lower().lstrip("/")
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
def customization_entry_allowed(
|
|
921
|
+
field: str,
|
|
922
|
+
entry: object,
|
|
923
|
+
expected_native_skills: set[str],
|
|
924
|
+
baseline_commands: set[str] | None = None,
|
|
925
|
+
baseline_skills: set[str] | None = None,
|
|
926
|
+
) -> bool:
|
|
927
|
+
identifier = customization_entry_identifier(entry)
|
|
928
|
+
baseline_commands = baseline_commands or set()
|
|
929
|
+
# Baseline skills remain available to drift diagnostics, but never grant
|
|
930
|
+
# formal-run authority: a future safe-mode regression could otherwise turn
|
|
931
|
+
# one leaked user skill into an accepted callable capability.
|
|
932
|
+
baseline_skills = baseline_skills or set()
|
|
933
|
+
selected_names = {name.lower() for name in expected_native_skills}
|
|
934
|
+
selected_namespaced = {
|
|
935
|
+
f"ccl-skills:{name}" for name in selected_names
|
|
936
|
+
}
|
|
937
|
+
if field == "plugins":
|
|
938
|
+
return identifier == "ccl-skills"
|
|
939
|
+
if field == "skills":
|
|
940
|
+
return (
|
|
941
|
+
identifier in KNOWN_SAFE_BUILTIN_SKILLS
|
|
942
|
+
or identifier in selected_names
|
|
943
|
+
or identifier in selected_namespaced
|
|
944
|
+
)
|
|
945
|
+
if field == "slash_commands":
|
|
946
|
+
return (
|
|
947
|
+
identifier in KNOWN_SAFE_BUILTIN_SLASH_COMMANDS
|
|
948
|
+
or identifier in baseline_commands
|
|
949
|
+
or identifier in selected_namespaced
|
|
950
|
+
)
|
|
951
|
+
return False
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
# Fields whose entries, in a real run, come from the HOST's built-in vocabulary
|
|
955
|
+
# rather than from anything this repo installs: measured at CLI 2.1.220, the
|
|
956
|
+
# review-skill invocation reports 46 built-in commands and 16 built-in skills and
|
|
957
|
+
# nothing else. `plugins` is deliberately absent -- a plugin is user-installed by
|
|
958
|
+
# definition, and the expected set there is exactly the one this repo owns.
|
|
959
|
+
HOST_VOCABULARY_FIELDS = ("slash_commands", "skills")
|
|
960
|
+
# A bare identifier: no namespace, no path. Deliberately NOT "anything the
|
|
961
|
+
# allowlist missed" -- the discriminator has to be a property of the identifier
|
|
962
|
+
# itself, because the allowlist is the thing that cannot be trusted to be
|
|
963
|
+
# current.
|
|
964
|
+
BARE_HOST_IDENTIFIER = re.compile(r"[a-z0-9][a-z0-9_.-]*\Z")
|
|
965
|
+
|
|
966
|
+
|
|
967
|
+
def host_entry_is_whole(entry: object, identifier: str) -> bool:
|
|
968
|
+
"""True when nothing about a host-vocabulary entry was discarded to read it.
|
|
969
|
+
|
|
970
|
+
`customization_entry_identifier` is deliberately lossy — it keeps the first
|
|
971
|
+
whitespace-delimited token and reads a dict's `name` — which is right for a
|
|
972
|
+
DIAGNOSTIC string and wrong for a trust decision. Three review findings in
|
|
973
|
+
this slice were one shape: evidence present in the entry but never read. A
|
|
974
|
+
dict hid a path in a sibling key; a dict hid it under an allowed built-in
|
|
975
|
+
name; and `"brand-new evil-plugin:pwn"` truncated to `brand-new` while the
|
|
976
|
+
discarded suffix was the very proof of a customization.
|
|
977
|
+
|
|
978
|
+
So the classification consumes the WHOLE value: the entry must be a plain
|
|
979
|
+
string whose normalization — case folding and at most one leading `/`, the
|
|
980
|
+
only normalization the identifier helper itself applies — reproduces the
|
|
981
|
+
identifier exactly. Any remainder, any other shape, and the entry is a
|
|
982
|
+
customization this parser cannot clear. Checked BEFORE the allowlist,
|
|
983
|
+
because the allowlist reads the same truncated token.
|
|
984
|
+
"""
|
|
985
|
+
if not isinstance(entry, str):
|
|
986
|
+
return False
|
|
987
|
+
# Deliberately NO `.strip()`. The first version of this function called it,
|
|
988
|
+
# which reproduced inside the fix the exact lossiness the fix exists to
|
|
989
|
+
# reject: `"import "` stripped to `import`, compared equal to the identifier,
|
|
990
|
+
# was declared whole, and the allowlist then ACCEPTED it with isolation
|
|
991
|
+
# reported verified. Surrounding whitespace is part of the value, so an entry
|
|
992
|
+
# carrying any is not a plain host name.
|
|
993
|
+
normalized = entry.lower()
|
|
994
|
+
if normalized.startswith("/"):
|
|
995
|
+
normalized = normalized[1:]
|
|
996
|
+
return normalized == identifier
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def is_bare_host_identifier(identifier: str) -> bool:
|
|
1000
|
+
"""True when a disallowed entry cannot be PROVEN to be a customization.
|
|
1001
|
+
|
|
1002
|
+
Only ever consulted for a PLAIN STRING entry — see the caller. A dict entry
|
|
1003
|
+
is a structured descriptor whose other keys this parser does not validate,
|
|
1004
|
+
so `{"name": "brand-new", "command": "/x/y"}` would otherwise be classified
|
|
1005
|
+
on its bare `name` while a sibling key carried path-shaped proof of a real
|
|
1006
|
+
customization. That is not the unclassifiable case this class is for: the
|
|
1007
|
+
evidence was present and merely unread. Measured against the real CLI, both
|
|
1008
|
+
host-vocabulary fields arrive as plain strings (only `plugins`, already
|
|
1009
|
+
excluded, uses dicts), so refusing dicts here costs nothing operationally.
|
|
1010
|
+
|
|
1011
|
+
`customization_entry_identifier` has already lowercased, stripped a leading
|
|
1012
|
+
`/`, and replaced anything it could not parse with `<unidentified>`. What is
|
|
1013
|
+
left is bare only if it carries no namespace separator and no path
|
|
1014
|
+
separator, so each of these stays a proven breach:
|
|
1015
|
+
|
|
1016
|
+
* `evil-plugin:pwn` -- a namespace proves a surface beyond the one
|
|
1017
|
+
expected plugin, which is a customization, not host vocabulary.
|
|
1018
|
+
* `dir/cmd` -- no host built-in is spelled as a path.
|
|
1019
|
+
* `<unidentified>` -- an entry that failed the charset or was not a
|
|
1020
|
+
string/dict is not evidence of anything, least of all of host origin.
|
|
1021
|
+
|
|
1022
|
+
Note what this does NOT claim: a bare name may still be a user-authored
|
|
1023
|
+
command. The point is that we cannot tell, and the unclassifiable case
|
|
1024
|
+
belongs in the class that refuses and cascades rather than the class that
|
|
1025
|
+
refuses and destroys the capability. Isolation itself is still proven only
|
|
1026
|
+
by the exact `tools` allowlist, the tool_use scan, and the value-pinned
|
|
1027
|
+
`permissionMode` -- none of which any identifier here can affect.
|
|
1028
|
+
"""
|
|
1029
|
+
return bool(BARE_HOST_IDENTIFIER.fullmatch(identifier))
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
def load_host_init_baseline(path: str) -> tuple[dict[str, object] | None, str | None]:
|
|
1033
|
+
"""Read one safe-mode, no-plugin init capture as version-owned vocabulary.
|
|
1034
|
+
|
|
1035
|
+
The baseline is not a verdict and its model result is ignored. It may name
|
|
1036
|
+
host commands and skills only because the wrapper invocation disables
|
|
1037
|
+
setting sources, CLAUDE.md, auto-memory, MCP, tools, and plugins. Any tool,
|
|
1038
|
+
plugin, malformed identifier, authority drift, or unknown non-empty surface
|
|
1039
|
+
keeps the baseline unusable rather than laundering it into the main run.
|
|
1040
|
+
"""
|
|
1041
|
+
try:
|
|
1042
|
+
text = Path(path).read_text(encoding="utf-8", errors="replace")
|
|
1043
|
+
except OSError:
|
|
1044
|
+
return None, "Claude host-vocabulary baseline could not be read"
|
|
1045
|
+
events = stream_events(normalize(text))
|
|
1046
|
+
stream_like, stream_corrupt = stream_classification(text, events)
|
|
1047
|
+
init_events = [
|
|
1048
|
+
event
|
|
1049
|
+
for event in events
|
|
1050
|
+
if event.get("type") == "system" and event.get("subtype") == "init"
|
|
1051
|
+
]
|
|
1052
|
+
if not stream_like or stream_corrupt or len(init_events) != 1:
|
|
1053
|
+
return None, "Claude host-vocabulary baseline lacks one intact init event"
|
|
1054
|
+
if invoked_tool_names(events):
|
|
1055
|
+
return None, "Claude host-vocabulary baseline invoked a tool"
|
|
1056
|
+
init_event = init_events[0]
|
|
1057
|
+
if init_event.get("tools") != []:
|
|
1058
|
+
return None, "Claude host-vocabulary baseline exposed a tool"
|
|
1059
|
+
# Keep this set-derived rather than spelling today's MCP/plugin fields
|
|
1060
|
+
# twice. If the formal init adds another required-empty customization
|
|
1061
|
+
# surface, the baseline must prove it empty before its command vocabulary
|
|
1062
|
+
# can carry authority. Known metadata such as `agents` is deliberately not
|
|
1063
|
+
# in REQUIRED_EMPTY_INIT_FIELDS: with tools pinned empty it is descriptive,
|
|
1064
|
+
# not model-invocable.
|
|
1065
|
+
for field in REQUIRED_EMPTY_INIT_FIELDS:
|
|
1066
|
+
if field not in HOST_VOCABULARY_FIELDS and init_event.get(field) != []:
|
|
1067
|
+
return None, f"Claude host-vocabulary baseline exposed {field}"
|
|
1068
|
+
if init_event.get("permissionMode") != "default":
|
|
1069
|
+
return None, "Claude host-vocabulary baseline changed permission authority"
|
|
1070
|
+
version = init_event.get("claude_code_version")
|
|
1071
|
+
if not isinstance(version, str) or not version or len(version) > 120:
|
|
1072
|
+
return None, "Claude host-vocabulary baseline lacks a bounded CLI version"
|
|
1073
|
+
|
|
1074
|
+
surfaces: dict[str, tuple[str, ...]] = {}
|
|
1075
|
+
for field in HOST_VOCABULARY_FIELDS:
|
|
1076
|
+
known_host_identifiers = (
|
|
1077
|
+
KNOWN_SAFE_BUILTIN_SLASH_COMMANDS
|
|
1078
|
+
if field == "slash_commands"
|
|
1079
|
+
else KNOWN_SAFE_BUILTIN_SKILLS
|
|
1080
|
+
)
|
|
1081
|
+
value = init_event.get(field)
|
|
1082
|
+
if not isinstance(value, list) or any(not isinstance(item, str) for item in value):
|
|
1083
|
+
return None, f"Claude host-vocabulary baseline has malformed {field}"
|
|
1084
|
+
identifiers = [customization_entry_identifier(item) for item in value]
|
|
1085
|
+
if (
|
|
1086
|
+
len(identifiers) != len(set(identifiers))
|
|
1087
|
+
or any(
|
|
1088
|
+
not host_entry_is_whole(item, identifier)
|
|
1089
|
+
for item, identifier in zip(value, identifiers)
|
|
1090
|
+
)
|
|
1091
|
+
or any(
|
|
1092
|
+
identifier not in known_host_identifiers
|
|
1093
|
+
and not is_bare_host_identifier(identifier)
|
|
1094
|
+
for identifier in identifiers
|
|
1095
|
+
)
|
|
1096
|
+
):
|
|
1097
|
+
return None, f"Claude host-vocabulary baseline has unsafe {field}"
|
|
1098
|
+
surfaces[field] = tuple(identifiers)
|
|
1099
|
+
|
|
1100
|
+
terminal_commands = init_event.get("terminal_slash_commands", [])
|
|
1101
|
+
if not isinstance(terminal_commands, list) or any(
|
|
1102
|
+
not isinstance(item, str) for item in terminal_commands
|
|
1103
|
+
):
|
|
1104
|
+
return None, "Claude host-vocabulary baseline has malformed terminal commands"
|
|
1105
|
+
terminal_identifiers = [
|
|
1106
|
+
customization_entry_identifier(item) for item in terminal_commands
|
|
1107
|
+
]
|
|
1108
|
+
if (
|
|
1109
|
+
len(terminal_identifiers) != len(set(terminal_identifiers))
|
|
1110
|
+
or any(
|
|
1111
|
+
not host_entry_is_whole(item, identifier)
|
|
1112
|
+
for item, identifier in zip(terminal_commands, terminal_identifiers)
|
|
1113
|
+
)
|
|
1114
|
+
or not set(terminal_identifiers).issubset(set(surfaces["slash_commands"]))
|
|
1115
|
+
):
|
|
1116
|
+
return None, "Claude host-vocabulary baseline has unsafe terminal commands"
|
|
1117
|
+
|
|
1118
|
+
known_fields = {
|
|
1119
|
+
"tools",
|
|
1120
|
+
"capabilities",
|
|
1121
|
+
*REQUIRED_EMPTY_INIT_FIELDS,
|
|
1122
|
+
*KNOWN_SAFE_INIT_METADATA_FIELDS,
|
|
1123
|
+
*KNOWN_SAFE_INIT_LIST_METADATA_FIELDS,
|
|
1124
|
+
}
|
|
1125
|
+
for field, value in init_event.items():
|
|
1126
|
+
if field not in known_fields and is_authority_name(field):
|
|
1127
|
+
return None, "Claude host-vocabulary baseline has unverifiable authority"
|
|
1128
|
+
if field not in known_fields and isinstance(value, (list, dict)) and value:
|
|
1129
|
+
return None, "Claude host-vocabulary baseline has an unknown non-empty surface"
|
|
1130
|
+
if field in ("agents", "capabilities") and (
|
|
1131
|
+
not isinstance(value, list)
|
|
1132
|
+
or any(not isinstance(item, str) for item in value)
|
|
1133
|
+
):
|
|
1134
|
+
return None, f"Claude host-vocabulary baseline has malformed {field}"
|
|
1135
|
+
|
|
1136
|
+
return {
|
|
1137
|
+
"claude_code_version": version,
|
|
1138
|
+
"slash_commands": surfaces["slash_commands"],
|
|
1139
|
+
"skills": surfaces["skills"],
|
|
1140
|
+
}, None
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
def unexpected_customization_identifiers(
|
|
1144
|
+
events: list[dict],
|
|
1145
|
+
expected_native_skills: set[str] | None = None,
|
|
1146
|
+
host_init_baseline: dict[str, object] | None = None,
|
|
1147
|
+
) -> set[str]:
|
|
1148
|
+
"""Return bounded identifiers without exposing customization bodies."""
|
|
1149
|
+
expected_native_skills = expected_native_skills or set()
|
|
1150
|
+
baseline_commands = (
|
|
1151
|
+
set(host_init_baseline.get("slash_commands", ()))
|
|
1152
|
+
if host_init_baseline
|
|
1153
|
+
else set()
|
|
1154
|
+
)
|
|
1155
|
+
baseline_skills = (
|
|
1156
|
+
set(host_init_baseline.get("skills", ()))
|
|
1157
|
+
if host_init_baseline
|
|
1158
|
+
else set()
|
|
1159
|
+
)
|
|
1160
|
+
unexpected: set[str] = set()
|
|
1161
|
+
for ev in events:
|
|
1162
|
+
if ev.get("type") != "system" or ev.get("subtype") != "init":
|
|
1163
|
+
continue
|
|
1164
|
+
for field in ("slash_commands", "skills", "plugins"):
|
|
1165
|
+
value = ev.get(field)
|
|
1166
|
+
if not isinstance(value, list):
|
|
1167
|
+
continue
|
|
1168
|
+
for entry in value:
|
|
1169
|
+
if customization_entry_allowed(
|
|
1170
|
+
field,
|
|
1171
|
+
entry,
|
|
1172
|
+
expected_native_skills,
|
|
1173
|
+
baseline_commands,
|
|
1174
|
+
baseline_skills,
|
|
1175
|
+
):
|
|
1176
|
+
continue
|
|
1177
|
+
unexpected.add(
|
|
1178
|
+
f"{field}:{customization_entry_identifier(entry)}"
|
|
1179
|
+
)
|
|
1180
|
+
return unexpected
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
def invoked_tool_names(events: list[dict]) -> set[str]:
|
|
1184
|
+
"""Names of tools the model actually invoked (tool_use blocks). Empty under a
|
|
1185
|
+
real no-tool sandbox even when the reply text claims a tool ran."""
|
|
1186
|
+
names: set[str] = set()
|
|
1187
|
+
for ev in events:
|
|
1188
|
+
message = ev.get("message")
|
|
1189
|
+
content = message.get("content") if isinstance(message, dict) else None
|
|
1190
|
+
if not isinstance(content, list):
|
|
1191
|
+
continue
|
|
1192
|
+
for block in content:
|
|
1193
|
+
if isinstance(block, dict) and block.get("type") == "tool_use":
|
|
1194
|
+
name = block.get("name")
|
|
1195
|
+
if isinstance(name, str):
|
|
1196
|
+
names.add(name.strip().lower())
|
|
1197
|
+
else:
|
|
1198
|
+
# A malformed tool_use still proves that an invocation
|
|
1199
|
+
# record exists. Preserve it as an impossible expected
|
|
1200
|
+
# name so every allowlist comparison rejects the stream.
|
|
1201
|
+
names.add("<invalid-tool-name>")
|
|
1202
|
+
return names
|
|
1203
|
+
|
|
1204
|
+
|
|
1205
|
+
def bash_tool_present(events: list[dict]) -> bool:
|
|
1206
|
+
"""Ground-truth: was the Bash tool available to or used by the session? An
|
|
1207
|
+
actual Bash tool_use is a hard fail INDEPENDENT of the init event (a stream
|
|
1208
|
+
missing its init must not launder a real Bash call through the text
|
|
1209
|
+
fallback)."""
|
|
1210
|
+
if PROBE_TOOL in invoked_tool_names(events):
|
|
1211
|
+
return True
|
|
1212
|
+
declared = init_declared_tools(events)
|
|
1213
|
+
return declared is not None and PROBE_TOOL in declared
|
|
1214
|
+
|
|
1215
|
+
|
|
1216
|
+
def envelope_structurally_clean(
|
|
1217
|
+
env: object,
|
|
1218
|
+
stdout: str = "",
|
|
1219
|
+
stderr: str = "",
|
|
1220
|
+
*,
|
|
1221
|
+
ignore_reply_text: bool = False,
|
|
1222
|
+
) -> bool:
|
|
1223
|
+
"""Result envelope carries no error/auth/quota/permission signal. Excludes the
|
|
1224
|
+
reply-text checks so a ground-truth caller can ignore a hallucinated reply."""
|
|
1225
|
+
if not isinstance(env, dict):
|
|
1226
|
+
return False
|
|
1227
|
+
if env.get("type") != "result":
|
|
1228
|
+
return False
|
|
1229
|
+
if is_truthy_error(env.get("is_error")):
|
|
1230
|
+
return False
|
|
1231
|
+
api_status = api_status_int(env)
|
|
1232
|
+
raw_api_status = env.get("api_error_status")
|
|
1233
|
+
if raw_api_status not in {None, 0, "0"} and (api_status is None or api_status >= 400):
|
|
1234
|
+
return False
|
|
1235
|
+
if env.get("permission_denials"):
|
|
1236
|
+
return False
|
|
1237
|
+
if env.get("subtype") not in {None, "success", "completed", "stop_turn", "end_turn"}:
|
|
1238
|
+
return False
|
|
1239
|
+
if envelope_error_values(env):
|
|
1240
|
+
return False
|
|
1241
|
+
if ignore_reply_text:
|
|
1242
|
+
# Main runtime attestation validates the result envelope's structural
|
|
1243
|
+
# error fields here, while parse_review_json.py owns the model payload.
|
|
1244
|
+
# Preserve stderr auth/quota signals without trusting or rejecting
|
|
1245
|
+
# model-authored reply prose that can quote those same words.
|
|
1246
|
+
surface = stderr
|
|
1247
|
+
else:
|
|
1248
|
+
surface = "\n".join(
|
|
1249
|
+
value
|
|
1250
|
+
for value in (classification_surface(env, stdout, stderr), stdout, stderr)
|
|
1251
|
+
if value
|
|
1252
|
+
)
|
|
1253
|
+
if QUOTA.search(surface) or has_auth_marker(surface):
|
|
1254
|
+
return False
|
|
1255
|
+
return True
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def stream_probe_passed(
|
|
1259
|
+
events: list[dict],
|
|
1260
|
+
env: object,
|
|
1261
|
+
stdout: str = "",
|
|
1262
|
+
stderr: str = "",
|
|
1263
|
+
expected_tools: set[str] | None = None,
|
|
1264
|
+
allow_expected_tool_use: bool = False,
|
|
1265
|
+
runtime_surface_only: bool = False,
|
|
1266
|
+
expected_native_skills: set[str] | None = None,
|
|
1267
|
+
required_native_skills: set[str] | None = None,
|
|
1268
|
+
host_init_baseline: dict[str, object] | None = None,
|
|
1269
|
+
) -> bool:
|
|
1270
|
+
"""Ground-truth pass: all init capability surfaces are present and empty,
|
|
1271
|
+
no tool was invoked, and the result envelope is clean. Reply text is ignored."""
|
|
1272
|
+
expected_tools = expected_tools or set()
|
|
1273
|
+
state = init_surface_state(
|
|
1274
|
+
events,
|
|
1275
|
+
expected_tools,
|
|
1276
|
+
expected_native_skills,
|
|
1277
|
+
required_native_skills,
|
|
1278
|
+
host_init_baseline,
|
|
1279
|
+
)
|
|
1280
|
+
if state is None:
|
|
1281
|
+
return False
|
|
1282
|
+
(
|
|
1283
|
+
missing_or_invalid,
|
|
1284
|
+
nonempty,
|
|
1285
|
+
declared_tools,
|
|
1286
|
+
unknown_fields,
|
|
1287
|
+
unsafe_values,
|
|
1288
|
+
unverifiable_authority,
|
|
1289
|
+
unclassifiable_vocabulary,
|
|
1290
|
+
) = state
|
|
1291
|
+
invoked = invoked_tool_names(events)
|
|
1292
|
+
# Every state set refuses here, including the unclassifiable-vocabulary one.
|
|
1293
|
+
# That is the whole safety argument for reclassifying it: the softer class
|
|
1294
|
+
# changes which client serves the review, never whether an unverified
|
|
1295
|
+
# isolation surface is ACCEPTED. There is no path from a bare unrecognised
|
|
1296
|
+
# name to a passing probe.
|
|
1297
|
+
if (
|
|
1298
|
+
missing_or_invalid
|
|
1299
|
+
or nonempty
|
|
1300
|
+
or unknown_fields
|
|
1301
|
+
or unsafe_values
|
|
1302
|
+
or unverifiable_authority
|
|
1303
|
+
or unclassifiable_vocabulary
|
|
1304
|
+
or declared_tools != expected_tools
|
|
1305
|
+
or invoked - expected_tools
|
|
1306
|
+
or (invoked and not allow_expected_tool_use)
|
|
1307
|
+
):
|
|
1308
|
+
return False
|
|
1309
|
+
if runtime_surface_only:
|
|
1310
|
+
if not envelope_structurally_clean(
|
|
1311
|
+
env, "", stderr, ignore_reply_text=True
|
|
1312
|
+
):
|
|
1313
|
+
return False
|
|
1314
|
+
elif not envelope_structurally_clean(env, stdout, stderr):
|
|
1315
|
+
return False
|
|
1316
|
+
return True
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
def clean_envelope_value(env: object, stdout: str = "", stderr: str = "") -> str | None:
|
|
1320
|
+
if not envelope_structurally_clean(env, stdout, stderr):
|
|
1321
|
+
return None
|
|
1322
|
+
if has_tool_available_marker("\n".join(all_string_values(env))):
|
|
1323
|
+
return None
|
|
1324
|
+
values = [normalize(value) for value in envelope_text_values(env)]
|
|
1325
|
+
if values and all(is_safe_ok_tail_value(value) for value in values):
|
|
1326
|
+
return "ok"
|
|
1327
|
+
return None
|
|
1328
|
+
|
|
1329
|
+
|
|
1330
|
+
def classify_failure(
|
|
1331
|
+
rc: int,
|
|
1332
|
+
stdout: str,
|
|
1333
|
+
stderr: str,
|
|
1334
|
+
expected_tools: set[str] | None = None,
|
|
1335
|
+
allow_expected_tool_use: bool = False,
|
|
1336
|
+
expected_native_skills: set[str] | None = None,
|
|
1337
|
+
required_native_skills: set[str] | None = None,
|
|
1338
|
+
host_init_baseline: dict[str, object] | None = None,
|
|
1339
|
+
):
|
|
1340
|
+
expected_tools = expected_tools or set()
|
|
1341
|
+
check_label = "Claude runtime isolation check" if expected_tools else "Claude no-tool probe"
|
|
1342
|
+
combined = f"{stdout}\n{stderr}"
|
|
1343
|
+
env = parse_json_object(stdout)
|
|
1344
|
+
# run_claude_capture.py owns these reserved statuses. Ordinary Claude exit
|
|
1345
|
+
# codes such as 124/143 remain local process failures, not proof that the
|
|
1346
|
+
# outer capture timer fired or terminated the child.
|
|
1347
|
+
timeout_or_terminated = rc in {198, 199}
|
|
1348
|
+
|
|
1349
|
+
surface = "\n".join(value for value in (classification_surface(env, stdout, stderr), stdout, stderr) if value)
|
|
1350
|
+
text = result_text(env) if env is not None else surface
|
|
1351
|
+
api_status = api_status_int(env)
|
|
1352
|
+
raw_api_status = env.get("api_error_status") if isinstance(env, dict) else None
|
|
1353
|
+
# A tool-boundary denial is terminal even when model/CLI prose also quotes
|
|
1354
|
+
# auth, quota, or timeout words. Never make this candidate-fallback eligible.
|
|
1355
|
+
if isinstance(env, dict) and env.get("permission_denials"):
|
|
1356
|
+
if timeout_or_terminated:
|
|
1357
|
+
return (
|
|
1358
|
+
f"{check_label} timed out or was terminated; "
|
|
1359
|
+
"tool permission-denial evidence was also observed",
|
|
1360
|
+
False,
|
|
1361
|
+
)
|
|
1362
|
+
return f"{check_label} reported tool permission denials", False
|
|
1363
|
+
if timeout_or_terminated and api_status == 401:
|
|
1364
|
+
return ((
|
|
1365
|
+
f"{check_label} timed out or was terminated; "
|
|
1366
|
+
"auth-path evidence was also observed, so rerun the wrapper from "
|
|
1367
|
+
"a host/escalated shell once local Claude auth is logged in"
|
|
1368
|
+
), False)
|
|
1369
|
+
if timeout_or_terminated and (
|
|
1370
|
+
api_status == 429
|
|
1371
|
+
or (isinstance(raw_api_status, str) and QUOTA.search(raw_api_status))
|
|
1372
|
+
or QUOTA.search(surface)
|
|
1373
|
+
):
|
|
1374
|
+
snippet = sanitize_snippet(surface or combined)
|
|
1375
|
+
return (
|
|
1376
|
+
f"{check_label} timed out or was terminated; "
|
|
1377
|
+
f"quota/rate-limit evidence was also observed: {snippet or 'rate limit or quota exceeded'}",
|
|
1378
|
+
False,
|
|
1379
|
+
)
|
|
1380
|
+
if timeout_or_terminated and isinstance(env, dict) and (
|
|
1381
|
+
is_truthy_error(env.get("is_error")) or env.get("subtype") not in {None, "success"}
|
|
1382
|
+
):
|
|
1383
|
+
snippet = sanitize_snippet(text)
|
|
1384
|
+
return (
|
|
1385
|
+
f"{check_label} timed out or was terminated; "
|
|
1386
|
+
f"error-envelope evidence was also observed: {snippet or env.get('subtype') or 'unknown error'}",
|
|
1387
|
+
False,
|
|
1388
|
+
)
|
|
1389
|
+
if api_status == 429 or (isinstance(raw_api_status, str) and QUOTA.search(raw_api_status)):
|
|
1390
|
+
snippet = sanitize_snippet(surface or combined)
|
|
1391
|
+
return (
|
|
1392
|
+
f"{check_label} hit quota/rate limit: {snippet or 'rate limit or quota exceeded'}",
|
|
1393
|
+
False,
|
|
1394
|
+
)
|
|
1395
|
+
if not timeout_or_terminated and (api_status == 401 or has_auth_marker(surface)):
|
|
1396
|
+
return ((
|
|
1397
|
+
f"safe {check_label} hit auth-path false negative; rerun the "
|
|
1398
|
+
"wrapper from a host/escalated shell once local Claude auth is logged in"
|
|
1399
|
+
), False)
|
|
1400
|
+
if not timeout_or_terminated and QUOTA.search(surface):
|
|
1401
|
+
snippet = sanitize_snippet(surface or combined)
|
|
1402
|
+
return (
|
|
1403
|
+
f"{check_label} hit quota/rate limit: {snippet or 'rate limit or quota exceeded'}",
|
|
1404
|
+
False,
|
|
1405
|
+
)
|
|
1406
|
+
if not timeout_or_terminated and api_status == 403:
|
|
1407
|
+
snippet = sanitize_snippet(text or combined)
|
|
1408
|
+
return (
|
|
1409
|
+
f"{check_label} returned permission/API access status 403: {snippet or 'no error text'}",
|
|
1410
|
+
False,
|
|
1411
|
+
)
|
|
1412
|
+
if raw_api_status is not None and (api_status is None or api_status >= 400) and not (
|
|
1413
|
+
timeout_or_terminated and api_status != 429
|
|
1414
|
+
):
|
|
1415
|
+
snippet = sanitize_snippet(text or combined)
|
|
1416
|
+
return (
|
|
1417
|
+
f"{check_label} returned API error status {raw_api_status}: {snippet or 'no error text'}",
|
|
1418
|
+
False,
|
|
1419
|
+
)
|
|
1420
|
+
if isinstance(env, dict) and (
|
|
1421
|
+
is_truthy_error(env.get("is_error")) or env.get("subtype") not in {None, "success"}
|
|
1422
|
+
):
|
|
1423
|
+
snippet = sanitize_snippet(text)
|
|
1424
|
+
return (
|
|
1425
|
+
f"{check_label} returned error envelope: {snippet or env.get('subtype') or 'unknown error'}",
|
|
1426
|
+
False,
|
|
1427
|
+
)
|
|
1428
|
+
failure_events = stream_events(stdout)
|
|
1429
|
+
if bash_tool_present(failure_events):
|
|
1430
|
+
return (
|
|
1431
|
+
f"{check_label} detected the Bash tool is available "
|
|
1432
|
+
"(declared or invoked in the session); the no-tool sandbox is not enforced",
|
|
1433
|
+
False,
|
|
1434
|
+
)
|
|
1435
|
+
failure_stream, failure_corrupt = stream_classification(stdout, failure_events)
|
|
1436
|
+
if failure_stream:
|
|
1437
|
+
if failure_corrupt:
|
|
1438
|
+
return (
|
|
1439
|
+
f"{check_label} produced a malformed stream-json record; "
|
|
1440
|
+
"tool-availability ground-truth is unverifiable",
|
|
1441
|
+
False,
|
|
1442
|
+
)
|
|
1443
|
+
state = init_surface_state(
|
|
1444
|
+
failure_events,
|
|
1445
|
+
expected_tools,
|
|
1446
|
+
expected_native_skills,
|
|
1447
|
+
required_native_skills,
|
|
1448
|
+
host_init_baseline,
|
|
1449
|
+
)
|
|
1450
|
+
if state is None:
|
|
1451
|
+
return (
|
|
1452
|
+
f"{check_label} stream-json capture is missing the init event; "
|
|
1453
|
+
"tool-availability ground-truth is unverifiable",
|
|
1454
|
+
False,
|
|
1455
|
+
)
|
|
1456
|
+
(
|
|
1457
|
+
missing_or_invalid,
|
|
1458
|
+
nonempty,
|
|
1459
|
+
declared_tools,
|
|
1460
|
+
unknown_fields,
|
|
1461
|
+
unsafe_values,
|
|
1462
|
+
unverifiable_authority,
|
|
1463
|
+
unclassifiable_vocabulary,
|
|
1464
|
+
) = state
|
|
1465
|
+
if missing_or_invalid:
|
|
1466
|
+
return (
|
|
1467
|
+
f"{check_label} init event is missing required isolation fields; "
|
|
1468
|
+
"runtime capability ground-truth is unverifiable",
|
|
1469
|
+
False,
|
|
1470
|
+
)
|
|
1471
|
+
if unsafe_values:
|
|
1472
|
+
# Terminal on purpose, and checked BEFORE the drift class: an unsafe
|
|
1473
|
+
# value on a known field is a breached boundary, not an unverifiable
|
|
1474
|
+
# schema. The "runtime capability" wording routes it to
|
|
1475
|
+
# tool_boundary_violation / stop_reviewer_lane in claude_review.sh.
|
|
1476
|
+
return (
|
|
1477
|
+
f"{check_label} init event declares an unsafe runtime capability "
|
|
1478
|
+
f"value on {', '.join(safe_identifier(f) for f in sorted(unsafe_values))}; "
|
|
1479
|
+
"the reviewer sandbox boundary is not enforced",
|
|
1480
|
+
False,
|
|
1481
|
+
)
|
|
1482
|
+
invoked = invoked_tool_names(failure_events)
|
|
1483
|
+
surface_breached = bool(
|
|
1484
|
+
nonempty
|
|
1485
|
+
or declared_tools != expected_tools
|
|
1486
|
+
or invoked - expected_tools
|
|
1487
|
+
or (invoked and not allow_expected_tool_use)
|
|
1488
|
+
)
|
|
1489
|
+
if unverifiable_authority and not surface_breached:
|
|
1490
|
+
# Fallback-eligible, NOT terminal: a vanished or renamed authority
|
|
1491
|
+
# knob means we can no longer prove the reviewer was unelevated —
|
|
1492
|
+
# not that it was. Terminalizing it would turn the next CLI rename
|
|
1493
|
+
# into the same total outage this landing removes.
|
|
1494
|
+
return (
|
|
1495
|
+
"Claude reviewer lane found an unrecognized surface-shaped init "
|
|
1496
|
+
"field (authority: "
|
|
1497
|
+
+ ", ".join(safe_identifier(f) for f in sorted(unverifiable_authority))
|
|
1498
|
+
+ "); the reviewer's authority is no longer verifiable from the "
|
|
1499
|
+
"stream-json schema",
|
|
1500
|
+
False,
|
|
1501
|
+
)
|
|
1502
|
+
if unknown_fields and not surface_breached:
|
|
1503
|
+
# Drift is reported ONLY when nothing else is wrong — same guard as
|
|
1504
|
+
# `runtime_drift_only` on the main-invocation path. A run that both
|
|
1505
|
+
# drifts and breaches must report the breach: this branch's phrase
|
|
1506
|
+
# routes to the fallback-eligible class, so reaching it first would
|
|
1507
|
+
# launder a real breach into "try another reviewer client".
|
|
1508
|
+
#
|
|
1509
|
+
# Wording is load-bearing: claude_review.sh routes this exact phrase
|
|
1510
|
+
# to a fallback-eligible schema-drift class, not to the terminal
|
|
1511
|
+
# tool-boundary class — a surface-shaped schema addition means THIS
|
|
1512
|
+
# lane cannot verify isolation, not that review must stop. Naming
|
|
1513
|
+
# the fields keeps the one-line follow-up obvious.
|
|
1514
|
+
#
|
|
1515
|
+
# `check_label` is deliberately NOT interpolated here: in consult
|
|
1516
|
+
# mode it reads "Claude runtime isolation check", and the routing
|
|
1517
|
+
# table's tool-boundary arm matches on "runtime isolation". Emitting
|
|
1518
|
+
# it would make correct routing depend on `case`-arm order alone.
|
|
1519
|
+
return (
|
|
1520
|
+
"Claude reviewer lane found an unrecognized surface-shaped init "
|
|
1521
|
+
f"field ({', '.join(safe_identifier(f) for f in sorted(unknown_fields))}); "
|
|
1522
|
+
"the stream-json schema must be reviewed before this lane can "
|
|
1523
|
+
"verify isolation",
|
|
1524
|
+
False,
|
|
1525
|
+
)
|
|
1526
|
+
if unclassifiable_vocabulary and not surface_breached:
|
|
1527
|
+
# Same guard, same reason as the two branches above: reached only
|
|
1528
|
+
# when nothing is actually breached, so a hostile CLI cannot use a
|
|
1529
|
+
# bare name to launder a real breach into "try another client".
|
|
1530
|
+
#
|
|
1531
|
+
# A distinct phrase, not a reuse of the surface-shaped one: what is
|
|
1532
|
+
# unrecognised here is an IDENTIFIER, not a field, and describing it
|
|
1533
|
+
# as the wrong thing is a diagnostic this repo has already paid for.
|
|
1534
|
+
# `claude_review.sh` carries a matching arm ahead of its terminal
|
|
1535
|
+
# arm; relying on its late `*"init"*` catch-all would make routing
|
|
1536
|
+
# depend on `case`-arm order alone.
|
|
1537
|
+
return (
|
|
1538
|
+
"Claude reviewer lane found an unclassifiable host-vocabulary "
|
|
1539
|
+
"entry ("
|
|
1540
|
+
+ ", ".join(
|
|
1541
|
+
safe_identifier(v) for v in sorted(unclassifiable_vocabulary)
|
|
1542
|
+
)
|
|
1543
|
+
+ "); the built-in allowlist cannot prove whether the host or a "
|
|
1544
|
+
"user owns that name, so this lane cannot verify isolation",
|
|
1545
|
+
False,
|
|
1546
|
+
)
|
|
1547
|
+
if (
|
|
1548
|
+
surface_breached
|
|
1549
|
+
or unknown_fields
|
|
1550
|
+
or unverifiable_authority
|
|
1551
|
+
or unclassifiable_vocabulary
|
|
1552
|
+
):
|
|
1553
|
+
if expected_tools:
|
|
1554
|
+
surface_reason = (
|
|
1555
|
+
f"{check_label} runtime capability surface does not match the expected boundary; "
|
|
1556
|
+
"a tool, MCP server, skill, command, or plugin is unexpected or unsafe"
|
|
1557
|
+
)
|
|
1558
|
+
else:
|
|
1559
|
+
surface_reason = (
|
|
1560
|
+
"Claude no-tool probe runtime capability surface is not empty; "
|
|
1561
|
+
"a tool, MCP server, skill, command, or plugin remains declared or invoked"
|
|
1562
|
+
)
|
|
1563
|
+
return (
|
|
1564
|
+
surface_reason,
|
|
1565
|
+
False,
|
|
1566
|
+
)
|
|
1567
|
+
snippet = sanitize_snippet(combined)
|
|
1568
|
+
return f"{check_label} failed: {snippet or 'unexpected probe output'}", True
|
|
1569
|
+
|
|
1570
|
+
|
|
1571
|
+
def main() -> int:
|
|
1572
|
+
if len(sys.argv) < 4:
|
|
1573
|
+
print(
|
|
1574
|
+
"usage: parse_probe_result.py RC STDOUT_FILE STDERR_FILE "
|
|
1575
|
+
"[--require-empty-init] [--expected-tools CSV] "
|
|
1576
|
+
"[--expected-native-skills CSV] "
|
|
1577
|
+
"[--required-native-skills CSV] "
|
|
1578
|
+
"[--host-init-baseline FILE] [--validate-host-init-baseline] "
|
|
1579
|
+
"[--allow-expected-tool-use] [--runtime-surface-only]",
|
|
1580
|
+
file=sys.stderr,
|
|
1581
|
+
)
|
|
1582
|
+
return 64
|
|
1583
|
+
require_empty_init = False
|
|
1584
|
+
expected_tools: set[str] = set()
|
|
1585
|
+
expected_native_skills: set[str] = set()
|
|
1586
|
+
required_native_skills: set[str] = set()
|
|
1587
|
+
allow_expected_tool_use = False
|
|
1588
|
+
runtime_surface_only = False
|
|
1589
|
+
host_init_baseline_path = ""
|
|
1590
|
+
validate_host_init_baseline = False
|
|
1591
|
+
option_index = 4
|
|
1592
|
+
while option_index < len(sys.argv):
|
|
1593
|
+
option = sys.argv[option_index]
|
|
1594
|
+
if option == "--require-empty-init":
|
|
1595
|
+
require_empty_init = True
|
|
1596
|
+
option_index += 1
|
|
1597
|
+
elif option == "--expected-tools" and option_index + 1 < len(sys.argv):
|
|
1598
|
+
expected_tools = {
|
|
1599
|
+
name.strip().lower()
|
|
1600
|
+
for name in sys.argv[option_index + 1].split(",")
|
|
1601
|
+
if name.strip()
|
|
1602
|
+
}
|
|
1603
|
+
option_index += 2
|
|
1604
|
+
elif option == "--expected-native-skills" and option_index + 1 < len(sys.argv):
|
|
1605
|
+
expected_native_skills = {
|
|
1606
|
+
name.strip().lower()
|
|
1607
|
+
for name in sys.argv[option_index + 1].split(",")
|
|
1608
|
+
if name.strip()
|
|
1609
|
+
}
|
|
1610
|
+
option_index += 2
|
|
1611
|
+
elif option == "--required-native-skills" and option_index + 1 < len(sys.argv):
|
|
1612
|
+
required_native_skills = {
|
|
1613
|
+
name.strip().lower()
|
|
1614
|
+
for name in sys.argv[option_index + 1].split(",")
|
|
1615
|
+
if name.strip()
|
|
1616
|
+
}
|
|
1617
|
+
option_index += 2
|
|
1618
|
+
elif option == "--allow-expected-tool-use":
|
|
1619
|
+
allow_expected_tool_use = True
|
|
1620
|
+
option_index += 1
|
|
1621
|
+
elif option == "--runtime-surface-only":
|
|
1622
|
+
runtime_surface_only = True
|
|
1623
|
+
option_index += 1
|
|
1624
|
+
elif option == "--host-init-baseline" and option_index + 1 < len(sys.argv):
|
|
1625
|
+
host_init_baseline_path = sys.argv[option_index + 1]
|
|
1626
|
+
option_index += 2
|
|
1627
|
+
elif option == "--validate-host-init-baseline":
|
|
1628
|
+
validate_host_init_baseline = True
|
|
1629
|
+
option_index += 1
|
|
1630
|
+
else:
|
|
1631
|
+
print("unknown or incomplete probe parser option", file=sys.stderr)
|
|
1632
|
+
return 64
|
|
1633
|
+
|
|
1634
|
+
# Tool-boundary verdicts are meaningful only with stream-json init
|
|
1635
|
+
# evidence. Make the stronger requirement intrinsic so a direct caller
|
|
1636
|
+
# cannot accidentally re-enable the legacy text fallback.
|
|
1637
|
+
if (
|
|
1638
|
+
runtime_surface_only
|
|
1639
|
+
or expected_tools
|
|
1640
|
+
or allow_expected_tool_use
|
|
1641
|
+
or expected_native_skills
|
|
1642
|
+
or required_native_skills
|
|
1643
|
+
):
|
|
1644
|
+
require_empty_init = True
|
|
1645
|
+
|
|
1646
|
+
try:
|
|
1647
|
+
rc = int(sys.argv[1])
|
|
1648
|
+
except ValueError:
|
|
1649
|
+
print("probe rc must be an integer", file=sys.stderr)
|
|
1650
|
+
return 64
|
|
1651
|
+
|
|
1652
|
+
stdout = Path(sys.argv[2]).read_text(encoding="utf-8", errors="replace")
|
|
1653
|
+
stderr = Path(sys.argv[3]).read_text(encoding="utf-8", errors="replace")
|
|
1654
|
+
host_init_baseline = None
|
|
1655
|
+
if validate_host_init_baseline:
|
|
1656
|
+
host_init_baseline, baseline_error = load_host_init_baseline(sys.argv[2])
|
|
1657
|
+
if baseline_error is not None:
|
|
1658
|
+
print(json.dumps({"reason": baseline_error, "generic_failure": False}))
|
|
1659
|
+
return 1
|
|
1660
|
+
return 0
|
|
1661
|
+
if host_init_baseline_path:
|
|
1662
|
+
host_init_baseline, baseline_error = load_host_init_baseline(
|
|
1663
|
+
host_init_baseline_path
|
|
1664
|
+
)
|
|
1665
|
+
if baseline_error is not None:
|
|
1666
|
+
print(json.dumps({"reason": baseline_error, "generic_failure": False}))
|
|
1667
|
+
return 1
|
|
1668
|
+
normalized_stdout = normalize(stdout)
|
|
1669
|
+
stderr_surface = classification_surface(None, "", stderr)
|
|
1670
|
+
env = parse_json_object(normalized_stdout)
|
|
1671
|
+
events = stream_events(normalized_stdout)
|
|
1672
|
+
invoked_tools = invoked_tool_names(events)
|
|
1673
|
+
stream_like, stream_corrupt = stream_classification(normalized_stdout, events)
|
|
1674
|
+
stdout_clean = not QUOTA.search(normalized_stdout) and not has_auth_marker(normalized_stdout)
|
|
1675
|
+
stderr_clean = not QUOTA.search(stderr_surface) and not has_auth_marker(stderr_surface)
|
|
1676
|
+
runtime_stdout_surface = ""
|
|
1677
|
+
if runtime_surface_only and rc == 0:
|
|
1678
|
+
# Only a structurally intact JSONL stream can distinguish host/CLI
|
|
1679
|
+
# banners from model reply content. Pretty-printed envelopes and corrupt
|
|
1680
|
+
# streams are unverifiable; never classify their stdout prose as auth or
|
|
1681
|
+
# quota evidence.
|
|
1682
|
+
if stream_like and not stream_corrupt:
|
|
1683
|
+
runtime_stdout_surface = stream_nonjson_surface(normalized_stdout)
|
|
1684
|
+
stdout_clean = not QUOTA.search(runtime_stdout_surface) and not has_auth_marker(
|
|
1685
|
+
runtime_stdout_surface
|
|
1686
|
+
)
|
|
1687
|
+
if (
|
|
1688
|
+
not require_empty_init
|
|
1689
|
+
and rc == 0
|
|
1690
|
+
and stdout_clean
|
|
1691
|
+
and stderr_clean
|
|
1692
|
+
and is_ok_token(normalized_stdout)
|
|
1693
|
+
):
|
|
1694
|
+
return 0
|
|
1695
|
+
|
|
1696
|
+
if rc == 0 and stdout_clean and stderr_clean:
|
|
1697
|
+
unexpected_tool_use = invoked_tools - expected_tools or (
|
|
1698
|
+
invoked_tools and not allow_expected_tool_use
|
|
1699
|
+
)
|
|
1700
|
+
if unexpected_tool_use:
|
|
1701
|
+
# Any unexpected tool_use is a hard fail regardless of init/text;
|
|
1702
|
+
# fall through to classify_failure (reports sandbox not enforced).
|
|
1703
|
+
pass
|
|
1704
|
+
elif stream_like:
|
|
1705
|
+
# stream-json mode REQUIRES trustworthy ground-truth: a parseable init
|
|
1706
|
+
# event, no corruption, Bash neither declared nor invoked. Missing init
|
|
1707
|
+
# or a dropped record could hide a real Bash session, so anything less
|
|
1708
|
+
# fails closed instead of passing on the reply token.
|
|
1709
|
+
if not stream_corrupt and stream_probe_passed(
|
|
1710
|
+
events,
|
|
1711
|
+
env,
|
|
1712
|
+
stdout,
|
|
1713
|
+
stderr,
|
|
1714
|
+
expected_tools,
|
|
1715
|
+
allow_expected_tool_use,
|
|
1716
|
+
runtime_surface_only,
|
|
1717
|
+
expected_native_skills,
|
|
1718
|
+
required_native_skills,
|
|
1719
|
+
host_init_baseline,
|
|
1720
|
+
):
|
|
1721
|
+
return 0
|
|
1722
|
+
elif not require_empty_init:
|
|
1723
|
+
# non-stream output (no standalone JSONL records, e.g. plain single
|
|
1724
|
+
# envelope or raw text): legacy text fallback.
|
|
1725
|
+
value = clean_envelope_value(env, stdout, stderr)
|
|
1726
|
+
if value is not None and is_ok_token(value):
|
|
1727
|
+
return 0
|
|
1728
|
+
|
|
1729
|
+
if require_empty_init and rc == 0 and stdout_clean and stderr_clean and not stream_like:
|
|
1730
|
+
print(
|
|
1731
|
+
json.dumps(
|
|
1732
|
+
{
|
|
1733
|
+
"reason": (
|
|
1734
|
+
"Claude no-tool probe is missing the required stream-json init evidence; "
|
|
1735
|
+
"runtime isolation cannot be verified"
|
|
1736
|
+
),
|
|
1737
|
+
"generic_failure": False,
|
|
1738
|
+
}
|
|
1739
|
+
)
|
|
1740
|
+
)
|
|
1741
|
+
return 1
|
|
1742
|
+
|
|
1743
|
+
if runtime_surface_only and rc == 0:
|
|
1744
|
+
runtime_diagnostic_surface = "\n".join(
|
|
1745
|
+
value for value in (runtime_stdout_surface, stderr_surface) if value
|
|
1746
|
+
)
|
|
1747
|
+
runtime_api_status = api_status_int(env)
|
|
1748
|
+
runtime_raw_api_status = env.get("api_error_status") if isinstance(env, dict) else None
|
|
1749
|
+
if isinstance(env, dict) and env.get("permission_denials"):
|
|
1750
|
+
runtime_reason = "Claude main invocation reported tool permission denials"
|
|
1751
|
+
elif runtime_api_status == 401 or has_auth_marker(runtime_diagnostic_surface):
|
|
1752
|
+
runtime_reason = (
|
|
1753
|
+
"safe Claude main invocation hit auth-path false negative; rerun the "
|
|
1754
|
+
"wrapper from a host/escalated shell once local Claude auth is logged in"
|
|
1755
|
+
)
|
|
1756
|
+
elif (
|
|
1757
|
+
runtime_api_status == 429
|
|
1758
|
+
or (
|
|
1759
|
+
isinstance(runtime_raw_api_status, str)
|
|
1760
|
+
and QUOTA.search(runtime_raw_api_status)
|
|
1761
|
+
)
|
|
1762
|
+
or QUOTA.search(runtime_diagnostic_surface)
|
|
1763
|
+
):
|
|
1764
|
+
snippet = sanitize_snippet(runtime_diagnostic_surface)
|
|
1765
|
+
runtime_reason = (
|
|
1766
|
+
"Claude main invocation hit quota/rate limit: "
|
|
1767
|
+
f"{snippet or 'rate limit or quota exceeded'}"
|
|
1768
|
+
)
|
|
1769
|
+
else:
|
|
1770
|
+
runtime_state = init_surface_state(
|
|
1771
|
+
events,
|
|
1772
|
+
expected_tools,
|
|
1773
|
+
expected_native_skills,
|
|
1774
|
+
required_native_skills,
|
|
1775
|
+
host_init_baseline,
|
|
1776
|
+
)
|
|
1777
|
+
runtime_detail = ""
|
|
1778
|
+
runtime_drift_only = False
|
|
1779
|
+
if runtime_state is not None:
|
|
1780
|
+
(
|
|
1781
|
+
missing,
|
|
1782
|
+
nonempty,
|
|
1783
|
+
declared,
|
|
1784
|
+
unknown,
|
|
1785
|
+
unsafe,
|
|
1786
|
+
unverifiable,
|
|
1787
|
+
vocabulary,
|
|
1788
|
+
) = runtime_state
|
|
1789
|
+
# Schema drift routes the same way on the main-invocation path as
|
|
1790
|
+
# on the probe path. Without this, an unrecognized init container
|
|
1791
|
+
# would terminate the lane here while merely falling back there —
|
|
1792
|
+
# the same condition, two verdicts, decided by which parse ran.
|
|
1793
|
+
#
|
|
1794
|
+
# `unexpected_identifiers` is deliberately absent from the guard,
|
|
1795
|
+
# but NOT because it is redundant with `nonempty` — that was true
|
|
1796
|
+
# only while every unrecognised entry was a breach. An entry it
|
|
1797
|
+
# reports now lands in either `nonempty` (proven customization)
|
|
1798
|
+
# or `vocabulary` (unclassifiable host name), both of which ARE
|
|
1799
|
+
# in the guard; it stays out because it would double-count them
|
|
1800
|
+
# while distinguishing neither. Fixtures pin the combined
|
|
1801
|
+
# drift+breach and vocabulary+breach cases so this stays true
|
|
1802
|
+
# rather than being taken on faith.
|
|
1803
|
+
runtime_drift_only = bool(unknown or unverifiable or vocabulary) and not (
|
|
1804
|
+
missing
|
|
1805
|
+
or nonempty
|
|
1806
|
+
or unsafe
|
|
1807
|
+
or declared != expected_tools
|
|
1808
|
+
or invoked_tools - expected_tools
|
|
1809
|
+
or (invoked_tools and not allow_expected_tool_use)
|
|
1810
|
+
)
|
|
1811
|
+
unexpected_identifiers = unexpected_customization_identifiers(
|
|
1812
|
+
events, expected_native_skills, host_init_baseline
|
|
1813
|
+
)
|
|
1814
|
+
# EVERY interpolated identifier is CLI-supplied, not just the
|
|
1815
|
+
# field names: tool names, skill/plugin/command identifiers and
|
|
1816
|
+
# invoked-tool names come from the inspected stream too, and may
|
|
1817
|
+
# contain spaces just as freely. Any one of them left raw lets
|
|
1818
|
+
# the inspected CLI pick which routing arm matches — a breach
|
|
1819
|
+
# laundered into "try another client".
|
|
1820
|
+
def joined(values):
|
|
1821
|
+
return ",".join(safe_identifier(v) for v in sorted(values))
|
|
1822
|
+
|
|
1823
|
+
runtime_detail = (
|
|
1824
|
+
"; missing_or_invalid="
|
|
1825
|
+
+ joined(missing)
|
|
1826
|
+
+ "; unexpected_customizations="
|
|
1827
|
+
+ joined(nonempty)
|
|
1828
|
+
+ "; unexpected_customization_identifiers="
|
|
1829
|
+
+ joined(unexpected_identifiers)
|
|
1830
|
+
+ "; declared_tools="
|
|
1831
|
+
+ joined(declared)
|
|
1832
|
+
+ "; invoked_tools="
|
|
1833
|
+
+ joined(invoked_tools)
|
|
1834
|
+
+ "; unknown_fields="
|
|
1835
|
+
+ joined(unknown)
|
|
1836
|
+
+ "; unsafe_values="
|
|
1837
|
+
+ joined(unsafe)
|
|
1838
|
+
+ "; unverifiable_authority="
|
|
1839
|
+
+ joined(unverifiable)
|
|
1840
|
+
+ "; unclassifiable_host_vocabulary="
|
|
1841
|
+
+ joined(vocabulary)
|
|
1842
|
+
)
|
|
1843
|
+
if runtime_drift_only and not (unknown or unverifiable):
|
|
1844
|
+
# The new class gets its own phrase only when it is the SOLE
|
|
1845
|
+
# unverifiable finding. Combined with schema drift or an
|
|
1846
|
+
# unverifiable authority knob, the existing phrase still applies
|
|
1847
|
+
# and still routes to the same fallback-eligible arm, so the
|
|
1848
|
+
# wording of every pre-existing case is left exactly as it was.
|
|
1849
|
+
runtime_reason = (
|
|
1850
|
+
"Claude main invocation found an unclassifiable "
|
|
1851
|
+
"host-vocabulary entry; the built-in allowlist cannot prove "
|
|
1852
|
+
"whether the host or a user owns that name, so this reviewer "
|
|
1853
|
+
"lane cannot verify isolation" + runtime_detail
|
|
1854
|
+
)
|
|
1855
|
+
elif runtime_drift_only:
|
|
1856
|
+
runtime_reason = (
|
|
1857
|
+
"Claude main invocation found an unrecognized surface-shaped "
|
|
1858
|
+
"init field; the stream-json schema must be reviewed before "
|
|
1859
|
+
"this reviewer lane can verify isolation" + runtime_detail
|
|
1860
|
+
)
|
|
1861
|
+
else:
|
|
1862
|
+
runtime_reason = (
|
|
1863
|
+
"Claude main invocation runtime isolation surface is invalid or unverifiable"
|
|
1864
|
+
+ runtime_detail
|
|
1865
|
+
)
|
|
1866
|
+
print(
|
|
1867
|
+
json.dumps(
|
|
1868
|
+
{
|
|
1869
|
+
"reason": runtime_reason,
|
|
1870
|
+
"generic_failure": False,
|
|
1871
|
+
}
|
|
1872
|
+
)
|
|
1873
|
+
)
|
|
1874
|
+
return 1
|
|
1875
|
+
|
|
1876
|
+
reason, generic_failure = classify_failure(
|
|
1877
|
+
rc,
|
|
1878
|
+
stdout,
|
|
1879
|
+
stderr,
|
|
1880
|
+
expected_tools,
|
|
1881
|
+
allow_expected_tool_use,
|
|
1882
|
+
expected_native_skills,
|
|
1883
|
+
required_native_skills,
|
|
1884
|
+
host_init_baseline,
|
|
1885
|
+
)
|
|
1886
|
+
print(
|
|
1887
|
+
json.dumps(
|
|
1888
|
+
{
|
|
1889
|
+
"reason": reason,
|
|
1890
|
+
"generic_failure": generic_failure,
|
|
1891
|
+
},
|
|
1892
|
+
ensure_ascii=False,
|
|
1893
|
+
)
|
|
1894
|
+
)
|
|
1895
|
+
return 1
|
|
1896
|
+
|
|
1897
|
+
|
|
1898
|
+
if __name__ == "__main__":
|
|
1899
|
+
raise SystemExit(main())
|