@ccoalm/ccl-skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (561) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +49 -0
  3. package/dist/assets/marketplace/.agents/plugins/marketplace.json +12 -0
  4. package/dist/assets/marketplace/.claude-plugin/marketplace.json +13 -0
  5. package/dist/assets/marketplace/marketplace-manifest.json +12 -0
  6. package/dist/assets/marketplace/plugins/ccl-skills/.claude-plugin/marketplace.json +16 -0
  7. package/dist/assets/marketplace/plugins/ccl-skills/.claude-plugin/plugin.json +5 -0
  8. package/dist/assets/marketplace/plugins/ccl-skills/.codex-plugin/plugin.json +5 -0
  9. package/dist/assets/marketplace/plugins/ccl-skills/.worktree-only +3 -0
  10. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/session-start.md +45 -0
  11. package/dist/assets/marketplace/plugins/ccl-skills/agent-context/subagent-start.md +12 -0
  12. package/dist/assets/marketplace/plugins/ccl-skills/hooks/AGENTS.md +19 -0
  13. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-delegation-owner.sh +125 -0
  14. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-edit-isolation.sh +102 -0
  15. package/dist/assets/marketplace/plugins/ccl-skills/hooks/guard-merge-authorization.sh +1156 -0
  16. package/dist/assets/marketplace/plugins/ccl-skills/hooks/hooks.json +131 -0
  17. package/dist/assets/marketplace/plugins/ccl-skills/hooks/merge-authorization-prompt.sh +142 -0
  18. package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-guard.sh +12 -0
  19. package/dist/assets/marketplace/plugins/ccl-skills/hooks/owner-dispatch-stop.sh +13 -0
  20. package/dist/assets/marketplace/plugins/ccl-skills/hooks/remind-post-merge-cleanup.sh +144 -0
  21. package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-context.sh +87 -0
  22. package/dist/assets/marketplace/plugins/ccl-skills/hooks/session-start.sh +86 -0
  23. package/dist/assets/marketplace/plugins/ccl-skills/hooks/skill-extraction-gate-stop.sh +69 -0
  24. package/dist/assets/marketplace/plugins/ccl-skills/hooks/subagent-start.sh +26 -0
  25. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_delegation_owner.sh +329 -0
  26. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_edit_isolation.sh +322 -0
  27. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_guard_merge_authorization.sh +902 -0
  28. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_merge_authorization_prompt.sh +178 -0
  29. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_remind_post_merge_cleanup.sh +121 -0
  30. package/dist/assets/marketplace/plugins/ccl-skills/hooks/test_session_start.sh +170 -0
  31. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/AGENTS.md +17 -0
  32. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/ccl-skills.ts +564 -0
  33. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-install-skills.md +14 -0
  34. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-update-skills.md +44 -0
  35. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-verify-skills.md +109 -0
  36. package/dist/assets/marketplace/plugins/ccl-skills/packages/opencode-plugin/commands/ccl-worktree-check.md +36 -0
  37. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/AGENTS.md +28 -0
  38. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/README.md +276 -0
  39. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.example.json +10 -0
  40. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/owner-dispatch.sh +1307 -0
  41. package/dist/assets/marketplace/plugins/ccl-skills/scripts/owner-dispatch/test.sh +941 -0
  42. package/dist/assets/marketplace/plugins/ccl-skills/skills/agents-file-coverage-gate/SKILL.md +45 -0
  43. package/dist/assets/marketplace/plugins/ccl-skills/skills/agents-file-coverage-gate/agents/openai.yaml +4 -0
  44. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/SKILL.md +188 -0
  45. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/agents/openai.yaml +4 -0
  46. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/android-dev.md +92 -0
  47. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/flutter-dev.md +80 -0
  48. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/ios-dev.md +72 -0
  49. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/kotlin-multiplatform.md +93 -0
  50. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-platform-boundaries.md +77 -0
  51. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/mobile-quality-release.md +77 -0
  52. package/dist/assets/marketplace/plugins/ccl-skills/skills/app-cross-platform-dev/references/source-evidence-map.md +64 -0
  53. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/SKILL.md +353 -0
  54. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/agents/openai.yaml +4 -0
  55. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/client-routing.md +419 -0
  56. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/manual-invocation-and-prompts.md +126 -0
  57. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/staged-review-contract.md +197 -0
  58. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/references/timeout-auth-and-capabilities.md +179 -0
  59. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/AGENTS.md +98 -0
  60. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_envelope.py +93 -0
  61. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/classify_timeout_exit.sh +15 -0
  62. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/claude_review.sh +1438 -0
  63. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/codex_review.sh +324 -0
  64. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/concern_excerpt.py +295 -0
  65. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/egress_schema.py +214 -0
  66. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/init_policy_matrix.py +642 -0
  67. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_packet_mcp.py +181 -0
  68. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/kimi_review.sh +1165 -0
  69. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/opencode_review.sh +1190 -0
  70. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_cli_review.py +946 -0
  71. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_opencode_review.py +474 -0
  72. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_probe_result.py +1899 -0
  73. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/parse_review_json.py +200 -0
  74. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.py +2845 -0
  75. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/review_gate.sh +6 -0
  76. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/run_claude_capture.py +71 -0
  77. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/runtime-surface-verification-design.md +53 -0
  78. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_classify_envelope.sh +68 -0
  79. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_claude_review_probe.sh +2311 -0
  80. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_cli_review_wrappers.sh +1832 -0
  81. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_code_review_identity.sh +73 -0
  82. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_concern_excerpt.sh +245 -0
  83. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_egress_schema.sh +177 -0
  84. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_init_policy_matrix.sh +272 -0
  85. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_kimi_packet_mcp.py +195 -0
  86. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_opencode_review_concurrency.sh +120 -0
  87. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_opencode_review_retry.sh +1005 -0
  88. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_opencode_review.sh +258 -0
  89. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_probe_result.sh +574 -0
  90. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_parse_review_json.sh +349 -0
  91. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_compat.py +434 -0
  92. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_client_order.sh +264 -0
  93. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/test_review_gate.sh +2412 -0
  94. package/dist/assets/marketplace/plugins/ccl-skills/skills/code-review/scripts/verify_native_skill_binding.py +123 -0
  95. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/SKILL.md +153 -0
  96. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/agents/openai.yaml +4 -0
  97. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/diagnosis-playbook.md +54 -0
  98. package/dist/assets/marketplace/plugins/ccl-skills/skills/defect-diagnosis/references/prevention-routing.md +36 -0
  99. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/SKILL.md +69 -0
  100. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/agents/openai.yaml +4 -0
  101. package/dist/assets/marketplace/plugins/ccl-skills/skills/feature-risk-router/references/security-review-gate.md +41 -0
  102. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/SKILL.md +165 -0
  103. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/agents/openai.yaml +4 -0
  104. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/api-security-boundaries.md +47 -0
  105. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/architecture-playbook.md +160 -0
  106. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/artifact-generation-architecture.md +37 -0
  107. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/audit-history-architecture.md +29 -0
  108. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/bulk-workflow-architecture.md +33 -0
  109. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/config-rule-routing-architecture.md +34 -0
  110. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/cross-cutting-concerns.md +72 -0
  111. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/data-modeling-and-migrations.md +79 -0
  112. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/data-platform-architecture.md +210 -0
  113. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/dependency-platform.md +105 -0
  114. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/developer-tooling-architecture.md +38 -0
  115. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/error-contract-architecture.md +36 -0
  116. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/event-driven-architecture.md +260 -0
  117. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/http-gateway-architecture.md +74 -0
  118. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/mq-consumer-architecture.md +38 -0
  119. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/multi-tenant-isolation.md +275 -0
  120. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/notification-architecture.md +25 -0
  121. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/ops-checklist.md +57 -0
  122. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/performance-capacity-architecture.md +38 -0
  123. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/protobuf-contract-architecture.md +119 -0
  124. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/redis-cache-coordination.md +93 -0
  125. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/release-runtime-readiness.md +65 -0
  126. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/replay-comparison-architecture.md +26 -0
  127. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/runtime-observability.md +94 -0
  128. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/service-scaffold.md +76 -0
  129. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/source-evidence-map.md +55 -0
  130. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-architecture/references/workflow-state-architecture.md +38 -0
  131. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/SKILL.md +159 -0
  132. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/agents/openai.yaml +4 -0
  133. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/artifact-generation-patterns.md +37 -0
  134. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/audit-history-patterns.md +28 -0
  135. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/bulk-import-export-patterns.md +56 -0
  136. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/config-rule-routing-patterns.md +38 -0
  137. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/data-access-patterns.md +55 -0
  138. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/db-schema-and-dal-patterns.md +109 -0
  139. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/dependency-client-patterns.md +130 -0
  140. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/developer-tooling-patterns.md +70 -0
  141. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/domain-feature-patterns.md +78 -0
  142. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/engineering-patterns.md +119 -0
  143. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/error-contract-patterns.md +55 -0
  144. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/feature-playbook.md +61 -0
  145. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/http-gateway-client-patterns.md +76 -0
  146. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/mq-consumer-patterns.md +55 -0
  147. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/notification-patterns.md +42 -0
  148. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/observability-implementation-patterns.md +101 -0
  149. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/performance-capacity-patterns.md +44 -0
  150. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/protobuf-contract-patterns.md +72 -0
  151. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/public-api-integration-patterns.md +56 -0
  152. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/quality-and-testing-patterns.md +91 -0
  153. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/redis-cache-lock-patterns.md +123 -0
  154. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/release-ops-patterns.md +112 -0
  155. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/reliability-patterns.md +83 -0
  156. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/replay-comparison-patterns.md +32 -0
  157. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/scaffold-and-codegen.md +86 -0
  158. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/source-evidence-map.md +54 -0
  159. package/dist/assets/marketplace/plugins/ccl-skills/skills/go-microservice-dev/references/state-machine-task-patterns.md +45 -0
  160. package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/SKILL.md +80 -0
  161. package/dist/assets/marketplace/plugins/ccl-skills/skills/grill-me/agents/openai.yaml +4 -0
  162. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/SKILL.md +117 -0
  163. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/agents/openai.yaml +4 -0
  164. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-approval-auto-reviewer.md +106 -0
  165. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-command-sandbox.md +441 -0
  166. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-context-freshness.md +47 -0
  167. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-credentials-auth.md +13 -0
  168. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-extensions-skills.md +13 -0
  169. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-file-edit-protocol.md +129 -0
  170. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-ide-integration.md +5 -0
  171. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-input-ingestion.md +13 -0
  172. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-instruction-composition.md +13 -0
  173. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-lifecycle-hooks.md +92 -0
  174. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-messaging.md +5 -0
  175. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-runtime-bootstrap.md +5 -0
  176. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-session-persistence.md +448 -0
  177. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-task-orchestration.md +13 -0
  178. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-tool-dispatch.md +123 -0
  179. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/agent-turn-lifecycle.md +131 -0
  180. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/inference-capacity-operations.md +162 -0
  181. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/llm-client-gateway.md +156 -0
  182. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/model-prompt-evaluation.md +146 -0
  183. package/dist/assets/marketplace/plugins/ccl-skills/skills/llm-inference-integration/references/retrieval-agent-safety.md +273 -0
  184. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/SKILL.md +202 -0
  185. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/agents/openai.yaml +4 -0
  186. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/contracts-and-state.md +62 -0
  187. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/cross-stack-alignment.md +94 -0
  188. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/framework-choice.md +76 -0
  189. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/online-practice-uptake.md +56 -0
  190. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/platform-capabilities.md +91 -0
  191. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/product-page-checklist.md +40 -0
  192. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/qa-release.md +72 -0
  193. package/dist/assets/marketplace/plugins/ccl-skills/skills/miniapp-product-dev/references/source-evidence-map.md +82 -0
  194. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/SKILL.md +103 -0
  195. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/agents/openai.yaml +5 -0
  196. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-agent-delegation/references/multi-agent-delegation-playbook.md +100 -0
  197. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/SKILL.md +70 -0
  198. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/agents/openai.yaml +4 -0
  199. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-data-acquisition.md +549 -0
  200. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/public-disclosure-channels.md +97 -0
  201. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/references/research-prompts.md +66 -0
  202. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/scripts/AGENTS.md +32 -0
  203. package/dist/assets/marketplace/plugins/ccl-skills/skills/multi-perspective-research/scripts/test-public-data-acquisition-recipes.sh +379 -0
  204. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/SKILL.md +244 -0
  205. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/agents/openai.yaml +4 -0
  206. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/alerting-and-on-call.md +76 -0
  207. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/framework-middleware-checklist.md +142 -0
  208. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/infra-component-deployment.md +268 -0
  209. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/log-correlation-recipe.md +124 -0
  210. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/log-schema-canonical.md +208 -0
  211. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/metrics-conventions.md +105 -0
  212. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/obs-stack-architecture.md +107 -0
  213. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/sli-slo-design.md +95 -0
  214. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-observability/references/source-register.md +11 -0
  215. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/SKILL.md +303 -0
  216. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/agents/openai.yaml +4 -0
  217. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/canary-and-rollout-strategy.md +163 -0
  218. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/config-center-via-etcd.md +245 -0
  219. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/custom-control-plane-boundary.md +298 -0
  220. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/deploy-cli-concrete-recipe.md +312 -0
  221. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/deploy-pipeline.md +165 -0
  222. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/env-and-lane-matrix.md +126 -0
  223. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/lane-orchestration-control-plane.md +383 -0
  224. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/multi-region-and-cluster.md +135 -0
  225. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/promotion-gate-and-review.md +149 -0
  226. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/python-package-registry-release.md +462 -0
  227. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/rollback-playbook.md +123 -0
  228. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/secret-and-config-management.md +231 -0
  229. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-release-engineering/references/version-authority-and-deprecation.md +21 -0
  230. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/SKILL.md +276 -0
  231. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/agents/openai.yaml +4 -0
  232. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/dual-sidecar-and-traffic-config-center.md +127 -0
  233. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/framework-middleware.md +143 -0
  234. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/grpc-authority-workaround.md +90 -0
  235. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/http-response-envelope-contract.md +24 -0
  236. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/mesh-architecture.md +127 -0
  237. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/multi-env-routing.md +192 -0
  238. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/protobuf-http-contract-signals.md +64 -0
  239. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/retry-timeout-circuit-breaker.md +124 -0
  240. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/rpc-framework-recipe.md +494 -0
  241. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-choice.md +113 -0
  242. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-migration-playbook.md +231 -0
  243. package/dist/assets/marketplace/plugins/ccl-skills/skills/platform-service-connectivity/references/service-discovery-recipe.md +131 -0
  244. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/SKILL.md +235 -0
  245. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/agents/openai.yaml +4 -0
  246. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/adr-convention.md +146 -0
  247. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-checklist.md +30 -0
  248. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-evaluation-report-template.md +25 -0
  249. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-execution-spec.md +108 -0
  250. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-sop.md +457 -0
  251. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/algorithm-launch-templates.md +24 -0
  252. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/artifact-egress-confidentiality.md +58 -0
  253. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/code-review-checklist.md +86 -0
  254. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/cross-repo-coordination.md +46 -0
  255. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/delivery-lifecycle.md +192 -0
  256. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-review-gate-mechanics.md +62 -0
  257. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/design-routing-and-readiness.md +45 -0
  258. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/diagnostic-spec-match-gate.md +36 -0
  259. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dispatch-owner-skills.md +35 -0
  260. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/dormant-code-activation.md +47 -0
  261. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/existing-project-assessment-report.md +223 -0
  262. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/external-skill-augmentation.md +46 -0
  263. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/feature-deprecation-cascade.md +15 -0
  264. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/high-risk-resilience-gates.md +73 -0
  265. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/implementation-completeness-and-minimality.md +120 -0
  266. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/implementation-entry-reentry-gate.md +122 -0
  267. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/modular-monolith-heuristic.md +105 -0
  268. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/pre-final-continuation-gate.md +115 -0
  269. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/problem-resolution-and-learning.md +62 -0
  270. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/quality-attributes.md +112 -0
  271. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/quality-remediation-program.md +88 -0
  272. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/rd-standards-doc-family-checklist.md +27 -0
  273. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/refactoring-discipline.md +52 -0
  274. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/review-reception.md +34 -0
  275. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/shared-gate-artifact-classification.md +76 -0
  276. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/source-evidence-map.md +31 -0
  277. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/status-tracker-sync.md +77 -0
  278. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/sync-spec-repo-contract.md +25 -0
  279. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/verify-developer-experience.md +34 -0
  280. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/references/worktree-mechanics.md +55 -0
  281. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/scripts/AGENTS.md +18 -0
  282. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-rd-workflow/scripts/check-agent-contract-coverage.sh +213 -0
  283. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/SKILL.md +136 -0
  284. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/agents/openai.yaml +9 -0
  285. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/analytics-visualization-interactions.md +206 -0
  286. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/behavioral-aesthetic-logic.md +108 -0
  287. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/complex-creation-interactions.md +194 -0
  288. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-execution-checklist.md +214 -0
  289. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-impl-naming-and-versioning.md +53 -0
  290. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-intake-and-acceptance.md +129 -0
  291. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/design-system-source-of-truth.md +97 -0
  292. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/external-ui-ux-quality-benchmarks.md +79 -0
  293. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/frontend-code-evidence-map.md +63 -0
  294. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/interaction-design-patterns.md +146 -0
  295. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/layout-recipes-and-screenshot-acceptance.md +250 -0
  296. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-project-token-consistency.md +237 -0
  297. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/multi-stack-strategy.md +65 -0
  298. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/operational-processing-workflows.md +237 -0
  299. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-mobile-patterns.md +324 -0
  300. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/platform-web-desktop-patterns.md +456 -0
  301. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-lifecycle-acceptance-and-iteration.md +114 -0
  302. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/product-surface-patterns.md +79 -0
  303. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/resource-management-interactions.md +113 -0
  304. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/scenario-community-patterns.md +133 -0
  305. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/source-map.md +130 -0
  306. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/tokens-and-components.md +47 -0
  307. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/trust-sensitive-ai-and-data-patterns.md +96 -0
  308. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-audit.md +106 -0
  309. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/ui-ux-design-development.md +176 -0
  310. package/dist/assets/marketplace/plugins/ccl-skills/skills/product-ui-ux-design/references/visual-craft.md +111 -0
  311. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/SKILL.md +157 -0
  312. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/agents/openai.yaml +4 -0
  313. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/ai-service-integration-boundaries.md +57 -0
  314. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/api-contract-and-schema.md +62 -0
  315. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/api-security-boundaries.md +39 -0
  316. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/architecture-playbook.md +46 -0
  317. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/async-execution-model.md +24 -0
  318. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/background-jobs-and-scheduling.md +18 -0
  319. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/batch-and-pipeline-architecture.md +11 -0
  320. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/config-secrets-runtime.md +22 -0
  321. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/data-modeling-and-migrations.md +64 -0
  322. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/data-platform-architecture.md +211 -0
  323. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/event-driven-architecture.md +263 -0
  324. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/multi-tenant-isolation.md +281 -0
  325. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/observability-and-ops.md +26 -0
  326. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/packaging-runtime-readiness.md +20 -0
  327. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/redis-cache-coordination.md +41 -0
  328. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/reliability-and-error-contract.md +17 -0
  329. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/source-evidence-map.md +55 -0
  330. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-architecture/references/web-framework-boundaries.md +26 -0
  331. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/SKILL.md +143 -0
  332. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/agents/openai.yaml +4 -0
  333. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/ai-service-wiring-patterns.md +16 -0
  334. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/async-and-worker-patterns.md +24 -0
  335. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/background-job-patterns.md +18 -0
  336. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/batch-and-artifact-patterns.md +13 -0
  337. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/dependency-client-patterns.md +39 -0
  338. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/error-handling-patterns.md +26 -0
  339. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/feature-playbook.md +43 -0
  340. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/observability-implementation-patterns.md +31 -0
  341. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/project-structure-and-tooling.md +24 -0
  342. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/public-api-security-patterns.md +52 -0
  343. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/redis-cache-lock-patterns.md +78 -0
  344. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/schema-and-validation-patterns.md +23 -0
  345. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/source-evidence-map.md +56 -0
  346. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/sqlalchemy-and-migrations-patterns.md +99 -0
  347. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/testing-and-quality-patterns.md +61 -0
  348. package/dist/assets/marketplace/plugins/ccl-skills/skills/python-service-dev/references/web-framework-patterns.md +35 -0
  349. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/SKILL.md +91 -0
  350. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/agents/openai.yaml +4 -0
  351. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/config-runtime-readback.md +20 -0
  352. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/mr-merge-authorization.md +31 -0
  353. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/post-release-env-reset.md +31 -0
  354. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/release-closeout-evidence.md +20 -0
  355. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/release-scope-confirmation.md +21 -0
  356. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/tag-and-prod-pipeline-gate.md +20 -0
  357. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/test-scope-prompt.md +24 -0
  358. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-coordination/references/watcher-discipline.md +14 -0
  359. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/SKILL.md +64 -0
  360. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/agents/openai.yaml +4 -0
  361. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/comment-safe-release-doc.md +19 -0
  362. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/release-evidence-workflow.md +23 -0
  363. package/dist/assets/marketplace/plugins/ccl-skills/skills/release-doc-writer/references/release-testing-scope-section.md +15 -0
  364. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/SKILL.md +87 -0
  365. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-baseline/agents/openai.yaml +4 -0
  366. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/SKILL.md +130 -0
  367. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/agents/openai.yaml +4 -0
  368. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/prd-composition-contract.md +35 -0
  369. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/requirement-closure-contract.md +86 -0
  370. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-doc-writer/references/security-four-questions.md +38 -0
  371. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-intent/SKILL.md +91 -0
  372. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-intent/agents/openai.yaml +4 -0
  373. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/SKILL.md +88 -0
  374. package/dist/assets/marketplace/plugins/ccl-skills/skills/requirement-scope/agents/openai.yaml +4 -0
  375. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/SKILL.md +337 -0
  376. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/agents/openai.yaml +4 -0
  377. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/analysis-parse-fix-test-challenge-replay.md +47 -0
  378. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/attribution-verification.md +69 -0
  379. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/bootstrap-slim-c3-obligation-table.md +112 -0
  380. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/coverage-exhaustion-traps.md +45 -0
  381. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/description-authoring.md +162 -0
  382. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/dual-track-review-gate.md +507 -0
  383. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/eval-routing.md +86 -0
  384. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/evidence-card-template.md +51 -0
  385. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/example-domain-preselect.md +79 -0
  386. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/external-practice-controls.md +57 -0
  387. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-lifecycle-handoff.md +65 -0
  388. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/extraction-quickstart.md +194 -0
  389. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/firing-point-placement.md +75 -0
  390. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/harness-patterns-and-eval.md +286 -0
  391. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/incident-postmortem-extraction.md +190 -0
  392. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/l0-l1-l2-routing.md +114 -0
  393. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/online-skill-review.md +47 -0
  394. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/parallel-stack-references-pattern.md +164 -0
  395. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/r0-leakage-audit.md +90 -0
  396. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/recurring-anti-patterns-checklist.md +320 -0
  397. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/resume-paused-delivery.md +16 -0
  398. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-feedback-mining.md +33 -0
  399. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-finding-standards.md +57 -0
  400. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/review-rubric.md +40 -0
  401. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/rule-consolidation.md +118 -0
  402. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/skill-listing-budget.md +19 -0
  403. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-register.md +254 -0
  404. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/source-to-skill-extraction.md +658 -0
  405. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/two-source-extraction-pattern.md +167 -0
  406. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-judgment-extraction.md +179 -0
  407. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/uiux-routing-map.md +51 -0
  408. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/references/validation-and-landing.md +180 -0
  409. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/AGENTS.md +18 -0
  410. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-ccl-skills.sh +1452 -0
  411. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-evidence-card-leak.sh +491 -0
  412. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-mr-target-freshness.sh +173 -0
  413. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-size-budget.sh +488 -0
  414. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/check-sync-pointers.sh +419 -0
  415. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-golden-trace.rb +197 -0
  416. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-health.rb +327 -0
  417. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing-bank.rb +401 -0
  418. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/eval-routing.rb +248 -0
  419. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/generic-r0-leak-scan.sh +282 -0
  420. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/governing-chain-diff.py +321 -0
  421. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/impact-chain-gate.rb +964 -0
  422. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/register-firing-path-resolution.rb +708 -0
  423. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/skill-behavior-eval.py +540 -0
  424. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/source-register-lifecycle.rb +51 -0
  425. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/source-register-pending-status.rb +55 -0
  426. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_ai_coding_implementation_gates.sh +829 -0
  427. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_impact_chain_refscripts.sh +1203 -0
  428. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_r0_status.sh +75 -0
  429. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_register_pending_exclusion.sh +137 -0
  430. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_regressions.sh +173 -0
  431. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_route_drift.sh +377 -0
  432. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_size_budget.sh +833 -0
  433. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_skill_catalog.sh +491 -0
  434. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_ccl_source_register_lifecycle.sh +114 -0
  435. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_mr_target_freshness.sh +261 -0
  436. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_check_sync_pointers.sh +538 -0
  437. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_controlled_escalation_pins.sh +154 -0
  438. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_grader_diagnostics.sh +190 -0
  439. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_bank_surface_binding.sh +178 -0
  440. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_eval_routing_prose_target.sh +86 -0
  441. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_generic_r0_leak_scan.sh +131 -0
  442. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_git_identity_predicate_gate.sh +243 -0
  443. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_governing_chain_diff.sh +419 -0
  444. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_impact_chain_gate_dateless_host.sh +120 -0
  445. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_resolution.sh +724 -0
  446. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_register_firing_path_wiring.sh +414 -0
  447. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_regression_runner_registration.sh +34 -0
  448. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_bank_integrity.sh +205 -0
  449. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_routing_pointer_integrity.sh +194 -0
  450. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_credential_cwd.sh +61 -0
  451. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_cross_refs.sh +111 -0
  452. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/test_validate_skill_root_depth.sh +53 -0
  453. package/dist/assets/marketplace/plugins/ccl-skills/skills/skill-extraction-workflow/scripts/validate-skill.sh +257 -0
  454. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/SKILL.md +98 -0
  455. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/agents/openai.yaml +4 -0
  456. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/input-state-machines.md +36 -0
  457. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/streaming-rich-output.md +130 -0
  458. package/dist/assets/marketplace/plugins/ccl-skills/skills/terminal-cli-dev/references/terminal-side-channels.md +96 -0
  459. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/SKILL.md +408 -0
  460. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/agents/openai.yaml +4 -0
  461. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/AGENTS.md +18 -0
  462. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/bitable-setup.md +573 -0
  463. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/README.md +120 -0
  464. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/github-actions.yml +119 -0
  465. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/gitlab-ci.yml +76 -0
  466. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/ci_templates/jenkins.Jenkinsfile +106 -0
  467. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/classical-test-design-techniques.md +279 -0
  468. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/gen_report.py +2807 -0
  469. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/makefile-template.md +200 -0
  470. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/report-config-schema.md +272 -0
  471. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/run_pytestless.py +475 -0
  472. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/source-to-case-workflows.md +258 -0
  473. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-marker-conventions.md +316 -0
  474. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc-review-and-prioritization.md +145 -0
  475. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/AGENTS.md +16 -0
  476. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.dart +129 -0
  477. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.go +197 -0
  478. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.py +135 -0
  479. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/tc_helpers/tc.ts +285 -0
  480. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/test_gen_report.py +2144 -0
  481. package/dist/assets/marketplace/plugins/ccl-skills/skills/test-artifact-management/references/update-lifecycle.md +62 -0
  482. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/SKILL.md +212 -0
  483. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/agents/openai.yaml +4 -0
  484. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/ci-fixtures-and-flake-control.md +75 -0
  485. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/client-runtime-test-matrices.md +50 -0
  486. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/data-and-workflow-testing.md +34 -0
  487. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/design-closed-contract-oracles.md +31 -0
  488. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/e2e-real-flow-testing.md +71 -0
  489. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/fitness-functions.md +240 -0
  490. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/integration-contract-testing.md +235 -0
  491. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/non-functional-specialized-scenarios.md +296 -0
  492. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/rd-testing-standard-template.md +126 -0
  493. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/run-killing-mutation-walk.md +43 -0
  494. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/scenario-testing.md +136 -0
  495. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/source-evidence-map.md +59 -0
  496. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/structured-tc-input-translation.md +67 -0
  497. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-code-authoring-patterns.md +392 -0
  498. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-data-and-determinism.md +39 -0
  499. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/test-topology-and-commands.md +92 -0
  500. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/unit-testing.md +46 -0
  501. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/vendored-contract-drift-checklist.md +64 -0
  502. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/references/verify-enforcement-mechanisms.md +18 -0
  503. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/AGENTS.md +17 -0
  504. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/client-terminal-ansi-check.py +140 -0
  505. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/client-terminal-ansi-check.test.sh +75 -0
  506. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-ast-check.py +170 -0
  507. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-ast-check.test.sh +87 -0
  508. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-go-check.go +198 -0
  509. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/lang-basics-go-check.test.sh +109 -0
  510. package/dist/assets/marketplace/plugins/ccl-skills/skills/testing-strategy/scripts/test_mutation_backup_recipe.sh +237 -0
  511. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/SKILL.md +184 -0
  512. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/agents/openai.yaml +4 -0
  513. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/comment-safe-feishu.md +93 -0
  514. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/cross-model-co-review.md +3 -0
  515. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/delivery-face-closeout.md +60 -0
  516. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/doc-charter-first.md +17 -0
  517. package/dist/assets/marketplace/plugins/ccl-skills/skills/tighten-doc/references/session-vantage-leakage.md +58 -0
  518. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/SKILL.md +126 -0
  519. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/agents/openai.yaml +4 -0
  520. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/complex-workspace-patterns.md +47 -0
  521. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/embedded-h5-in-host.md +87 -0
  522. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/react-architecture.md +194 -0
  523. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/source-evidence-map.md +60 -0
  524. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-quality-release.md +190 -0
  525. package/dist/assets/marketplace/plugins/ccl-skills/skills/web-react-dev/references/web-ui-quality.md +83 -0
  526. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/SKILL.md +179 -0
  527. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/agents/openai.yaml +4 -0
  528. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/references/shared-branch-rebase.md +25 -0
  529. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/AGENTS.md +23 -0
  530. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/test_worktree_status.sh +207 -0
  531. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/test_worktree_sweep.sh +481 -0
  532. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/worktree-status.sh +325 -0
  533. package/dist/assets/marketplace/plugins/ccl-skills/skills/worktree-isolation/scripts/worktree-sweep.sh +245 -0
  534. package/dist/assets/release.json +2797 -0
  535. package/dist/claude-adapter.d.ts +9 -0
  536. package/dist/claude-adapter.js +240 -0
  537. package/dist/cli-worker.d.ts +1 -0
  538. package/dist/cli-worker.js +32 -0
  539. package/dist/cli.d.ts +22 -0
  540. package/dist/cli.js +214 -0
  541. package/dist/codex-host.d.ts +30 -0
  542. package/dist/codex-host.js +162 -0
  543. package/dist/fs-safe.d.ts +21 -0
  544. package/dist/fs-safe.js +241 -0
  545. package/dist/index.d.ts +2 -0
  546. package/dist/index.js +1 -0
  547. package/dist/manifest.d.ts +8 -0
  548. package/dist/manifest.js +135 -0
  549. package/dist/opencode-adapter.d.ts +10 -0
  550. package/dist/opencode-adapter.js +416 -0
  551. package/dist/operations.d.ts +3 -0
  552. package/dist/operations.js +956 -0
  553. package/dist/paths.d.ts +20 -0
  554. package/dist/paths.js +4 -0
  555. package/dist/types.d.ts +58 -0
  556. package/dist/types.js +1 -0
  557. package/dist/unified.d.ts +4 -0
  558. package/dist/unified.js +64 -0
  559. package/dist/version.d.ts +2 -0
  560. package/dist/version.js +5 -0
  561. package/package.json +35 -0
@@ -0,0 +1,1307 @@
1
+ #!/usr/bin/env bash
2
+ # owner-dispatch: mechanical firing for the product-rd "owner-dispatch firing gate".
3
+ #
4
+ # WHY THIS EXISTS
5
+ # The owner-invoke rule (invoke the owning stack/design skill at each product-R&D
6
+ # phase transition before producing substance) lived only as prose in SKILL.md +
7
+ # bootstrap salience, and recurringly did not fire — the failing action is
8
+ # agent-initiated and intent-dependent, so a stronger prompt cannot enforce it.
9
+ # Industry practice (Anthropic "gate intermediate steps" / poka-yoke; Claude Code
10
+ # hooks deny/ask/block enforced at the interpreter level; OpenAI Agents-SDK
11
+ # guardrail tripwires; structured-required) is unanimous: encode a must-happen
12
+ # step as a CODE gate that blocks, run as a separate check — never as prose.
13
+ # This engine is that gate. Built on the decision model in
14
+ # skills/llm-inference-integration/references/agent-lifecycle-hooks.md.
15
+ #
16
+ # SAFETY POSTURE (every default is the non-annoying / non-destructive one)
17
+ # - OPT-IN: a repo with no committed `.owner-dispatch.json` (or enabled:false) is a
18
+ # complete no-op. A cheap parent-walk for that file runs BEFORE any git/jq work,
19
+ # so a non-opted repo pays ~nothing and never waits on git discovery.
20
+ # - FAIL-OPEN: any internal error (missing jq/git, parse failure, unwritable/unsafe
21
+ # state dir, missing session id) ALLOWS the action. A broken gate must never brick
22
+ # editing, and no error path may fail-CLOSED.
23
+ # - DEFAULT `ask`, NOT `deny`: hard `deny` is the explicit `strict:true` opt-in, is
24
+ # Claude-Code-only (Codex ignores the decision), applies ONLY to precise
25
+ # Edit/Write/MultiEdit/NotebookEdit file paths (never the heuristic Bash match),
26
+ # and is downgraded to `ask` when the boundary state dir is not safely writable
27
+ # (so strict can never brick a repo whose state dir is unavailable).
28
+ # - Bash write-gating is BEST-EFFORT / ADVISORY only (a shell hook cannot reliably
29
+ # detect every write form — `python -c`, `ed`, `make generate`, rsync, etc. slip
30
+ # through). The host-agnostic un-bypassable gate is the `ci` subcommand.
31
+ # - STATE WRITES are symlink-safe (refuse/replace symlinked state paths; atomic
32
+ # temp+rename) so a crafted repo cannot redirect a write outside the state dir.
33
+ # - STOP enforcement is per-session (keyed by a real session id; fail-open without
34
+ # one), blocks at most once per actor per session, and fails open if the cap stamp
35
+ # cannot be persisted — so it can never trap the user. The SAME engine handles
36
+ # SubagentStop: when stdin carries agent_id (a dispatched subagent) the activity
37
+ # marker, block-once cap, and waiver are scoped to that subagent, so a read-only
38
+ # sibling / the main thread's dirty gated work never blocks an unrelated subagent.
39
+ #
40
+ # SUBCOMMANDS: pretool | stop | record --owners "a,b" | ci [--base REF] | status
41
+ # | verify-invocations --owners "a,b" --transcript PATH
42
+
43
+ set -u
44
+
45
+ # Absolute path to this engine, so remediation messages name a command that works
46
+ # from any product repo's cwd (a product repo only holds `.owner-dispatch.json`).
47
+ SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/$(basename "${BASH_SOURCE[0]}")"
48
+ SID="nosession" # set per-invocation from the hook's session_id (pretool/stop)
49
+ AID="" # agent_id (raw) — non-empty only inside a dispatched subagent (PreToolUse
50
+ # fires in subagents carrying agent_id; SubagentStop carries it too).
51
+ AIDKEY="" # filename-safe, INJECTIVE key for agent_id, computed by jq @base64 from
52
+ # the RAW JSON (NOT a bash var — command substitution would drop NUL /
53
+ # strip trailing newline / mangle non-ASCII and let distinct agent_ids
54
+ # collide on the same marker). Empty iff no agent_id. Scopes the activity
55
+ # marker / block-cap / waiver to ONE subagent.
56
+
57
+ # ----- fail-open helpers ------------------------------------------------------
58
+ allow_pretool() { exit 0; } # no output => allow
59
+ allow_stop() { exit 0; } # no output => allow stop
60
+ emit_pretool_deny() { # $1 reason $2 decision(deny|ask)
61
+ jq -nc --arg r "$1" --arg d "$2" \
62
+ '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$d,permissionDecisionReason:$r}}'
63
+ exit 0
64
+ }
65
+ emit_stop_block() { jq -nc --arg r "$1" '{decision:"block",reason:$r}'; exit 0; }
66
+ have() { command -v "$1" >/dev/null 2>&1; }
67
+ # ----- cheap opt-in discovery (NO git; runs before any expensive call) ---------
68
+ # Walk up from a start dir looking for `.owner-dispatch.json`. Pure bash, no git/jq,
69
+ # so a NON-opted repo pays only a few stat()s and never waits on git discovery.
70
+ # (The match here is a CANDIDATE; opt_in_root verifies it is a tracked top-level
71
+ # config before the gate activates.)
72
+ find_config_dir() { # $1 start dir -> echo candidate root dir, or fail
73
+ local d="$1"
74
+ [ -d "$d" ] || d=$(dirname -- "$d")
75
+ while [ -n "$d" ] && [ "$d" != "/" ] && [ "$d" != "." ]; do
76
+ [ -f "$d/.owner-dispatch.json" ] && { printf '%s' "$d"; return 0; }
77
+ d=$(dirname -- "$d")
78
+ done
79
+ [ -f "/.owner-dispatch.json" ] && { printf '%s' "/"; return 0; }
80
+ return 1
81
+ }
82
+ # Verified opt-in: the candidate config must sit at the git TOP-LEVEL of the target
83
+ # AND be TRACKED in the repo (a stray untracked file or a parent-directory config
84
+ # must never gate an unrelated/nested repo). Runs git ONLY when a candidate exists.
85
+ opt_in_root() { # $1 start dir -> echo verified root, or fail
86
+ local cand top; cand=$(find_config_dir "$1") || return 1
87
+ cand=$(realpath "$cand" 2>/dev/null) || return 1 # physical, to match git's toplevel
88
+ top=$(git -C "$cand" rev-parse --show-toplevel 2>/dev/null) || return 1
89
+ [ "$top" = "$cand" ] || return 1
90
+ git -C "$cand" ls-files --error-unmatch .owner-dispatch.json >/dev/null 2>&1 || return 1
91
+ printf '%s' "$cand"
92
+ }
93
+
94
+ # ----- config + classification ------------------------------------------------
95
+ config_path() { printf '%s/.owner-dispatch.json' "$1"; }
96
+ # Echo "enabled" iff opted in AND parseable AND enabled; malformed => empty (fail-open).
97
+ cfg_enabled() { # $1 root
98
+ local c; c=$(config_path "$1"); [ -r "$c" ] || return 0
99
+ jq -er 'if (.enabled // false) then "enabled" else empty end' "$c" 2>/dev/null
100
+ }
101
+ cfg_get() { # $1 root $2 jq-filter $3 default
102
+ local c; c=$(config_path "$1"); [ -r "$c" ] || { printf '%s' "$3"; return; }
103
+ jq -r "$2 // \"$3\"" "$c" 2>/dev/null || printf '%s' "$3"
104
+ }
105
+ path_matches_any() { # $1 rel $2.. globs
106
+ local rel="$1"; shift
107
+ shopt -s extglob 2>/dev/null
108
+ local g
109
+ for g in "$@"; do
110
+ [ -z "$g" ] && continue
111
+ # $g INTENTIONALLY unquoted: match as a glob, not a literal.
112
+ # shellcheck disable=SC2254
113
+ case "$rel" in $g) return 0 ;; esac
114
+ # A leading **/ means "zero or more directories" (gitignore/globstar intent).
115
+ # bash `case` is NOT globstar-aware (* already crosses /, so **/PAT collapses
116
+ # to */PAT and only matches paths WITH a slash). Also test the slash-stripped
117
+ # PAT so a top-level (repo-root) file is gated too — otherwise a repo whose
118
+ # product code is top-level + a ["**/*.go"]-style config silently gates nothing.
119
+ case "$g" in
120
+ '**/'*)
121
+ # shellcheck disable=SC2254
122
+ case "$rel" in ${g#'**/'}) return 0 ;; esac
123
+ ;;
124
+ esac
125
+ done
126
+ return 1
127
+ }
128
+ load_globs() { # $1 root -> PRODUCT_GLOBS / EXCLUDE_GLOBS
129
+ local c; c=$(config_path "$1"); PRODUCT_GLOBS=(); EXCLUDE_GLOBS=()
130
+ [ -r "$c" ] || return 0
131
+ local line
132
+ while IFS= read -r line; do [ -n "$line" ] && PRODUCT_GLOBS+=("$line"); done < <(jq -r '(.product_globs // [])[]' "$c" 2>/dev/null)
133
+ while IFS= read -r line; do [ -n "$line" ] && EXCLUDE_GLOBS+=("$line"); done < <(jq -r '(.exclude_globs // [])[]' "$c" 2>/dev/null)
134
+ EXCLUDE_GLOBS+=("**/*.md" ".owner-dispatch.json" ".git/**")
135
+ }
136
+ is_gated_path() { # $1 root $2 rel
137
+ load_globs "$1"
138
+ [ "${#PRODUCT_GLOBS[@]}" -gt 0 ] || return 1
139
+ path_matches_any "$2" "${EXCLUDE_GLOBS[@]}" && return 1
140
+ path_matches_any "$2" "${PRODUCT_GLOBS[@]}" && return 0
141
+ return 1
142
+ }
143
+
144
+ # ----- symlink-safe per-REPOSITORY state --------------------------------------
145
+ # Keyed on the COMMON git dir, not `--absolute-git-dir`. Under a linked worktree the
146
+ # latter is `.git/worktrees/<name>`, so state written while working in a worktree is
147
+ # invisible to a read from the main checkout — and that split is guaranteed by this
148
+ # org's own rules, not an edge case: `worktree-isolation` mandates developing in a
149
+ # worktree, while `cmd_stop` resolves its repo from $PWD, which the harness routinely
150
+ # resets back to the main checkout. The gate then reports "never recorded" on the
151
+ # standard workflow, and a gate that cries wolf every delivery stops being read at all.
152
+ # Worktree cleanup would also delete the record. State is session-scoped and the actor
153
+ # is already isolated by session/agent id inside this dir, so sharing it per repository
154
+ # is the correct scope — one repo, one boundary namespace.
155
+ #
156
+ # `--git-common-dir` is asked for ABSOLUTELY on purpose: plain `--git-common-dir`
157
+ # answers a bare relative `.git` when the caller is the main checkout (absolute only
158
+ # from a linked worktree), which would make the state dir depend on the caller's cwd —
159
+ # the same defect class, re-introduced. `--path-format` needs git >= 2.31; the fallback
160
+ # resolves the relative answer against the root git was pointed at, not against $PWD.
161
+ boundary_dir() { # $1 root (echo <common-git-dir>/owner-dispatch)
162
+ local gd
163
+ gd=$(git -C "$1" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) || gd=""
164
+ if [ -z "$gd" ]; then
165
+ gd=$(git -C "$1" rev-parse --git-common-dir 2>/dev/null) || return 1
166
+ [ -n "$gd" ] || return 1
167
+ case "$gd" in /*) ;; *) gd="$1/$gd" ;; esac
168
+ fi
169
+ # Canonicalise. The two branches above disagree on symlinked roots: git answers the
170
+ # PHYSICAL path, while the fallback's `$1/<relative>` keeps whatever logical path the
171
+ # caller passed (macOS /var -> /private/var is the everyday case). Left alone, a host on
172
+ # git < 2.31 would get a different key from the main checkout than from a worktree and
173
+ # this whole bug would come back on exactly the machines that cannot use the fast path.
174
+ # `opt_in_root` already realpath's "to match git's toplevel"; match it here.
175
+ local rp; rp=$(realpath "$gd" 2>/dev/null) && [ -n "$rp" ] && gd="$rp"
176
+ printf '%s/owner-dispatch' "$gd"
177
+ }
178
+ # Resolve + create the state dir, refusing a symlinked dir (a crafted repo could
179
+ # point it elsewhere). Echo the dir on success; fail (=> caller fails open).
180
+ state_dir() { # $1 root
181
+ local d; d=$(boundary_dir "$1") || return 1
182
+ [ -L "$d" ] && return 1
183
+ [ -e "$d" ] && [ ! -d "$d" ] && return 1
184
+ mkdir -p "$d" 2>/dev/null || return 1
185
+ printf '%s' "$d"
186
+ }
187
+ # Symlink-safe writes: a symlink in our own namespace is illegitimate -> drop it,
188
+ # then write via mktemp (O_EXCL, unpredictable name => no predictable-temp race) +
189
+ # atomic rename so the redirect never follows a link. NOTE: the state dir lives
190
+ # inside `<git-dir>/owner-dispatch`; an attacker who can race writes there already
191
+ # has write access to your `.git` (hooks, objects, config) and owns the repo — so
192
+ # residual TOCTOU here is bounded by a trust boundary you have already lost.
193
+ safe_put() { # $1 file $2 content
194
+ [ -L "$1" ] && rm -f "$1" 2>/dev/null
195
+ local dir t; dir=$(dirname -- "$1")
196
+ t=$(mktemp "$dir/.od.XXXXXX" 2>/dev/null) || return 1
197
+ printf '%s' "$2" > "$t" 2>/dev/null && mv -f "$t" "$1" 2>/dev/null || { rm -f "$t" 2>/dev/null; return 1; }
198
+ }
199
+ safe_append() { # $1 file $2 line (atomic read-modify-rewrite via safe_put)
200
+ [ -L "$1" ] && rm -f "$1" 2>/dev/null
201
+ local cur=""; [ -f "$1" ] && cur=$(cat "$1" 2>/dev/null)
202
+ safe_put "$1" "$cur$2"$'\n'
203
+ }
204
+ # Append $2 + a NUL terminator using O_APPEND (`>>`), which is CONCURRENCY-SAFE: each
205
+ # write is positioned at EOF atomically, so two parallel same-agent PreToolUse hooks (the
206
+ # model can emit parallel Edits in one turn) never lose an entry — unlike a read-copy-
207
+ # rename, where both read the same old file and the last rename clobbers the other's add.
208
+ # A single path is far under PIPE_BUF so the write is atomic. NUL-delimited so entries
209
+ # with newlines/spaces are read back by exact byte-equality. Symlink-dropped first (same
210
+ # .git-trust bound as the other state writers; whole-file atomicity isn't needed for an
211
+ # append-only log).
212
+ safe_append_z() { # $1 file $2 value
213
+ [ -L "$1" ] && rm -f "$1" 2>/dev/null
214
+ printf '%s\0' "$2" >> "$1" 2>/dev/null || return 1
215
+ }
216
+ # Atomic, symlink-safe, BINARY/NUL-safe write from stdin (bash vars can't hold NUL,
217
+ # so NUL-delimited path lists must stream straight to the temp file via cat).
218
+ safe_put_stdin() { # $1 file (content on stdin)
219
+ [ -L "$1" ] && rm -f "$1" 2>/dev/null
220
+ local dir t; dir=$(dirname -- "$1")
221
+ t=$(mktemp "$dir/.od.XXXXXX" 2>/dev/null) || return 1
222
+ cat > "$t" 2>/dev/null && mv -f "$t" "$1" 2>/dev/null || { rm -f "$t" 2>/dev/null; return 1; }
223
+ }
224
+ sid_safe() { printf '%s' "${1:-}" | tr -c 'A-Za-z0-9._-' '_'; }
225
+ # Compute the INJECTIVE, filename-safe agent_id key directly from the hook's raw JSON on
226
+ # stdin via jq @base64 — operating on the JSON value, NOT a bash var, so NUL / trailing
227
+ # newline / non-ASCII in agent_id cannot be dropped or mangled into a collision. base64's
228
+ # '+' and '/' are remapped to '-' '_' (base64url) so the key is a safe filename component;
229
+ # '=' padding is filename-safe as-is. Empty agent_id => empty key (base64 of "" is "").
230
+ aid_key_from() { # stdin: raw hook JSON -> echo injective key (empty if no agent_id)
231
+ jq -r '.agent_id // "" | @base64' 2>/dev/null | tr '+/' '-_'
232
+ }
233
+ # In an UNBORN repo `git rev-parse HEAD` prints the literal "HEAD" on stdout AND exits
234
+ # non-zero, so the old `cmd 2>/dev/null || printf NO_HEAD` emitted BOTH — a two-line value
235
+ # that equalled nothing and made every NO_HEAD comparison downstream dead code. Use
236
+ # --verify so the failure path produces no stdout, and the sentinel is the only output.
237
+ current_head() { git -C "$1" rev-parse --verify --quiet HEAD 2>/dev/null || printf 'NO_HEAD'; }
238
+
239
+ # Validity is (repo, TTL, HEAD-continuity): the recorded head must remain an ANCESTOR of the
240
+ # current HEAD, not equal to it.
241
+ #
242
+ # Why equality was wrong: the gate requires recording BEFORE the first gated edit, and a
243
+ # delivery's own commit necessarily lands AFTER that edit — so a HEAD-equality condition
244
+ # invalidated the boundary of every delivery that commits, and Stop then reported it as
245
+ # "never recorded". Worse than the false block: the only strategy that satisfied a
246
+ # HEAD-pinned boundary was re-recording at session end, which turns "record before editing"
247
+ # into post-hoc attestation — the record-field forgery surface the invocation-evidence
248
+ # check exists to close.
249
+ #
250
+ # Why dropping HEAD entirely was ALSO wrong: equality was incidentally catching history
251
+ # discontinuities — `git switch` to an unrelated branch, a reset onto a divergent commit —
252
+ # which genuinely do mean "a different piece of work". A TTL-only rule let the same actor
253
+ # record once, finish that delivery, jump to an unrelated branch inside the TTL, and edit
254
+ # gated code with no gate at either end. Ancestry keeps the commit-forward case working
255
+ # (the actual defect) while re-firing on the discontinuity case.
256
+ #
257
+ # HEAD was ALSO standing in for actor identity ("this boundary is THIS work unit's"), and
258
+ # nothing here replaces that: there is no actor binding. `owners_not_invoked` at Stop
259
+ # verifies only that the recorded owner NAMES appear as invoked skills in the current
260
+ # actor's transcript. That blocks an actor which invoked nothing, or a different owner set
261
+ # — but it clears an actor that invoked the same names, whoever recorded the boundary. So
262
+ # it detects SOME cross-actor reuse as a side effect; it does not bind a boundary to an
263
+ # actor. Do not restate it as identity binding (see the residual note below).
264
+ #
265
+ # Residual (accepted by the risk owner): within TTL, on the same descendant line of work, in
266
+ # the same worktree, a DIFFERENT actor inherits this boundary. PreToolUse never reads the
267
+ # transcript, and Stop clears that actor too whenever it invoked the same owner NAMES
268
+ # (`missing` empty => allow) — the normal case for a repo with a stable owner set, not an
269
+ # edge case. So "record before first edit" is not enforced per-actor. Do not restate this as
270
+ # "Stop still catches it": it does not. Also unenforced: whether the recorded owner set
271
+ # matches the concerns actually touched (needs a glob->owner map; deferred).
272
+ # Parse all gate-critical fields from ONE jq invocation, i.e. one opened version of the file.
273
+ # Sets b_repo / b_exp / b_head in the caller's scope (they are declared `local` there).
274
+ # @tsv escapes any literal tab inside a value, so splitting on real tabs is unambiguous.
275
+ boundary_read() { # $1 boundary.json -> sets b_repo b_exp b_head b_gitdir; fails if unparseable
276
+ local snap rest
277
+ snap=$(jq -r '[(.repo_root // ""), (.expires_at // 0), (.head // ""), (.repo_git_dir // "")] | @tsv' "$1" 2>/dev/null) || return 1
278
+ [ -n "$snap" ] || return 1
279
+ b_repo=${snap%%$'\t'*}; rest=${snap#*$'\t'}
280
+ b_exp=${rest%%$'\t'*}; rest=${rest#*$'\t'}
281
+ # `@tsv` always emits all four fields, so a missing tab here means a malformed
282
+ # snapshot rather than an absent value — treat the git dir as unknown and let the
283
+ # caller fall back, instead of silently aliasing it to the head.
284
+ case "$rest" in
285
+ *$'\t'*) b_head=${rest%%$'\t'*}; b_gitdir=${rest#*$'\t'} ;;
286
+ *) b_head=$rest; b_gitdir="" ;;
287
+ esac
288
+ }
289
+
290
+ boundary_valid() { # $1 root -> echo "valid" iff a non-expired boundary exists
291
+ local d f now b_repo b_exp b_head b_gitdir cur
292
+ d=$(boundary_dir "$1") || return 1
293
+ f="$d/boundary.json"; [ -r "$f" ] && [ ! -L "$f" ] || return 1
294
+ now=$(date +%s)
295
+ # ONE snapshot, one jq. Reading the fields through separate jq processes was a torn-read
296
+ # fail-open: `record` replaces this file by atomic rename, so a concurrent record between
297
+ # two reads let the check combine an unexpired expiry from one record with the head from
298
+ # another and return valid though neither record was. A single invocation sees exactly one
299
+ # version of the file.
300
+ boundary_read "$f" || return 1 # sets b_repo / b_exp / b_head / b_gitdir
301
+ # REPOSITORY identity, not working-tree path. `repo_root` is a per-worktree path, so
302
+ # comparing it literally rejected a boundary recorded in a linked worktree and read
303
+ # from the main checkout — i.e. every delivery that follows `worktree-isolation`.
304
+ # The common git dir is the same for every worktree of one repo AND survives the
305
+ # worktree being removed at cleanup, which the recording path never does. This is a
306
+ # replacement, not a removal: it still refuses a boundary belonging to a different
307
+ # repository (what the path equality was really there for). A legacy record with no
308
+ # repo_git_dir keeps the old path equality — conservative, since that record was
309
+ # written by the pre-fix code and can only have come from this same root anyway.
310
+ if [ -n "$b_gitdir" ]; then
311
+ [ "$b_gitdir" = "$(boundary_dir "$1")" ] || return 1
312
+ else
313
+ [ "$b_repo" = "$1" ] || return 1
314
+ fi
315
+ [ "$now" -lt "$b_exp" ] 2>/dev/null || return 1
316
+ # CONTINUITY, not equality: the recorded head must still be an ancestor of the current
317
+ # HEAD. Committing forward on the same line of work keeps the boundary (that is the whole
318
+ # point of dropping equality), while `git switch` to an unrelated branch, a reset onto a
319
+ # divergent commit, or any other history discontinuity re-fires the gate — those really do
320
+ # mean "a different piece of work", which equality was incidentally catching and a
321
+ # TTL-only rule would silently let through.
322
+ cur=$(current_head "$1")
323
+ # Unborn repo on either side: there is no history, so continuity cannot be established —
324
+ # fail closed. This IS a deliberate behaviour change, not a no-op: the previous equality
325
+ # rule ALLOWED here. `current_head` used to emit the two-line value "HEAD\nNO_HEAD" (the
326
+ # unborn `rev-parse` prints "HEAD" on stdout *and* fails), and since both `record` and the
327
+ # check used the same helper, both sides produced that identical string and compared equal
328
+ # — verified against the old code, which returns allow in an unborn repo where this returns
329
+ # ask. Failing closed is the intended outcome: an unborn repo has no line of work to be
330
+ # continuous with, and switching between orphan branches must not satisfy the gate.
331
+ { [ "$b_head" = "NO_HEAD" ] || [ "$cur" = "NO_HEAD" ]; } && return 1
332
+ [ -n "$b_head" ] || return 1
333
+ # Both env vars are load-bearing and neither substitutes for the other (verified on
334
+ # git 2.50): GIT_NO_REPLACE_OBJECTS disables `git replace` refs, GIT_GRAFT_FILE disables
335
+ # the legacy `.git/info/grafts` file — with only the former, a grafts file still makes
336
+ # merge-base report an unrelated commit as an ancestor. Not a serious adversary story
337
+ # (anyone deliberately evading can `touch <waiver>`, which is documented), but a repo
338
+ # legitimately carrying grafts or replace refs would otherwise get a silently wrong
339
+ # verdict. True-object ancestry only.
340
+ GIT_NO_REPLACE_OBJECTS=1 GIT_GRAFT_FILE=/dev/null \
341
+ git -C "$1" merge-base --is-ancestor "$b_head" "$cur" 2>/dev/null || return 1
342
+ printf 'valid'
343
+ }
344
+
345
+ # Classify WHY a boundary is unusable. Wording only — never a gate input; every enforcement
346
+ # path keeps calling boundary_valid.
347
+ #
348
+ # Each invalidating reason gets its OWN label, because collapsing them is the exact defect
349
+ # this file was changed to fix: telling an actor it "never recorded" a boundary when a
350
+ # record exists sends diagnosis hunting for a missing file. `expired` and `discontinuous`
351
+ # are both "you recorded, but it no longer applies" — very different from "there is nothing
352
+ # here". An unparseable record stays `absent` (conservative: never imply a usable record was
353
+ # found when the field could not be read).
354
+ boundary_state() { # $1 root -> echo valid|expired|discontinuous|absent
355
+ local d f now b_repo b_exp b_head b_gitdir cur
356
+ boundary_valid "$1" >/dev/null && { printf 'valid'; return 0; }
357
+ d=$(boundary_dir "$1") || { printf 'absent'; return 0; }
358
+ f="$d/boundary.json"; { [ -r "$f" ] && [ ! -L "$f" ]; } || { printf 'absent'; return 0; }
359
+ boundary_read "$f" || { printf 'absent'; return 0; } # one snapshot, same as the gate
360
+ # Same repository-identity rule as the gate. Keeping literal path equality HERE while the
361
+ # gate compares git dirs would rebuild the exact contradiction `cmd_status` already warns
362
+ # about: the diagnostic saying "absent" for a boundary the gate accepts.
363
+ if [ -n "$b_gitdir" ]; then
364
+ [ "$b_gitdir" = "$(boundary_dir "$1")" ] || { printf 'absent'; return 0; }
365
+ else
366
+ [ "$b_repo" = "$1" ] || { printf 'absent'; return 0; }
367
+ fi
368
+ case "$b_exp" in (''|0|*[!0-9]*) printf 'absent'; return 0 ;; esac # non-integer => absent
369
+ now=$(date +%s)
370
+ [ "$now" -lt "$b_exp" ] 2>/dev/null || { printf 'expired'; return 0; }
371
+ # Present, repo-matching, unexpired, yet boundary_valid rejected it. Before calling that a
372
+ # history discontinuity, prove the recorded head IS a real commit here: a well-formed but
373
+ # nonexistent sha (an all-zero value, a gc'd or shallow-clone-missing object) is an
374
+ # unusable record, not evidence of a branch switch — saying "a reset moved HEAD off it"
375
+ # there would be the same false diagnosis this whole change set exists to remove.
376
+ cur=$(current_head "$1")
377
+ case "$b_head" in (''|*[!0-9a-fA-F]*) printf 'absent'; return 0 ;; esac # not sha-shaped
378
+ GIT_NO_REPLACE_OBJECTS=1 GIT_GRAFT_FILE=/dev/null \
379
+ git -C "$1" cat-file -e "$b_head^{commit}" 2>/dev/null || { printf 'absent'; return 0; }
380
+ [ "$b_head" = "$cur" ] && { printf 'absent'; return 0; } # equal yet invalid => not continuity
381
+ printf 'discontinuous'
382
+ }
383
+
384
+ # ----- invocation evidence (name != invoke) -----------------------------------
385
+ # The record only stores owner NAMES; this cross-checks them against the skills
386
+ # ACTUALLY invoked (loaded) this session, read from the Claude Code session
387
+ # transcript (a JSONL of message events). A Skill invocation appears as a
388
+ # `tool_use` content block with name=="Skill" and input.skill=="<plugin>:<name>".
389
+ # Empirically verified on the current transcript format; missing/unreadable or
390
+ # malformed or shape-drifted evidence stays FAIL-OPEN. A subagent transcript with a
391
+ # verifiable tool-event shape but zero Skill calls proves the worker never loaded one.
392
+ #
393
+ # Emit the base skill names invoked in a transcript, ONE PER LINE (strip any "prefix:").
394
+ invoked_skills() { # $1 transcript-path
395
+ [ -n "$1" ] && [ -r "$1" ] || return 0
396
+ # Per-line jq (JSONL); tolerate malformed lines. A line's message.content[] may
397
+ # carry tool_use blocks. input.skill is "ccl-skills:foo" or bare "foo".
398
+ jq -rR '
399
+ fromjson?
400
+ | select(.type=="assistant") # real invocations live in assistant events only
401
+ | (.message.content // empty)
402
+ | if type=="array" then .[] else empty end
403
+ | select(type=="object" and .type=="tool_use" and .name=="Skill")
404
+ | (.input.skill)
405
+ | strings # drop non-string skill values (no sub() abort)
406
+ | select(length>0)
407
+ | ascii_downcase
408
+ | if startswith("ccl-skills:") then ltrimstr("ccl-skills:") else . end
409
+ ' "$1" 2>/dev/null
410
+ }
411
+
412
+ # True iff the transcript contains at least one recognized assistant tool_use block and
413
+ # every visible Skill block has the invocation shape parsed by invoked_skills. This lets
414
+ # strict worker verification distinguish "the worker invoked no skills" from "the host
415
+ # transcript or Skill-event shape drifted" without turning valid-JSON drift into a trap.
416
+ transcript_has_verifiable_invocation_shape() { # $1 transcript-path
417
+ [ -n "$1" ] && [ -r "$1" ] || return 1
418
+ jq -seR '
419
+ [ split("\n")[]
420
+ | try fromjson catch empty
421
+ | select(.type=="assistant")
422
+ | (.message.content // empty)
423
+ | if type=="array" then .[] else empty end
424
+ | select(type=="object" and .type=="tool_use")
425
+ ] as $tools
426
+ | ($tools | length) > 0
427
+ and ([ $tools[] | select((.name | type) != "string") ] | length) == 0
428
+ and ([ $tools[]
429
+ | select(.name=="Skill"
430
+ and (((.input | type) != "object")
431
+ or ((.input.skill | type) != "string")
432
+ or (.input.skill | length) == 0))
433
+ ] | length) == 0
434
+ ' "$1" >/dev/null 2>&1
435
+ }
436
+
437
+ # Echo (comma-joined) the recorded owners that (a) look like a skill name
438
+ # (kebab token, no spaces) and (b) were NEVER invoked in the transcript. Free-text
439
+ # owner labels ("then the touched owner") are skill-name-shaped-filtered out, so
440
+ # they never false-flag. Empty output => nothing to report (all invoked, or
441
+ # unverifiable => fail-open). $3=true makes a shape-verifiable zero-invocation transcript
442
+ # report every skill-shaped owner; this is used for a worker's own transcript only.
443
+ owners_not_invoked() { # $1 boundary.json $2 transcript-path $3 empty-is-missing(true|false)
444
+ local bf="$1" tp="$2" empty_is_missing="${3:-false}"
445
+ [ -r "$bf" ] && [ -n "$tp" ] && [ -r "$tp" ] || return 0
446
+ # newline-separated invoked base-name set for grep -Fxq membership
447
+ local invoked; invoked=$(invoked_skills "$tp")
448
+ # Main sessions preserve the historical fail-open behavior for an empty set. A worker
449
+ # uses its dedicated transcript and strict mode: if that file contains a verifiable
450
+ # tool-event shape but zero Skill calls, the empty set is evidence of a cold worker.
451
+ # Missing, malformed, or Skill-shape-drifted evidence remains unverifiable/fail-open.
452
+ if [ -z "$invoked" ]; then
453
+ [ "$empty_is_missing" = "true" ] && transcript_has_verifiable_invocation_shape "$tp" || return 0
454
+ fi
455
+ local out="" o norm
456
+ while IFS= read -r o; do
457
+ [ -n "$o" ] || continue
458
+ # Normalize the owner token the SAME way invoked skills are: lowercase and strip a
459
+ # leading "ccl-skills:" so a recorded full id (ccl-skills:foo) matches the
460
+ # invoked base name "foo". Other-plugin prefixes are NOT stripped (they keep the ':'
461
+ # and fail the kebab-shape check below), so a same-named skill from an untrusted
462
+ # plugin cannot silently satisfy a CCL owner.
463
+ norm=$(printf '%s' "$o" | tr '[:upper:]' '[:lower:]')
464
+ case "$norm" in ccl-skills:*) norm=${norm#ccl-skills:} ;; esac
465
+ # skill-name shape only: a single kebab token; excludes phrases with spaces/commas
466
+ # and any remaining "plugin:" colon.
467
+ case "$norm" in *[!a-z0-9-]*|"") continue ;; esac
468
+ printf '%s\n' "$invoked" | grep -Fxq "$norm" && continue
469
+ out="${out:+$out,}$o"
470
+ done < <(jq -r '(.touched_owners // [])[]? | gsub("^\\s+|\\s+$";"")' "$bf" 2>/dev/null)
471
+ printf '%s' "$out"
472
+ }
473
+
474
+ # ----- per-session activity markers + baseline --------------------------------
475
+ # Two activity markers record THAT this session did owner-dispatch-relevant work:
476
+ # .touched — an Edit/Write tool touched a gated path.
477
+ # .bashwrite — the best-effort Bash write-verb heuristic matched (over-matches
478
+ # read-only commands, so it is only an activity hint, never proof).
479
+ # Neither marker's CONTENT is parsed (safe_append concatenates without a reliable
480
+ # separator) — they are existence-only triggers. What a session actually CHANGED is
481
+ # decided at Stop by diffing the current tree against a baseline snapshot (HEAD +
482
+ # product/exclude globs + content-hashed pre-session dirty paths) captured at the FIRST
483
+ # touch (PreToolUse fires before the edit applies, so the baseline is the pre-edit
484
+ # state). That distinguishes real new gated work (committed OR dirty) from a read-only/
485
+ # rejected/reverted session and from untouched pre-existing dirt — without trapping —
486
+ # and resists config mutation (product-shrink / exclude-widen) via baseline∪current
487
+ # classification, with `ci` as the reliable backstop.
488
+ note_touched() { # $1 root $2 rel (per-session, symlink-safe; + per-agent in a subagent)
489
+ local sd; sd=$(state_dir "$1") || return 0
490
+ capture_baseline "$1"
491
+ safe_append "$sd/s.$(sid_safe "$SID").touched" "$2"
492
+ # In a subagent, ALSO record a per-agent marker so SubagentStop can tell THIS subagent
493
+ # did gated work (vs. a read-only sibling or the main thread). NUL-delimited so the path
494
+ # is read back by exact byte-equality (the session marker above is existence-only).
495
+ [ -n "$AID" ] && safe_append_z "$sd/s.$(sid_safe "$SID").a.$AIDKEY.touched" "$2"
496
+ }
497
+ note_bashwrite() { # $1 root (per-session heuristic sentinel, symlink-safe; + per-agent in a subagent)
498
+ local sd; sd=$(state_dir "$1") || return 0
499
+ capture_baseline "$1"
500
+ safe_put "$sd/s.$(sid_safe "$SID").bashwrite" "1"
501
+ [ -n "$AID" ] && safe_put "$sd/s.$(sid_safe "$SID").a.$AIDKEY.bashwrite" "1"
502
+ }
503
+
504
+ # Baseline globs captured at first touch (the authoritative classification at the time
505
+ # the work happened), kept separate from the current globs so config mutation can't
506
+ # dodge. Always SET (even empty) so `"${BASE_*[@]}"` is safe under bash 3.2 `set -u`.
507
+ BASE_PRODUCT=(); BASE_EXCLUDE=()
508
+
509
+ # Echo the working-tree content hash of <rel> (or ABSENT if missing/unreadable).
510
+ # Filters are applied identically at capture and at Stop, so the comparison is stable.
511
+ path_hash() { # $1 root $2 rel
512
+ git -C "$1" hash-object -- "$2" 2>/dev/null || printf 'ABSENT'
513
+ }
514
+
515
+ # Snapshot, ONCE per session+repo (first touch = PreToolUse, before the edit applies),
516
+ # the pre-session state so Stop can tell what THIS session changed. Writes: .basehead
517
+ # (HEAD SHA, or UNBORN); .baseglobs / .baseexcl (product/exclude globs at capture time,
518
+ # the authoritative classification — used at Stop alongside the current globs so a
519
+ # later product-shrink OR exclude-widen can't hide a gated path); .basepaths (NUL pairs
520
+ # rel\0hash\0 of gated paths ALREADY changed-vs-HEAD before the session, WITH their
521
+ # content hash — so a pre-existing dirty file is excused only while its content is
522
+ # unchanged; editing it further changes the hash and is caught). .basehead is written
523
+ # LAST and is the idempotency guard.
524
+ capture_baseline() { # $1 root
525
+ local sd sidf; sd=$(state_dir "$1") || return 0; sidf=$(sid_safe "$SID")
526
+ [ -e "$sd/s.$sidf.basehead" ] && return 0
527
+ local head; head=$(git -C "$1" rev-parse HEAD 2>/dev/null || printf 'UNBORN')
528
+ load_globs "$1"; BASE_PRODUCT=(); BASE_EXCLUDE=() # baseline classification == current here
529
+ # Write the data files FIRST; .basehead is the success-guard written LAST, so a
530
+ # partial write (disk full, RO fs) leaves no .basehead => the next touch re-captures
531
+ # and meanwhile session_gated_changed falls back to gated_dirty (never a stale baseline).
532
+ { local g; for g in ${PRODUCT_GLOBS[@]+"${PRODUCT_GLOBS[@]}"}; do [ -n "$g" ] && printf '%s\0' "$g"; done; } \
533
+ | safe_put_stdin "$sd/s.$sidf.baseglobs" || return 0
534
+ { local x; for x in ${EXCLUDE_GLOBS[@]+"${EXCLUDE_GLOBS[@]}"}; do [ -n "$x" ] && printf '%s\0' "$x"; done; } \
535
+ | safe_put_stdin "$sd/s.$sidf.baseexcl" || return 0
536
+ { local rel; while IFS= read -r -d '' rel; do
537
+ [ -n "$rel" ] && printf '%s\0%s\0' "$rel" "$(path_hash "$1" "$rel")"
538
+ done < <(emit_changed_z "$1" "$head"); } | safe_put_stdin "$sd/s.$sidf.basepaths" || return 0
539
+ safe_put "$sd/s.$sidf.basehead" "$head"
540
+ }
541
+
542
+ # Load BASE_PRODUCT / BASE_EXCLUDE from the captured baseline glob files.
543
+ load_base_globs() { # $1 baseglobs-file $2 baseexcl-file
544
+ BASE_PRODUCT=(); BASE_EXCLUDE=(); local g
545
+ [ -r "$1" ] && while IFS= read -r -d '' g; do [ -n "$g" ] && BASE_PRODUCT+=("$g"); done < "$1"
546
+ [ -r "$2" ] && while IFS= read -r -d '' g; do [ -n "$g" ] && BASE_EXCLUDE+=("$g"); done < "$2"
547
+ }
548
+
549
+ # True (0) iff <rel> is gated under the CURRENT config OR the BASELINE config (gated ==
550
+ # matches product AND not matches exclude, evaluated per-config) — so neither a
551
+ # product-shrink nor an exclude-widen after the first touch can de-classify it.
552
+ gated_either() { # $1 rel (empty-array-safe expansions for bash 3.2 `set -u`)
553
+ local rel="$1"
554
+ path_matches_any "$rel" ${EXCLUDE_GLOBS[@]+"${EXCLUDE_GLOBS[@]}"} \
555
+ || { path_matches_any "$rel" ${PRODUCT_GLOBS[@]+"${PRODUCT_GLOBS[@]}"} && return 0; }
556
+ path_matches_any "$rel" ${BASE_EXCLUDE[@]+"${BASE_EXCLUDE[@]}"} \
557
+ || { path_matches_any "$rel" ${BASE_PRODUCT[@]+"${BASE_PRODUCT[@]}"} && return 0; }
558
+ return 1
559
+ }
560
+
561
+ # Emit (NUL) every path that differs from <head> in the current tree (committed-since-
562
+ # head OR staged OR unstaged — `git diff <head>` compares head to the WORKING TREE, so
563
+ # a committed change still shows) plus untracked files, filtered to gated_either. Caller
564
+ # must have loaded current + baseline globs. head=UNBORN => diff the empty tree. Inner
565
+ # `| while` only printfs (no `return`), so the subshell is harmless.
566
+ emit_changed_z() { # $1 root $2 head
567
+ local root="$1" head="$2" rel diffref="$2"
568
+ [ "$head" = "UNBORN" ] && diffref=$(git -C "$root" hash-object -t tree /dev/null 2>/dev/null)
569
+ { [ -n "$diffref" ] && git -C "$root" diff --name-only --no-renames -z "$diffref" 2>/dev/null
570
+ git -C "$root" ls-files --others --exclude-standard -z 2>/dev/null; } \
571
+ | while IFS= read -r -d '' rel; do
572
+ [ -n "$rel" ] && gated_either "$rel" && printf '%s\0' "$rel"
573
+ done
574
+ }
575
+
576
+ # True (0) iff <rel> is recorded in baseline-paths AND its current content hash matches
577
+ # the recorded one (i.e. a pre-existing change untouched this session). A re-edit changes
578
+ # the hash => not unchanged => the caller blocks. Not recorded => not unchanged.
579
+ baseline_unchanged() { # $1 basepaths-file $2 root $3 rel
580
+ [ -r "$1" ] || return 1
581
+ local p h cur; cur=$(path_hash "$2" "$3")
582
+ while IFS= read -r -d '' p && IFS= read -r -d '' h; do
583
+ [ "$p" = "$3" ] && { [ "$h" = "$cur" ] && return 0; return 1; }
584
+ done < "$1"
585
+ return 1
586
+ }
587
+
588
+ # True (0) iff this session changed a gated path that was NOT already changed (same
589
+ # content) at the baseline — i.e. real new gated work, committed or in the worktree.
590
+ # Read-only, rejected, reverted/stashed-to-baseline, and untouched-pre-existing-dirty
591
+ # sessions yield no such path => return 1 (allow). No baseline (state write failed) =>
592
+ # conservative fallback to gated_dirty. `< <(...)` keeps the loop in this shell so
593
+ # `return` exits the function.
594
+ session_gated_changed() { # $1 root $2 state-dir $3 sidf
595
+ local root="$1" sd="$2" sidf="$3" head rel
596
+ [ -r "$sd/s.$sidf.basehead" ] || { gated_dirty "$root"; return; }
597
+ head=$(cat "$sd/s.$sidf.basehead" 2>/dev/null)
598
+ load_globs "$root"
599
+ load_base_globs "$sd/s.$sidf.baseglobs" "$sd/s.$sidf.baseexcl"
600
+ while IFS= read -r -d '' rel; do
601
+ [ -n "$rel" ] || continue
602
+ baseline_unchanged "$sd/s.$sidf.basepaths" "$root" "$rel" && continue
603
+ return 0
604
+ done < <(emit_changed_z "$root" "$head")
605
+ return 1
606
+ }
607
+
608
+ # ACTOR-PRECISE variant for SubagentStop: true (0) iff a gated path THIS subagent
609
+ # actually touched (its per-agent .touched list of Edit/Write paths) is among the
610
+ # session's newly-changed gated paths. This is the real isolation — session_gated_changed
611
+ # alone would blame a subagent that merely ATTEMPTED a gated edit for a SIBLING's change.
612
+ # A subagent whose only activity was a Bash write (no .touched paths, sentinel only) is
613
+ # NOT path-attributable, so it is NOT blocked here (the Bash heuristic is advisory by
614
+ # design; the session Stop + `ci` remain the backstop for those). No per-agent touched
615
+ # file => nothing to attribute => allow. No baseline (state write failed) => CANNOT
616
+ # attribute precisely, so ALLOW (return 1) rather than falling back to the session-wide
617
+ # check, which would re-introduce the sibling-blame this function exists to prevent.
618
+ # The touched list is NUL-delimited (paths may contain newlines/spaces) and membership is
619
+ # tested by exact byte-equality, so no substring/line-fragment can cross-attribute.
620
+ agent_touched_has() { # $1 touched-file(NUL-delimited) $2 rel -> 0 iff exact member
621
+ local p
622
+ [ -r "$1" ] || return 1
623
+ while IFS= read -r -d '' p; do [ "$p" = "$2" ] && return 0; done < "$1"
624
+ return 1
625
+ }
626
+ agent_gated_changed() { # $1 root $2 state-dir $3 sidf $4 per-agent-touched-file
627
+ local root="$1" sd="$2" sidf="$3" tf="$4" head rel
628
+ [ -r "$sd/s.$sidf.basehead" ] || return 1 # no baseline => not attributable => allow
629
+ [ -r "$tf" ] || return 1 # only a .bashwrite sentinel (or nothing) => not attributable
630
+ head=$(cat "$sd/s.$sidf.basehead" 2>/dev/null)
631
+ load_globs "$root"
632
+ load_base_globs "$sd/s.$sidf.baseglobs" "$sd/s.$sidf.baseexcl"
633
+ while IFS= read -r -d '' rel; do
634
+ [ -n "$rel" ] || continue
635
+ baseline_unchanged "$sd/s.$sidf.basepaths" "$root" "$rel" && continue
636
+ # rel is a newly-changed gated path this session — attribute it only if THIS
637
+ # subagent touched that EXACT path (NUL-safe byte-equality, not line/substring match).
638
+ agent_touched_has "$tf" "$rel" && return 0
639
+ done < <(emit_changed_z "$root" "$head")
640
+ return 1
641
+ }
642
+
643
+ # Fallback only (used when no baseline was captured): block iff a gated path is dirty
644
+ # in the current tree, using current globs.
645
+ gated_dirty() { # $1 root
646
+ local root="$1" rel
647
+ load_globs "$root"
648
+ [ "${#PRODUCT_GLOBS[@]}" -gt 0 ] || return 1
649
+ while IFS= read -r -d '' rel; do
650
+ [ -n "$rel" ] || continue
651
+ path_matches_any "$rel" "${EXCLUDE_GLOBS[@]}" && continue
652
+ path_matches_any "$rel" "${PRODUCT_GLOBS[@]}" && return 0
653
+ done < <(git -C "$root" diff --name-only --no-renames -z HEAD 2>/dev/null; \
654
+ git -C "$root" diff --name-only --no-renames -z --cached 2>/dev/null; \
655
+ git -C "$root" ls-files --others --exclude-standard -z 2>/dev/null)
656
+ return 1
657
+ }
658
+
659
+ # =============================================================================
660
+ cmd_pretool() {
661
+ have jq || allow_pretool # can't parse stdin / determine opt-in => silent fail-open
662
+ local input tool fp cmd repo rel
663
+ input=$(cat)
664
+ SID=$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)
665
+ AID=$(printf '%s' "$input" | jq -r '.agent_id // empty' 2>/dev/null) # subagent => per-agent markers
666
+ AIDKEY=$(printf '%s' "$input" | aid_key_from) # injective key (jq, raw JSON)
667
+ tool=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)
668
+
669
+ case "$tool" in
670
+ Edit|Write|MultiEdit|NotebookEdit)
671
+ fp=$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.notebook_path // .tool_input.path // empty' 2>/dev/null)
672
+ [ -n "$fp" ] || allow_pretool
673
+ if [ -e "$fp" ]; then
674
+ local _r; _r=$(realpath "$fp" 2>/dev/null) && [ -n "$_r" ] && fp="$_r"
675
+ else
676
+ local _d; _d=$(realpath "$(dirname -- "$fp")" 2>/dev/null) && [ -n "$_d" ] && fp="$_d/$(basename -- "$fp")"
677
+ fi
678
+ repo=$(opt_in_root "$(dirname -- "$fp")") || allow_pretool # cheap walk, git only if candidate
679
+ [ "$(cfg_enabled "$repo")" = "enabled" ] || allow_pretool
680
+ rel=${fp#"$repo"/}
681
+ is_gated_path "$repo" "$rel" || allow_pretool
682
+ local strict decision hint sidf sdv
683
+ sidf=$(sid_safe "$SID"); sdv=$(state_dir "$repo" 2>/dev/null)
684
+ # Capture the activity marker + baseline for EVERY gated edit — even under a valid
685
+ # boundary — so the Stop invocation-evidence check can run on the proactive-record
686
+ # path (record owners BEFORE any edit). Without this, a boundary short-circuit here
687
+ # left no marker and Stop silently allowed, fully bypassing the name!=invoke check.
688
+ note_touched "$repo" "$rel"
689
+ # A valid boundary satisfies the PRETOOL gate (allow the edit through); Stop still
690
+ # verifies actual-gated-change + invocation evidence from the marker just captured.
691
+ boundary_valid "$repo" >/dev/null && allow_pretool
692
+ # Explicit per-session exemption (the escape hatch, esp. under strict): if the
693
+ # agent recorded a waiver for a genuinely narrow exempt edit, allow.
694
+ [ -n "$sdv" ] && [ -e "$sdv/s.$sidf.waiver" ] && allow_pretool
695
+ strict=$(cfg_get "$repo" '.strict' 'false')
696
+ hint=$(cfg_get "$repo" '.owner_hint' 'the owning product-rd / stack skill')
697
+ decision="ask"
698
+ # strict hard-deny ONLY if we can actually persist state (else the agent could
699
+ # never unblock => brick); otherwise downgrade to ask.
700
+ [ "$strict" = "true" ] && [ -n "$sdv" ] && decision="deny"
701
+ if [ "$decision" = "deny" ]; then
702
+ emit_pretool_deny "owner-dispatch (strict): blocked edit of gated product code ($rel) in [$repo] — no owner-dispatch boundary this session. INVOKE (load) ${hint} for each touched concern, build the owner-dispatch map, then: bash $SELF record --owners \"<owner1,owner2>\". If this is a genuinely narrow exempt edit (single bug/test/doc/config), record an exemption to proceed: touch \"$sdv/s.$sidf.waiver\" (only after classifying this as a genuinely narrow exempt edit; otherwise invoke + record). (naming an owner is not invoking it.) Clearing THIS owner-dispatch boundary is yours, agent — invoke the owners then run record; don't punt it to the user as an approval. It unblocks only this gate: merge/push/destructive/scope/product decisions still require user authorization." "deny"
703
+ else
704
+ emit_pretool_deny "owner-dispatch: about to edit gated product code ($rel) in [$repo] with no owner-dispatch boundary this session. Per the product-rd owner-dispatch firing gate, INVOKE (load) ${hint} for each touched concern and build the owner-dispatch map BEFORE producing substance — naming an owner is not invoking it. Then record it: bash $SELF record --owners \"<owner1,owner2>\". (Narrow self-contained bug/test/doc/config edits are exempt — proceed if so.) Agent: clearing this is yours — invoke the owners + record; don't punt it to the user as an approval. It clears only this owner-dispatch gate; merge/push/destructive/scope/product decisions still require user authorization." "ask"
705
+ fi
706
+ ;;
707
+ Bash)
708
+ cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
709
+ [ -n "$cmd" ] || allow_pretool
710
+ repo=$(opt_in_root "$PWD") || allow_pretool # cheap walk, git only if candidate
711
+ [ "$(cfg_enabled "$repo")" = "enabled" ] || allow_pretool
712
+ [ "$(cfg_get "$repo" '.gate_bash' 'true')" = "true" ] || allow_pretool
713
+ # Best-effort write-verb detection (advisory only — many forms slip through).
714
+ printf '%s' "$cmd" | grep -Eq '(>>?|>\||[[:space:]]tee[[:space:]]|sed -i|perl -i|[[:space:]]ed[[:space:]]|[[:space:]]dd[[:space:]]|[[:space:]]cp[[:space:]]|[[:space:]]mv[[:space:]]|[[:space:]]install[[:space:]]|[[:space:]]rsync[[:space:]]|git (apply|restore|checkout)|[[:space:]]patch[[:space:]]|python[0-9.]* -c|<<)' || allow_pretool
715
+ load_globs "$repo"
716
+ [ "${#PRODUCT_GLOBS[@]}" -gt 0 ] || allow_pretool # empty/malformed globs => clean fail-open
717
+ local matched="" g stem
718
+ for g in "${PRODUCT_GLOBS[@]}"; do
719
+ stem="${g%%/\**}"; stem="${stem%%\**}"
720
+ [ -n "$stem" ] && printf '%s' "$cmd" | grep -Fq "$stem" && { matched=1; break; }
721
+ done
722
+ [ -n "$matched" ] || allow_pretool
723
+ boundary_valid "$repo" >/dev/null && allow_pretool
724
+ # RECORD-ONLY: the Bash write-verb heuristic over-matches read-only commands
725
+ # (`python -c`, heredocs, `>/dev/null`) and can never hard-deny anyway, so raising
726
+ # a user `ask` popup here is pure false-positive noise that trains users to disable
727
+ # the gate. Drop the activity marker for the Stop backstop and ALLOW SILENTLY (emit
728
+ # nothing). Real gated work is caught by the evidence-based Stop hook + `ci`; a real
729
+ # Edit/Write to a gated file still prompts the agent via the Edit branch above.
730
+ note_bashwrite "$repo"
731
+ allow_pretool
732
+ ;;
733
+ *) allow_pretool ;;
734
+ esac
735
+ }
736
+
737
+ # =============================================================================
738
+ cmd_stop() {
739
+ have jq || allow_stop # silent fail-open
740
+ # Handles BOTH the main Stop and SubagentStop (same wrapper; agent_id present only for
741
+ # the latter). NOTE: only inspects the repo under the current PWD; a gated edit made in
742
+ # a DIFFERENT repo during the same session is not re-checked here (documented limit).
743
+ local input repo sd sidf aidf mpfx capkey actor
744
+ input=$(cat)
745
+ SID=$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null)
746
+ # No reliable session id => cannot scope per-session => fail open (never risk a trap).
747
+ [ -n "$SID" ] && [ "$SID" != "nosession" ] || allow_stop
748
+ # agent_id is populated only on SubagentStop. When present, scope the activity marker,
749
+ # block-once cap, and waiver to THIS subagent so (a) a read-only sibling/main thread's
750
+ # dirty gated work never blocks an unrelated subagent, and (b) the subagent's once-cap
751
+ # is independent of the main Stop's. The session-level baseline (capture_baseline keys
752
+ # on SID) is shared and correct for everyone — only the per-actor markers differ.
753
+ AID=$(printf '%s' "$input" | jq -r '.agent_id // empty' 2>/dev/null)
754
+ AIDKEY=$(printf '%s' "$input" | aid_key_from) # injective key (jq, raw JSON) — see globals
755
+ local transcript agent_transcript empty_is_missing="false"
756
+ if [ -n "$AID" ]; then
757
+ # Claude's SubagentStop carries BOTH paths: transcript_path is the controller/main
758
+ # session; agent_transcript_path is this worker. Only the latter can prove that the
759
+ # worker loaded its required skills. Older hosts may omit it; their main-transcript
760
+ # fallback retains legacy empty-set fail-open because it is not worker evidence.
761
+ agent_transcript=$(printf '%s' "$input" | jq -r '.agent_transcript_path // empty' 2>/dev/null)
762
+ if [ -n "$agent_transcript" ]; then
763
+ transcript="$agent_transcript"
764
+ empty_is_missing="true"
765
+ else
766
+ transcript=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
767
+ fi
768
+ else
769
+ transcript=$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)
770
+ fi
771
+ repo=$(opt_in_root "$PWD") || allow_stop
772
+ [ "$(cfg_enabled "$repo")" = "enabled" ] || allow_stop
773
+ sd=$(state_dir "$repo") || allow_stop
774
+ sidf=$(sid_safe "$SID")
775
+ if [ -n "$AID" ]; then mpfx="s.$sidf.a.$AIDKEY"; else mpfx="s.$sidf"; fi
776
+ capkey="$mpfx.blocked"
777
+ [ -r "$sd/$mpfx.touched" ] || [ -r "$sd/$mpfx.bashwrite" ] || allow_stop # no activity by this actor
778
+ # Invocation-evidence check (name != invoke): a valid boundary normally satisfies the
779
+ # gate — BUT if it recorded owners that were never actually invoked (loaded) this
780
+ # session (per the transcript), the "record" was a bare name, not a real dispatch.
781
+ # Block ONCE (same cap/waiver safety) to surface it. Fail-open: no transcript / no
782
+ # un-invoked owner / parse gap => the boundary satisfies as before.
783
+ if boundary_valid "$repo" >/dev/null; then
784
+ local bdir bf missing
785
+ bdir=$(boundary_dir "$repo" 2>/dev/null); bf="$bdir/boundary.json"
786
+ missing=$(owners_not_invoked "$bf" "$transcript" "$empty_is_missing" 2>/dev/null)
787
+ if [ -z "$missing" ]; then allow_stop; fi # all recorded owners were invoked (or unverifiable)
788
+ # Same no-trap safety as the boundary-missing block: only block a session/actor that
789
+ # ACTUALLY changed gated code this session (not a reverted edit or a Bash-heuristic
790
+ # over-match). This preserves the prior invariant that a no-change session is never
791
+ # blocked, even though a valid boundary exists.
792
+ if [ -n "$AID" ]; then
793
+ agent_gated_changed "$repo" "$sd" "$sidf" "$sd/$mpfx.touched" || allow_stop
794
+ else
795
+ session_gated_changed "$repo" "$sd" "$sidf" || allow_stop
796
+ fi
797
+ [ -e "$sd/s.$sidf.waiver" ] && allow_stop
798
+ [ -n "$AID" ] && [ -e "$sd/$mpfx.waiver" ] && allow_stop
799
+ [ -e "$sd/$capkey" ] && allow_stop
800
+ safe_put "$sd/$capkey" "1" || allow_stop
801
+ [ -r "$sd/$capkey" ] || allow_stop
802
+ emit_stop_block "owner-dispatch: recorded owner(s) [$missing] in [$repo] but this actor's transcript shows they were never invoked (loaded) — naming an owner is not invoking it. Continue the same actor now: invoke each named owner skill, re-check the work against its rules, repair any miss, and re-record with: bash $SELF record --owners \"<owners-you-actually-invoked>\". If [$missing] is a non-skill reference (not an invocable skill), correct the record and proceed. (Fires at most once per actor per session.) This recovery is the agent's job; do not ask the user to perform it."
803
+ fi
804
+ [ -e "$sd/s.$sidf.waiver" ] && allow_stop # session-level waiver suppresses both
805
+ [ -n "$AID" ] && [ -e "$sd/$mpfx.waiver" ] && allow_stop # per-subagent waiver
806
+ [ -e "$sd/$capkey" ] && allow_stop # already blocked once for this actor
807
+ # Evidence gate: block only if this session actually changed a gated path, judged by
808
+ # diffing the current WORKING TREE against the baseline snapshot taken at the first
809
+ # touch. This blocks real new gated work whether it ended up committed or dirty (the
810
+ # worktree reflects committed content too), while a read-only, rejected, reverted/
811
+ # stashed-to-baseline, or untouched-pre-existing-dirty session produces no NEW gated
812
+ # path and is allowed (no false trap). Design boundary: Stop inspects the WORKTREE, so
813
+ # gated work that lives ONLY in the index/HEAD with a worktree deliberately restored to
814
+ # baseline (`git add` + `git restore --worktree`, or commit + `git checkout <base> --`),
815
+ # or a write the Bash heuristic never recorded (`node -e …`), is out of this nudge's
816
+ # reach — the reliable `ci` subcommand (diffs base..HEAD at commit/merge) is the gate
817
+ # for those. Stop is a fast in-session nudge, not the enforcement boundary.
818
+ # Evidence gate — actor-precise: a subagent blocks only for a gated path IT touched
819
+ # (not a sibling's); the main session uses the session-wide check.
820
+ if [ -n "$AID" ]; then
821
+ agent_gated_changed "$repo" "$sd" "$sidf" "$sd/$mpfx.touched" || allow_stop
822
+ else
823
+ session_gated_changed "$repo" "$sd" "$sidf" || allow_stop
824
+ fi
825
+ # Persist the cap FIRST; if we can't, fail open (never block forever on a RO FS).
826
+ safe_put "$sd/$capkey" "1" || allow_stop
827
+ [ -r "$sd/$capkey" ] || allow_stop
828
+ local hint; hint=$(cfg_get "$repo" '.owner_hint' 'the owning product-rd / stack skill')
829
+ [ -n "$AID" ] && actor="this subagent" || actor="this session"
830
+ # Distinguish "never recorded" from "recorded, then expired": reporting an expired record
831
+ # as absent sends diagnosis after a record that was in fact written.
832
+ local why lead; why=$(boundary_state "$repo")
833
+ case "$why" in
834
+ expired)
835
+ lead="edited gated product code in [$repo], where an owner-dispatch boundary exists but expired before this actor finished (it may have been recorded by an earlier actor; boundaries are time-bounded — re-record to extend)" ;;
836
+ discontinuous)
837
+ lead="edited gated product code in [$repo], where an owner-dispatch boundary exists but was recorded on a different line of history (a branch switch, reset, amend, or rebase moved HEAD off it) — that record covers different work, so re-record for this one" ;;
838
+ *)
839
+ lead="edited gated product code in [$repo] but never recorded an owner-dispatch boundary" ;;
840
+ esac
841
+ emit_stop_block "owner-dispatch: $actor $lead. Before finishing: confirm you invoked ${hint} for each touched concern (not just named it). If you did the work, record it — bash $SELF record --owners \"<owners>\"; if this delivery is genuinely exempt (narrow bug/test/doc), waive — touch \"$sd/$mpfx.waiver\". (Fires at most once per actor per session.) Clearing this is yours, agent — invoke + record; don't punt it to the user as an approval (it clears only this gate, not merge/push/destructive actions)."
842
+ }
843
+
844
+ # =============================================================================
845
+ # Standalone invocation-evidence check (also the Stop hook's mechanism, exposed for
846
+ # testing/CI). Given a comma owner list and a transcript, report which skill-shaped
847
+ # owners were never invoked. Exit 0 = all invoked / unverifiable (fail-open); exit 3
848
+ # = at least one skill-shaped owner was recorded but never invoked.
849
+ cmd_verify_invocations() {
850
+ have jq || { echo "verify-invocations: jq required" >&2; return 0; } # fail-open
851
+ local owners="" transcript="" empty_is_missing="false"
852
+ while [ $# -gt 0 ]; do
853
+ case "$1" in
854
+ --owners) owners="${2:-}"; shift 2 ;;
855
+ --transcript) transcript="${2:-}"; shift 2 ;;
856
+ --empty-is-missing) empty_is_missing="true"; shift ;;
857
+ *) shift ;;
858
+ esac
859
+ done
860
+ [ -n "$owners" ] || { echo "verify-invocations: --owners required" >&2; return 2; }
861
+ [ -n "$transcript" ] && [ -r "$transcript" ] || { echo "verify-invocations: transcript unreadable => unverifiable (fail-open)"; return 0; }
862
+ # Reuse owners_not_invoked by synthesizing a minimal boundary json.
863
+ local tmp; tmp=$(mktemp "${TMPDIR:-/tmp}/od-vi.XXXXXX") || return 0
864
+ jq -nc --arg o "$owners" '{touched_owners:($o|split(",")|map(gsub("^\\s+|\\s+$";"")))}' > "$tmp" 2>/dev/null
865
+ local missing; missing=$(owners_not_invoked "$tmp" "$transcript" "$empty_is_missing" 2>/dev/null)
866
+ rm -f "$tmp"
867
+ if [ -n "$missing" ]; then
868
+ echo "verify-invocations: recorded but NEVER invoked: $missing"
869
+ return 3
870
+ fi
871
+ echo "verify-invocations: all skill-shaped owners were invoked (or unverifiable)"
872
+ return 0
873
+ }
874
+
875
+ cmd_record() {
876
+ have jq && have git || { echo "owner-dispatch record: jq+git required" >&2; return 1; }
877
+ local owners="" artifact="" ttl=28800
878
+ while [ $# -gt 0 ]; do
879
+ case "$1" in
880
+ --owners) owners="${2:-}"; shift 2 ;;
881
+ --artifact) artifact="${2:-}"; shift 2 ;;
882
+ --ttl) ttl="${2:-28800}"; shift 2 ;;
883
+ *) shift ;;
884
+ esac
885
+ done
886
+ [ -n "$owners" ] || { echo "owner-dispatch record: --owners \"a,b\" required (the owner skills you actually invoked)" >&2; return 2; }
887
+ local repo; repo=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "owner-dispatch record: not in a git repo" >&2; return 2; }
888
+ local sd; sd=$(state_dir "$repo") || { echo "owner-dispatch record: state dir unavailable/unsafe" >&2; return 1; }
889
+ local now exp head json gdir
890
+ now=$(date +%s); exp=$((now + ttl)); head=$(current_head "$repo")
891
+ # Repository identity for validation. `repo_root` stays for diagnostics (it says WHICH
892
+ # worktree recorded), but it must not be the key: it differs per worktree and vanishes
893
+ # when that worktree is cleaned up after merge.
894
+ gdir=$(boundary_dir "$repo") || { echo "owner-dispatch record: cannot resolve git dir" >&2; return 1; }
895
+ json=$(jq -nc --arg repo "$repo" --arg head "$head" --arg owners "$owners" \
896
+ --arg artifact "$artifact" --argjson created "$now" --argjson expires "$exp" \
897
+ --arg gdir "$gdir" \
898
+ --arg sid "${CLAUDE_SESSION_ID:-${OWNER_DISPATCH_SESSION:-unknown}}" \
899
+ '{repo_root:$repo,repo_git_dir:$gdir,head:$head,touched_owners:($owners|split(",")|map(gsub("^\\s+|\\s+$";""))),artifact:$artifact,created_at:$created,expires_at:$expires,session_id:$sid}' 2>/dev/null) \
900
+ || { echo "owner-dispatch record: encode failed" >&2; return 1; }
901
+ safe_put "$sd/boundary.json" "$json" || { echo "owner-dispatch record: write failed" >&2; return 1; }
902
+ # No cleanup of s.*.touched/blocked: a valid boundary supersedes them (boundary_valid
903
+ # short-circuits before those markers are consulted), so stale per-session markers are
904
+ # inert — and NOT globbing-deleting avoids any raced-symlink delete risk.
905
+ echo "owner-dispatch: boundary recorded for [$repo] @ ${head:0:8} (owners: $owners; expires in $((ttl/3600))h)"
906
+ }
907
+
908
+ # Load product/exclude globs from a config CONTENT string (used for the BASE config).
909
+ load_globs_from() { # $1 json-string -> sets ARG_PRODUCT_GLOBS / ARG_EXCLUDE_GLOBS
910
+ ARG_PRODUCT_GLOBS=(); ARG_EXCLUDE_GLOBS=()
911
+ local line
912
+ while IFS= read -r line; do [ -n "$line" ] && ARG_PRODUCT_GLOBS+=("$line"); done < <(printf '%s' "$1" | jq -r '(.product_globs // [])[]' 2>/dev/null)
913
+ while IFS= read -r line; do [ -n "$line" ] && ARG_EXCLUDE_GLOBS+=("$line"); done < <(printf '%s' "$1" | jq -r '(.exclude_globs // [])[]' 2>/dev/null)
914
+ ARG_EXCLUDE_GLOBS+=("**/*.md" ".owner-dispatch.json" ".git/**")
915
+ }
916
+ # Fail-closed schema check for an ENABLED config: an enabled-but-unusable config
917
+ # (product_globs missing / empty / not a string array) would silently gate nothing.
918
+ cfg_schema_ok() { # $1 config-json -> 0 if a valid enabled config
919
+ printf '%s' "$1" | jq -e '
920
+ (.enabled==true)
921
+ and ((.product_globs|type)=="array") and ((.product_globs|length)>0)
922
+ and (all(.product_globs[]; (type=="string") and (length>0)))
923
+ and (((.exclude_globs)//[])|type=="array")
924
+ and (((.ci_artifact)//"x")|type=="string")
925
+ and (((.strict)//false)|type=="boolean")
926
+ and (((.gate_bash)//true)|type=="boolean")
927
+ ' >/dev/null 2>&1
928
+ }
929
+
930
+ cmd_ci_python() {
931
+ python3 - "$@" <<'PY'
932
+ import fnmatch
933
+ import json
934
+ import os
935
+ import subprocess
936
+ import sys
937
+
938
+
939
+ def fail(message, code=2):
940
+ print(message, file=sys.stderr)
941
+ return code
942
+
943
+
944
+ def run_git(args, cwd=None, text=True, check=False):
945
+ result = subprocess.run(
946
+ ["git", *args],
947
+ cwd=cwd,
948
+ check=False,
949
+ stdout=subprocess.PIPE,
950
+ stderr=subprocess.DEVNULL,
951
+ text=text,
952
+ )
953
+ if check and result.returncode != 0:
954
+ raise RuntimeError(args)
955
+ return result
956
+
957
+
958
+ def load_json_text(text):
959
+ if not text:
960
+ return None
961
+ return json.loads(text)
962
+
963
+
964
+ def load_json_file(path):
965
+ with open(path, "r", encoding="utf-8") as f:
966
+ return json.load(f)
967
+
968
+
969
+ def enabled(cfg):
970
+ return bool(isinstance(cfg, dict) and cfg.get("enabled") is True)
971
+
972
+
973
+ def glob_list(cfg, key):
974
+ if not isinstance(cfg, dict):
975
+ return []
976
+ value = cfg.get(key) or []
977
+ return value if isinstance(value, list) else []
978
+
979
+
980
+ def path_matches_any(rel, globs):
981
+ for glob in globs:
982
+ if not glob:
983
+ continue
984
+ # fnmatch's '*' crosses '/', matching bash `case` semantics used above.
985
+ if fnmatch.fnmatchcase(rel, glob):
986
+ return True
987
+ if glob.startswith("**/") and fnmatch.fnmatchcase(rel, glob[3:]):
988
+ return True
989
+ return False
990
+
991
+
992
+ def load_globs(cfg):
993
+ product = [g for g in glob_list(cfg, "product_globs") if isinstance(g, str) and g]
994
+ exclude = [g for g in glob_list(cfg, "exclude_globs") if isinstance(g, str) and g]
995
+ exclude.extend(["**/*.md", ".owner-dispatch.json", ".git/**"])
996
+ return product, exclude
997
+
998
+
999
+ def is_gated_path(rel, cfg):
1000
+ product, exclude = load_globs(cfg)
1001
+ if not product:
1002
+ return False
1003
+ if path_matches_any(rel, exclude):
1004
+ return False
1005
+ return path_matches_any(rel, product)
1006
+
1007
+
1008
+ def schema_ok(cfg):
1009
+ if not isinstance(cfg, dict) or cfg.get("enabled") is not True:
1010
+ return False
1011
+ product = cfg.get("product_globs")
1012
+ exclude = cfg.get("exclude_globs", [])
1013
+ ci_artifact = cfg.get("ci_artifact", "x")
1014
+ return (
1015
+ isinstance(product, list)
1016
+ and len(product) > 0
1017
+ and all(isinstance(x, str) and len(x) > 0 for x in product)
1018
+ and isinstance(exclude, list)
1019
+ and (ci_artifact is None or isinstance(ci_artifact, str))
1020
+ and isinstance(cfg.get("strict", False), bool)
1021
+ and isinstance(cfg.get("gate_bash", True), bool)
1022
+ )
1023
+
1024
+
1025
+ def main(argv):
1026
+ base = ""
1027
+ if len(argv) >= 2 and argv[0] == "--base":
1028
+ base = argv[1]
1029
+
1030
+ top = run_git(["rev-parse", "--show-toplevel"], check=False)
1031
+ if top.returncode != 0:
1032
+ return fail("owner-dispatch ci: not a git repo — FAIL-CLOSED (2)")
1033
+ repo = top.stdout.strip()
1034
+
1035
+ if not base:
1036
+ default_branch = os.environ.get("CI_DEFAULT_BRANCH") or "origin/main"
1037
+ mb = run_git(["merge-base", "HEAD", default_branch], cwd=repo, check=False)
1038
+ base = mb.stdout.strip()
1039
+ if not base:
1040
+ return fail("owner-dispatch ci: could not determine a diff base — pass --base <sha>. FAIL-CLOSED (2).")
1041
+ if run_git(["rev-parse", "--verify", f"{base}^{{commit}}"], cwd=repo).returncode != 0:
1042
+ return fail(f"owner-dispatch ci: invalid base '{base}' — FAIL-CLOSED (2)")
1043
+
1044
+ head_cfg = None
1045
+ head_cfg_path = os.path.join(repo, ".owner-dispatch.json")
1046
+ if os.path.exists(head_cfg_path):
1047
+ try:
1048
+ head_cfg = load_json_file(head_cfg_path)
1049
+ except (OSError, json.JSONDecodeError):
1050
+ head_cfg = None
1051
+
1052
+ base_show = run_git(["show", f"{base}:.owner-dispatch.json"], cwd=repo)
1053
+ base_cfg_text = base_show.stdout if base_show.returncode == 0 else ""
1054
+ try:
1055
+ base_cfg = load_json_text(base_cfg_text)
1056
+ except json.JSONDecodeError:
1057
+ base_cfg = None
1058
+
1059
+ head_enabled = enabled(head_cfg)
1060
+ base_enabled = enabled(base_cfg)
1061
+
1062
+ diff = run_git(["diff", "-z", "--name-only", base, "HEAD"], cwd=repo, text=False)
1063
+ if diff.returncode != 0:
1064
+ return fail("owner-dispatch ci: diff failed — FAIL-CLOSED (2)")
1065
+ changed = [p.decode("utf-8", "surrogateescape") for p in diff.stdout.split(b"\0") if p]
1066
+
1067
+ base_product, base_exclude = load_globs(base_cfg)
1068
+ gated = []
1069
+ for rel in changed:
1070
+ if is_gated_path(rel, head_cfg):
1071
+ gated.append(rel)
1072
+ continue
1073
+ if base_product and not path_matches_any(rel, base_exclude) and path_matches_any(rel, base_product):
1074
+ gated.append(rel)
1075
+
1076
+ gated_disp = "".join(f" {p}" for p in gated)
1077
+
1078
+ tracked = run_git(["ls-files", "--error-unmatch", ".owner-dispatch.json"], cwd=repo).returncode == 0
1079
+ if tracked and not isinstance(head_cfg, dict):
1080
+ return fail("owner-dispatch ci: FAIL-CLOSED (2) — tracked .owner-dispatch.json is malformed or non-object JSON.")
1081
+
1082
+ if base_enabled and not head_enabled:
1083
+ if gated:
1084
+ print(
1085
+ f"owner-dispatch ci: FAIL (1) — this change disables/removes the gate AND touches gated product code:{gated_disp}",
1086
+ file=sys.stderr,
1087
+ )
1088
+ print(" Decommission the gate in a separate config-only change, or keep it enabled.", file=sys.stderr)
1089
+ return 1
1090
+ print("owner-dispatch ci: gate disabled/removed in a config-only change — allowed (clean decommit)")
1091
+ return 0
1092
+
1093
+ if not head_enabled:
1094
+ print("owner-dispatch ci: not opted in — skip")
1095
+ return 0
1096
+
1097
+ if not schema_ok(head_cfg):
1098
+ return fail(
1099
+ "owner-dispatch ci: FAIL-CLOSED (2) — enabled .owner-dispatch.json is schema-invalid "
1100
+ "(product_globs must be a non-empty array of non-empty strings; strict/gate_bash boolean; ci_artifact a string)."
1101
+ )
1102
+
1103
+ if not gated:
1104
+ print("owner-dispatch ci: no gated product files changed — ok")
1105
+ return 0
1106
+
1107
+ artifact_value = head_cfg.get("ci_artifact", "design/owner-dispatch-map.md")
1108
+ if artifact_value is None:
1109
+ artifact_value = "design/owner-dispatch-map.md"
1110
+ artifact = str(artifact_value)
1111
+ if artifact.startswith("./"):
1112
+ artifact = artifact[2:]
1113
+ if artifact.startswith("/") or ".." in artifact.split("/") or artifact == "":
1114
+ print(
1115
+ f"owner-dispatch ci: FAIL (1) — unsafe ci_artifact path '{artifact}' (must be repo-relative, no '..' path component).",
1116
+ file=sys.stderr,
1117
+ )
1118
+ return 1
1119
+ if artifact == ".owner-dispatch.json":
1120
+ print("owner-dispatch ci: FAIL (1) — ci_artifact must not be the config file.", file=sys.stderr)
1121
+ return 1
1122
+
1123
+ head_product, _ = load_globs(head_cfg)
1124
+ if head_product and path_matches_any(artifact, head_product):
1125
+ print(
1126
+ f"owner-dispatch ci: FAIL (1) — ci_artifact '{artifact}' matches product_globs; the map must be a dedicated non-product file.",
1127
+ file=sys.stderr,
1128
+ )
1129
+ return 1
1130
+ if artifact in gated:
1131
+ print(
1132
+ f"owner-dispatch ci: FAIL (1) — ci_artifact '{artifact}' is itself a gated changed file; use a dedicated map.",
1133
+ file=sys.stderr,
1134
+ )
1135
+ return 1
1136
+
1137
+ artifact_path = os.path.join(repo, artifact)
1138
+ if not os.path.exists(artifact_path) or os.path.getsize(artifact_path) <= 0:
1139
+ print(
1140
+ f"owner-dispatch ci: FAIL (1) — gated product files changed:{gated_disp} but no owner-dispatch map at '{artifact}'.",
1141
+ file=sys.stderr,
1142
+ )
1143
+ return 1
1144
+ if artifact not in changed:
1145
+ print(
1146
+ f"owner-dispatch ci: FAIL (1) — '{artifact}' exists but was not updated in this change (stale) for gated files:{gated_disp}",
1147
+ file=sys.stderr,
1148
+ )
1149
+ return 1
1150
+
1151
+ print(f"owner-dispatch ci: gated files changed and '{artifact}' updated in this change — ok")
1152
+ return 0
1153
+
1154
+
1155
+ if __name__ == "__main__":
1156
+ sys.exit(main(sys.argv[1:]))
1157
+ PY
1158
+ }
1159
+
1160
+ # =============================================================================
1161
+ # Host-agnostic BACKSTOP for product repos. Unlike the in-session hooks (which fail
1162
+ # OPEN so a bug never bricks editing), CI fails CLOSED: any inability to verify is a
1163
+ # failure, because this is the gate of last resort. Exit: 0 ok / not-opted / clean
1164
+ # decommit; 1 violation; 2 could-not-verify (a gating job must treat 2 as failure).
1165
+ cmd_ci() {
1166
+ have git || { echo "owner-dispatch ci: git required to verify — FAIL-CLOSED (2)" >&2; return 2; }
1167
+ if ! have jq; then
1168
+ have python3 || { echo "owner-dispatch ci: jq missing and python3 fallback unavailable — FAIL-CLOSED (2)" >&2; return 2; }
1169
+ cmd_ci_python "$@"; return $?
1170
+ fi
1171
+ local base=""; [ "${1:-}" = "--base" ] && base="${2:-}"
1172
+ local repo; repo=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "owner-dispatch ci: not a git repo — FAIL-CLOSED (2)" >&2; return 2; }
1173
+ [ -n "$base" ] || base=$(git merge-base HEAD "${CI_DEFAULT_BRANCH:-origin/main}" 2>/dev/null)
1174
+ [ -n "$base" ] || { echo "owner-dispatch ci: could not determine a diff base — pass --base <sha>. FAIL-CLOSED (2)." >&2; return 2; }
1175
+ git rev-parse --verify "$base^{commit}" >/dev/null 2>&1 || { echo "owner-dispatch ci: invalid base '$base' — FAIL-CLOSED (2)" >&2; return 2; }
1176
+
1177
+ local head_enabled base_cfg base_enabled
1178
+ head_enabled=$([ "$(cfg_enabled "$repo")" = "enabled" ] && echo 1 || echo 0)
1179
+ base_cfg=$(git show "$base:.owner-dispatch.json" 2>/dev/null)
1180
+ base_enabled=$(printf '%s' "$base_cfg" | jq -er 'if (.enabled // false) then 1 else 0 end' 2>/dev/null || echo 0)
1181
+ # Direct two-tree diff against the EXPLICIT base (NOT three-dot, which recomputes a
1182
+ # merge-base and breaks on shallow/disconnected CI clones). NUL-delimited via a temp
1183
+ # file (NUL can't survive a shell var) so paths with spaces/newlines classify exactly.
1184
+ local CHANGED=() difftmp rel
1185
+ difftmp=$(mktemp 2>/dev/null) || { echo "owner-dispatch ci: mktemp failed — FAIL-CLOSED (2)" >&2; return 2; }
1186
+ git diff -z --name-only "$base" HEAD > "$difftmp" 2>/dev/null || { rm -f "$difftmp"; echo "owner-dispatch ci: diff failed — FAIL-CLOSED (2)" >&2; return 2; }
1187
+ while IFS= read -r -d '' rel; do CHANGED+=("$rel"); done < "$difftmp"
1188
+ rm -f "$difftmp"
1189
+
1190
+ # Classify changed files against the UNION of base-config and head-config product
1191
+ # globs (base = the contract in force before this change), so an MR cannot dodge the
1192
+ # gate by shrinking product_globs / widening excludes in the same change.
1193
+ load_globs_from "$base_cfg"
1194
+ # bash-3.2-safe array copies (expanding an empty array under `set -u` errors on 3.2).
1195
+ local base_product=() base_exclude=() GATED=()
1196
+ [ "${#ARG_PRODUCT_GLOBS[@]}" -gt 0 ] && base_product=("${ARG_PRODUCT_GLOBS[@]}")
1197
+ [ "${#ARG_EXCLUDE_GLOBS[@]}" -gt 0 ] && base_exclude=("${ARG_EXCLUDE_GLOBS[@]}")
1198
+ if [ "${#CHANGED[@]}" -gt 0 ]; then
1199
+ for rel in "${CHANGED[@]}"; do
1200
+ if is_gated_path "$repo" "$rel"; then GATED+=("$rel"); continue; fi
1201
+ if [ "${#base_product[@]}" -gt 0 ] && ! path_matches_any "$rel" "${base_exclude[@]}" && path_matches_any "$rel" "${base_product[@]}"; then
1202
+ GATED+=("$rel")
1203
+ fi
1204
+ done
1205
+ fi
1206
+ local n_gated=${#GATED[@]} gated_disp=""
1207
+ [ "$n_gated" -gt 0 ] && gated_disp=$(printf ' %s' "${GATED[@]}")
1208
+
1209
+ # A TRACKED-but-unparseable config is "cannot verify" (fail-closed), NOT "not opted
1210
+ # in" and NOT a clean decommit: cfg_enabled returns empty for malformed JSON. This MUST
1211
+ # run BEFORE the decommit branch, else malforming an enabled config in a config-only
1212
+ # change (n_gated=0) would be mistaken for a clean removal and pass.
1213
+ if git -C "$repo" ls-files --error-unmatch .owner-dispatch.json >/dev/null 2>&1; then
1214
+ jq -e 'type=="object"' "$repo/.owner-dispatch.json" >/dev/null 2>&1 || {
1215
+ echo "owner-dispatch ci: FAIL-CLOSED (2) — tracked .owner-dispatch.json is malformed or non-object JSON." >&2; return 2; }
1216
+ fi
1217
+ # Anti-bypass: turning the gate OFF (disable/REMOVE) is allowed ONLY in a change that
1218
+ # touches NO gated product code — a clean, separately-reviewed decommit. (A malformed
1219
+ # config was already rejected above, so only a genuine disable/removal reaches here.)
1220
+ if [ "$base_enabled" = "1" ] && [ "$head_enabled" != "1" ]; then
1221
+ if [ "$n_gated" -gt 0 ]; then
1222
+ echo "owner-dispatch ci: FAIL (1) — this change disables/removes the gate AND touches gated product code:$gated_disp" >&2
1223
+ echo " Decommission the gate in a separate config-only change, or keep it enabled." >&2
1224
+ return 1
1225
+ fi
1226
+ echo "owner-dispatch ci: gate disabled/removed in a config-only change — allowed (clean decommit)"
1227
+ return 0
1228
+ fi
1229
+ [ "$head_enabled" = "1" ] || { echo "owner-dispatch ci: not opted in — skip"; return 0; }
1230
+ # Fail-closed: an enabled config must be schema-valid, else it gates nothing silently.
1231
+ cfg_schema_ok "$(cat "$repo/.owner-dispatch.json" 2>/dev/null)" || {
1232
+ echo "owner-dispatch ci: FAIL-CLOSED (2) — enabled .owner-dispatch.json is schema-invalid (product_globs must be a non-empty array of non-empty strings; strict/gate_bash boolean; ci_artifact a string)." >&2; return 2; }
1233
+ [ "$n_gated" -gt 0 ] || { echo "owner-dispatch ci: no gated product files changed — ok"; return 0; }
1234
+
1235
+ local artifact; artifact=$(cfg_get "$repo" '.ci_artifact' 'design/owner-dispatch-map.md')
1236
+ artifact=${artifact#./} # normalize to git's path form (git reports 'design/x', not './design/x')
1237
+ case "$artifact" in
1238
+ /*|"") echo "owner-dispatch ci: FAIL (1) — unsafe ci_artifact path '$artifact' (must be repo-relative, no '..' path component)." >&2; return 1 ;;
1239
+ esac
1240
+ case "/$artifact/" in
1241
+ */../*) echo "owner-dispatch ci: FAIL (1) — unsafe ci_artifact path '$artifact' (must be repo-relative, no '..' path component)." >&2; return 1 ;;
1242
+ esac
1243
+ # The map must be a DEDICATED artifact — not the config and not product code, or a
1244
+ # change to that very file would falsely satisfy the gate.
1245
+ [ "$artifact" = ".owner-dispatch.json" ] && { echo "owner-dispatch ci: FAIL (1) — ci_artifact must not be the config file." >&2; return 1; }
1246
+ load_globs "$repo"
1247
+ if [ "${#PRODUCT_GLOBS[@]}" -gt 0 ] && path_matches_any "$artifact" "${PRODUCT_GLOBS[@]}"; then
1248
+ echo "owner-dispatch ci: FAIL (1) — ci_artifact '$artifact' matches product_globs; the map must be a dedicated non-product file." >&2; return 1
1249
+ fi
1250
+ for rel in "${GATED[@]}"; do
1251
+ [ "$rel" = "$artifact" ] && { echo "owner-dispatch ci: FAIL (1) — ci_artifact '$artifact' is itself a gated changed file; use a dedicated map." >&2; return 1; }
1252
+ done
1253
+ if [ ! -s "$repo/$artifact" ]; then
1254
+ echo "owner-dispatch ci: FAIL (1) — gated product files changed:$gated_disp but no owner-dispatch map at '$artifact'." >&2
1255
+ return 1
1256
+ fi
1257
+ # Anti-stale: the artifact must be one of the files actually changed in this diff.
1258
+ local art_updated=0
1259
+ for rel in "${CHANGED[@]}"; do [ "$rel" = "$artifact" ] && { art_updated=1; break; }; done
1260
+ if [ "$art_updated" != "1" ]; then
1261
+ echo "owner-dispatch ci: FAIL (1) — '$artifact' exists but was not updated in this change (stale) for gated files:$gated_disp" >&2
1262
+ return 1
1263
+ fi
1264
+ # RESIDUAL (documented): CI checks the map EXISTS, is updated in-change, and sits on
1265
+ # a safe repo-relative path. It does NOT validate the map's semantic content (that the
1266
+ # listed owners actually cover the gated files) — that stays reviewer/owner judgment.
1267
+ echo "owner-dispatch ci: gated files changed and '$artifact' updated in this change — ok"
1268
+ }
1269
+
1270
+ cmd_status() {
1271
+ # Resolve exactly like the enforcement paths do (opt_in_root realpath's to the physical
1272
+ # path, "to match git's toplevel"). Using the raw logical path here made `status` compare
1273
+ # a logical path against the physical repo_root stored by `record`. Whenever the checkout
1274
+ # is reached through a symlink so `$PWD` stays logical (macOS /tmp among them; entering by
1275
+ # the physical path is unaffected), it reported absent/stale for a perfectly valid
1276
+ # boundary while pretool allowed the edit — the diagnostic surface contradicting the gate
1277
+ # it exists to explain. Fall back to the older resolution only when not opted in, so the
1278
+ # "opted-in: no" output still works outside a gated repo.
1279
+ local repo; repo=$(opt_in_root "$PWD") || repo=$(find_config_dir "$PWD") || repo=$(git rev-parse --show-toplevel 2>/dev/null)
1280
+ [ -n "${repo:-}" ] || { echo "not in a git repo / no opt-in found"; return 0; }
1281
+ echo "repo: $repo"
1282
+ echo "opted-in: $([ "$(cfg_enabled "$repo")" = enabled ] && echo yes || echo 'no (no/disabled/malformed .owner-dispatch.json)')"
1283
+ echo "strict: $(cfg_get "$repo" '.strict' 'false')"
1284
+ echo "HEAD: $(current_head "$repo")"
1285
+ # Report the actual state, not a valid/not-valid collapse: `status` is the command an
1286
+ # agent runs to diagnose a Stop block, so it must distinguish "never recorded" from
1287
+ # "recorded, then expired" for the same reason the Stop message does.
1288
+ echo "boundary: $(boundary_state "$repo")"
1289
+ }
1290
+
1291
+ main() {
1292
+ local sub="${1:-}"; shift || true
1293
+ case "$sub" in
1294
+ pretool) cmd_pretool "$@" ;;
1295
+ stop) cmd_stop "$@" ;;
1296
+ record) cmd_record "$@" ;;
1297
+ verify-invocations) cmd_verify_invocations "$@" ;;
1298
+ ci) cmd_ci "$@" ;;
1299
+ status) cmd_status "$@" ;;
1300
+ # Introspection: where boundary state for a root actually lands. Exists so the
1301
+ # read/write path can be ASSERTED across the worktree boundary rather than
1302
+ # inferred, and so a human debugging a Stop block can compare two roots directly.
1303
+ boundary-path) boundary_dir "${1:-$PWD}" ;;
1304
+ *) echo "usage: owner-dispatch.sh {pretool|stop|record --owners \"a,b\"|ci [--base REF]|status|boundary-path [root]}" >&2; return 2 ;;
1305
+ esac
1306
+ }
1307
+ main "$@"