dsh-compound-engineering 0.0.0-stage → 0.1.1

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 (461) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -2
  3. package/README.zh.md +161 -0
  4. package/cordis.patch.yml +6 -0
  5. package/lib/frontmatter.js +167 -0
  6. package/lib/index.js +46 -0
  7. package/lib/provider.js +201 -0
  8. package/package.json +62 -4
  9. package/skills/CODING_STANDARDS.md +68 -0
  10. package/skills/ce-babysit-pr/SKILL.md +59 -0
  11. package/skills/ce-babysit-pr/references/branch-currency.md +22 -0
  12. package/skills/ce-babysit-pr/references/envelope.md +44 -0
  13. package/skills/ce-babysit-pr/references/pipeline.md +15 -0
  14. package/skills/ce-babysit-pr/references/report.md +20 -0
  15. package/skills/ce-babysit-pr/references/settle.md +37 -0
  16. package/skills/ce-babysit-pr/references/setup.md +33 -0
  17. package/skills/ce-babysit-pr/references/stack-commands.md +39 -0
  18. package/skills/ce-babysit-pr/references/stack.md +43 -0
  19. package/skills/ce-babysit-pr/references/tick.md +102 -0
  20. package/skills/ce-babysit-pr/references/watch-loop.md +222 -0
  21. package/skills/ce-babysit-pr/scripts/pr-snapshot +3656 -0
  22. package/skills/ce-bakeoff/SKILL.md +48 -0
  23. package/skills/ce-bakeoff/references/candidates.md +37 -0
  24. package/skills/ce-bakeoff/references/judging.md +19 -0
  25. package/skills/ce-bakeoff/references/output.md +11 -0
  26. package/skills/ce-bakeoff/references/verification.md +15 -0
  27. package/skills/ce-brainstorm/SKILL.md +69 -0
  28. package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
  29. package/skills/ce-brainstorm/references/approaches.md +57 -0
  30. package/skills/ce-brainstorm/references/bakeoff.md +9 -0
  31. package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
  32. package/skills/ce-brainstorm/references/brainstorm-sections.md +458 -0
  33. package/skills/ce-brainstorm/references/dialogue.md +89 -0
  34. package/skills/ce-brainstorm/references/handoff.md +187 -0
  35. package/skills/ce-brainstorm/references/html-rendering.md +638 -0
  36. package/skills/ce-brainstorm/references/interaction-rules.md +33 -0
  37. package/skills/ce-brainstorm/references/markdown-rendering.md +242 -0
  38. package/skills/ce-brainstorm/references/model-tiers.md +13 -0
  39. package/skills/ce-brainstorm/references/output-mode.md +26 -0
  40. package/skills/ce-brainstorm/references/phase-0.md +104 -0
  41. package/skills/ce-brainstorm/references/plan-write.md +32 -0
  42. package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
  43. package/skills/ce-brainstorm/references/reasoning-elevation.md +124 -0
  44. package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
  45. package/skills/ce-brainstorm/references/synthesis-summary.md +149 -0
  46. package/skills/ce-brainstorm/references/universal-brainstorming.md +72 -0
  47. package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
  48. package/skills/ce-brainstorm/references/visual-probes.md +164 -0
  49. package/skills/ce-brainstorm/scripts/elevation-dispatch.sh +288 -0
  50. package/skills/ce-brainstorm/scripts/light-webserver.js +1275 -0
  51. package/skills/ce-brainstorm/scripts/packs-resolve.py +737 -0
  52. package/skills/ce-brainstorm/scripts/peer-job-runner.py +2594 -0
  53. package/skills/ce-code-review/SKILL.md +40 -0
  54. package/skills/ce-code-review/references/action-class-rubric.md +67 -0
  55. package/skills/ce-code-review/references/cross-model-recovery.md +38 -0
  56. package/skills/ce-code-review/references/cross-model-review.md +191 -0
  57. package/skills/ce-code-review/references/depth-paths.md +32 -0
  58. package/skills/ce-code-review/references/diff-scope.md +59 -0
  59. package/skills/ce-code-review/references/dispatch-reviewers.md +135 -0
  60. package/skills/ce-code-review/references/findings-schema.json +125 -0
  61. package/skills/ce-code-review/references/finish-input.md +118 -0
  62. package/skills/ce-code-review/references/finish-review.md +202 -0
  63. package/skills/ce-code-review/references/intent-and-plan.md +73 -0
  64. package/skills/ce-code-review/references/modes-and-output.md +156 -0
  65. package/skills/ce-code-review/references/persona-catalog.md +66 -0
  66. package/skills/ce-code-review/references/personas/adversarial-reviewer.md +111 -0
  67. package/skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
  68. package/skills/ce-code-review/references/personas/api-contract-reviewer.md +44 -0
  69. package/skills/ce-code-review/references/personas/correctness-reviewer.md +48 -0
  70. package/skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
  71. package/skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
  72. package/skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +45 -0
  73. package/skills/ce-code-review/references/personas/learnings-researcher.md +260 -0
  74. package/skills/ce-code-review/references/personas/maintainability-reviewer.md +85 -0
  75. package/skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
  76. package/skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
  77. package/skills/ce-code-review/references/personas/project-standards-reviewer.md +77 -0
  78. package/skills/ce-code-review/references/personas/reliability-reviewer.md +49 -0
  79. package/skills/ce-code-review/references/personas/security-reviewer.md +50 -0
  80. package/skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
  81. package/skills/ce-code-review/references/personas/testing-reviewer.md +51 -0
  82. package/skills/ce-code-review/references/review-output-template.md +172 -0
  83. package/skills/ce-code-review/references/scope.md +176 -0
  84. package/skills/ce-code-review/references/select-and-route.md +93 -0
  85. package/skills/ce-code-review/references/subagent-template.md +206 -0
  86. package/skills/ce-code-review/references/validator-batch-template.md +67 -0
  87. package/skills/ce-code-review/scripts/cross-model-adversarial-review.sh +1275 -0
  88. package/skills/ce-code-review/scripts/findings-mechanics.py +274 -0
  89. package/skills/ce-code-review/scripts/packs-resolve.py +737 -0
  90. package/skills/ce-code-review/scripts/peer-job-runner.py +2594 -0
  91. package/skills/ce-code-review/scripts/review-scope.py +419 -0
  92. package/skills/ce-code-review/scripts/run-log.py +280 -0
  93. package/skills/ce-commit/SKILL.md +60 -0
  94. package/skills/ce-commit-push-pr/SKILL.md +69 -0
  95. package/skills/ce-commit-push-pr/references/apply-and-handoff.md +79 -0
  96. package/skills/ce-commit-push-pr/references/branch-creation.md +47 -0
  97. package/skills/ce-commit-push-pr/references/commit-and-push.md +26 -0
  98. package/skills/ce-commit-push-pr/references/compose.md +29 -0
  99. package/skills/ce-commit-push-pr/references/context.md +42 -0
  100. package/skills/ce-commit-push-pr/references/gh-stack-cli.md +83 -0
  101. package/skills/ce-commit-push-pr/references/pr-description-writing.md +208 -0
  102. package/skills/ce-commit-push-pr/references/stack-submit.md +81 -0
  103. package/skills/ce-compound/SKILL.md +79 -0
  104. package/skills/ce-compound/assets/resolution-template.md +96 -0
  105. package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
  106. package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
  107. package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
  108. package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
  109. package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
  110. package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
  111. package/skills/ce-compound/references/agents/session-historian.md +82 -0
  112. package/skills/ce-compound/references/assembly.md +127 -0
  113. package/skills/ce-compound/references/concepts-vocabulary.md +98 -0
  114. package/skills/ce-compound/references/enhancement.md +42 -0
  115. package/skills/ce-compound/references/grounding-validation.md +89 -0
  116. package/skills/ce-compound/references/lightweight.md +56 -0
  117. package/skills/ce-compound/references/modes.md +13 -0
  118. package/skills/ce-compound/references/refresh-and-discoverability.md +96 -0
  119. package/skills/ce-compound/references/report.md +115 -0
  120. package/skills/ce-compound/references/research.md +144 -0
  121. package/skills/ce-compound/references/schema.yaml +236 -0
  122. package/skills/ce-compound/references/session-history.md +73 -0
  123. package/skills/ce-compound/references/yaml-schema.md +130 -0
  124. package/skills/ce-compound/scripts/packs-resolve.py +737 -0
  125. package/skills/ce-compound/scripts/session-history/__pycache__/dsh_session.cpython-314.pyc +0 -0
  126. package/skills/ce-compound/scripts/session-history/discover-sessions.sh +290 -0
  127. package/skills/ce-compound/scripts/session-history/discover_dsh.py +123 -0
  128. package/skills/ce-compound/scripts/session-history/dsh_session.py +225 -0
  129. package/skills/ce-compound/scripts/session-history/extract-errors.py +178 -0
  130. package/skills/ce-compound/scripts/session-history/extract-metadata.py +301 -0
  131. package/skills/ce-compound/scripts/session-history/extract-skeleton.py +346 -0
  132. package/skills/ce-compound/scripts/validate-doc-claims.py +497 -0
  133. package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
  134. package/skills/ce-compound-refresh/SKILL.md +88 -0
  135. package/skills/ce-compound-refresh/assets/resolution-template.md +96 -0
  136. package/skills/ce-compound-refresh/references/classify.md +42 -0
  137. package/skills/ce-compound-refresh/references/commit.md +7 -0
  138. package/skills/ce-compound-refresh/references/concepts-vocabulary.md +116 -0
  139. package/skills/ce-compound-refresh/references/discoverability.md +15 -0
  140. package/skills/ce-compound-refresh/references/investigate.md +23 -0
  141. package/skills/ce-compound-refresh/references/modes.md +27 -0
  142. package/skills/ce-compound-refresh/references/per-action-flows.md +142 -0
  143. package/skills/ce-compound-refresh/references/report.md +23 -0
  144. package/skills/ce-compound-refresh/references/schema.yaml +236 -0
  145. package/skills/ce-compound-refresh/references/scope.md +18 -0
  146. package/skills/ce-compound-refresh/references/worth-audit.md +35 -0
  147. package/skills/ce-compound-refresh/references/yaml-schema.md +130 -0
  148. package/skills/ce-compound-refresh/scripts/validate-doc-claims.py +497 -0
  149. package/skills/ce-compound-refresh/scripts/validate-frontmatter.py +137 -0
  150. package/skills/ce-debug/SKILL.md +122 -0
  151. package/skills/ce-debug/references/anti-patterns.md +91 -0
  152. package/skills/ce-debug/references/defense-in-depth.md +39 -0
  153. package/skills/ce-debug/references/fix.md +21 -0
  154. package/skills/ce-debug/references/investigate.md +114 -0
  155. package/skills/ce-debug/references/investigation-techniques.md +384 -0
  156. package/skills/ce-debug/references/pipeline-mode.md +78 -0
  157. package/skills/ce-debug/references/post-fix-handoff.md +60 -0
  158. package/skills/ce-debug/references/return-to-caller.md +57 -0
  159. package/skills/ce-doc-review/SKILL.md +75 -0
  160. package/skills/ce-doc-review/references/bulk-preview.md +146 -0
  161. package/skills/ce-doc-review/references/cross-model-review.md +207 -0
  162. package/skills/ce-doc-review/references/decision-primer.md +47 -0
  163. package/skills/ce-doc-review/references/dispatch.md +56 -0
  164. package/skills/ce-doc-review/references/document-intake.md +44 -0
  165. package/skills/ce-doc-review/references/findings-schema.json +85 -0
  166. package/skills/ce-doc-review/references/modes.md +20 -0
  167. package/skills/ce-doc-review/references/open-questions-defer.md +158 -0
  168. package/skills/ce-doc-review/references/persona-selection.md +41 -0
  169. package/skills/ce-doc-review/references/personas/adversarial-document-reviewer.md +109 -0
  170. package/skills/ce-doc-review/references/personas/coherence-reviewer.md +69 -0
  171. package/skills/ce-doc-review/references/personas/design-lens-reviewer.md +48 -0
  172. package/skills/ce-doc-review/references/personas/feasibility-reviewer.md +50 -0
  173. package/skills/ce-doc-review/references/personas/product-lens-reviewer.md +85 -0
  174. package/skills/ce-doc-review/references/personas/scope-guardian-reviewer.md +83 -0
  175. package/skills/ce-doc-review/references/personas/security-lens-reviewer.md +41 -0
  176. package/skills/ce-doc-review/references/personas/whole-doc-reviewer.md +25 -0
  177. package/skills/ce-doc-review/references/rendering-floor.md +175 -0
  178. package/skills/ce-doc-review/references/review-output-template.md +123 -0
  179. package/skills/ce-doc-review/references/subagent-template.md +188 -0
  180. package/skills/ce-doc-review/references/synthesis-and-presentation.md +350 -0
  181. package/skills/ce-doc-review/references/walkthrough.md +347 -0
  182. package/skills/ce-doc-review/scripts/cross-model-doc-review.sh +1270 -0
  183. package/skills/ce-doc-review/scripts/packs-resolve.py +737 -0
  184. package/skills/ce-doc-review/scripts/peer-job-runner.py +2594 -0
  185. package/skills/ce-dogfood/SKILL.md +66 -0
  186. package/skills/ce-dogfood/agents/openai.yaml +2 -0
  187. package/skills/ce-dogfood/references/dogfood-report-template.md +97 -0
  188. package/skills/ce-dogfood/references/phases.md +167 -0
  189. package/skills/ce-dogfood/references/test-matrix-taxonomy.md +62 -0
  190. package/skills/ce-dogfood/scripts/packs-resolve.py +737 -0
  191. package/skills/ce-explain/SKILL.md +81 -0
  192. package/skills/ce-explain/references/agents/behavior-trace-scout.md +19 -0
  193. package/skills/ce-explain/references/agents/work-recap-scout.md +23 -0
  194. package/skills/ce-explain/references/check-in.md +18 -0
  195. package/skills/ce-explain/references/destinations.md +35 -0
  196. package/skills/ce-explain/references/explainer-html.md +29 -0
  197. package/skills/ce-explain/references/explainer-markdown.md +24 -0
  198. package/skills/ce-explain/references/intake.md +50 -0
  199. package/skills/ce-explain/references/orchestration.md +46 -0
  200. package/skills/ce-handoff/SKILL.md +54 -0
  201. package/skills/ce-handoff/references/create.md +88 -0
  202. package/skills/ce-handoff/references/resume.md +46 -0
  203. package/skills/ce-ideate/SKILL.md +75 -0
  204. package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +166 -0
  205. package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
  206. package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
  207. package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
  208. package/skills/ce-ideate/references/decomposition.md +39 -0
  209. package/skills/ce-ideate/references/divergent-ideation.md +112 -0
  210. package/skills/ce-ideate/references/grounding.md +138 -0
  211. package/skills/ce-ideate/references/html-rendering.md +638 -0
  212. package/skills/ce-ideate/references/ideation-sections.md +191 -0
  213. package/skills/ce-ideate/references/issue-intelligence.md +44 -0
  214. package/skills/ce-ideate/references/markdown-rendering.md +242 -0
  215. package/skills/ce-ideate/references/output-mode.md +34 -0
  216. package/skills/ce-ideate/references/post-ideation-workflow.md +169 -0
  217. package/skills/ce-ideate/references/scope-gates.md +148 -0
  218. package/skills/ce-ideate/references/universal-ideation.md +119 -0
  219. package/skills/ce-ideate/references/user-research-artifacts.md +28 -0
  220. package/skills/ce-ideate/references/web-research-cache.md +56 -0
  221. package/skills/ce-noslop/SKILL.md +45 -0
  222. package/skills/ce-noslop/references/patterns.md +61 -0
  223. package/skills/ce-noslop/references/terminology.md +3 -0
  224. package/skills/ce-optimize/SKILL.md +64 -0
  225. package/skills/ce-optimize/references/agents/learnings-researcher.md +247 -0
  226. package/skills/ce-optimize/references/agents/repo-research-analyst.md +258 -0
  227. package/skills/ce-optimize/references/example-expensive-benchmark-spec.yaml +88 -0
  228. package/skills/ce-optimize/references/example-hard-spec.yaml +64 -0
  229. package/skills/ce-optimize/references/example-judge-spec.yaml +83 -0
  230. package/skills/ce-optimize/references/example-text-target-spec.yaml +80 -0
  231. package/skills/ce-optimize/references/experiment-log-schema.yaml +563 -0
  232. package/skills/ce-optimize/references/experiment-prompt-template.md +143 -0
  233. package/skills/ce-optimize/references/judge-prompt-template.md +117 -0
  234. package/skills/ce-optimize/references/loop.md +286 -0
  235. package/skills/ce-optimize/references/measurement.md +168 -0
  236. package/skills/ce-optimize/references/optimize-spec-schema.yaml +570 -0
  237. package/skills/ce-optimize/references/persistence.md +93 -0
  238. package/skills/ce-optimize/references/spec.md +107 -0
  239. package/skills/ce-optimize/references/text-targets.md +51 -0
  240. package/skills/ce-optimize/references/usage-guide.md +147 -0
  241. package/skills/ce-optimize/references/wrap-up.md +54 -0
  242. package/skills/ce-optimize/scripts/decide.mjs +676 -0
  243. package/skills/ce-optimize/scripts/experiment-worktree.sh +330 -0
  244. package/skills/ce-optimize/scripts/measure.sh +140 -0
  245. package/skills/ce-optimize/scripts/parallel-probe.sh +134 -0
  246. package/skills/ce-plan/SKILL.md +60 -0
  247. package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +66 -0
  248. package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
  249. package/skills/ce-plan/references/agents/best-practices-researcher.md +118 -0
  250. package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
  251. package/skills/ce-plan/references/agents/framework-docs-researcher.md +97 -0
  252. package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
  253. package/skills/ce-plan/references/agents/learnings-researcher.md +268 -0
  254. package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
  255. package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
  256. package/skills/ce-plan/references/agents/repo-research-analyst.md +262 -0
  257. package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
  258. package/skills/ce-plan/references/agents/slack-researcher.md +131 -0
  259. package/skills/ce-plan/references/agents/spec-flow-analyzer.md +84 -0
  260. package/skills/ce-plan/references/agents/web-researcher.md +125 -0
  261. package/skills/ce-plan/references/approach-altitude.md +55 -0
  262. package/skills/ce-plan/references/bakeoff.md +9 -0
  263. package/skills/ce-plan/references/deepening-workflow.md +263 -0
  264. package/skills/ce-plan/references/final-review.md +134 -0
  265. package/skills/ce-plan/references/html-rendering.md +638 -0
  266. package/skills/ce-plan/references/intake.md +158 -0
  267. package/skills/ce-plan/references/markdown-rendering.md +242 -0
  268. package/skills/ce-plan/references/output-contracts.md +29 -0
  269. package/skills/ce-plan/references/output-mode.md +45 -0
  270. package/skills/ce-plan/references/plan-handoff.md +116 -0
  271. package/skills/ce-plan/references/plan-sections.md +489 -0
  272. package/skills/ce-plan/references/reasoning-elevation.md +124 -0
  273. package/skills/ce-plan/references/research.md +211 -0
  274. package/skills/ce-plan/references/resume.md +61 -0
  275. package/skills/ce-plan/references/settled-decisions.md +45 -0
  276. package/skills/ce-plan/references/structure.md +207 -0
  277. package/skills/ce-plan/references/synthesis-summary.md +260 -0
  278. package/skills/ce-plan/references/universal-planning.md +170 -0
  279. package/skills/ce-plan/scripts/elevation-dispatch.sh +288 -0
  280. package/skills/ce-plan/scripts/packs-resolve.py +737 -0
  281. package/skills/ce-plan/scripts/peer-job-runner.py +2594 -0
  282. package/skills/ce-polish/SKILL.md +22 -0
  283. package/skills/ce-polish/agents/openai.yaml +2 -0
  284. package/skills/ce-polish/references/dev-server-astro.md +58 -0
  285. package/skills/ce-polish/references/dev-server-detection.md +40 -0
  286. package/skills/ce-polish/references/dev-server-next.md +61 -0
  287. package/skills/ce-polish/references/dev-server-nuxt.md +58 -0
  288. package/skills/ce-polish/references/dev-server-procfile.md +59 -0
  289. package/skills/ce-polish/references/dev-server-rails.md +50 -0
  290. package/skills/ce-polish/references/dev-server-remix.md +58 -0
  291. package/skills/ce-polish/references/dev-server-sveltekit.md +58 -0
  292. package/skills/ce-polish/references/dev-server-vite.md +48 -0
  293. package/skills/ce-polish/references/install-riffrec.md +35 -0
  294. package/skills/ce-polish/references/launch-json-schema.md +177 -0
  295. package/skills/ce-polish/references/live-loop.md +98 -0
  296. package/skills/ce-polish/references/live-remote.md +35 -0
  297. package/skills/ce-polish/references/live-start.md +93 -0
  298. package/skills/ce-polish/references/live-stream-contract.md +253 -0
  299. package/skills/ce-polish/references/run.md +67 -0
  300. package/skills/ce-polish/scripts/detect-project-type.sh +277 -0
  301. package/skills/ce-polish/scripts/detect-riffrec.sh +225 -0
  302. package/skills/ce-polish/scripts/live-endpoint.js +2841 -0
  303. package/skills/ce-polish/scripts/read-launch-json.sh +117 -0
  304. package/skills/ce-polish/scripts/resolve-package-manager.sh +95 -0
  305. package/skills/ce-polish/scripts/resolve-port.sh +307 -0
  306. package/skills/ce-pov/SKILL.md +64 -0
  307. package/skills/ce-pov/references/agents/external-evidence-researcher.md +26 -0
  308. package/skills/ce-pov/references/agents/pov-peer.md +43 -0
  309. package/skills/ce-pov/references/agents/precedent-activity-scout.md +21 -0
  310. package/skills/ce-pov/references/agents/project-grounding-scout.md +32 -0
  311. package/skills/ce-pov/references/boundaries.md +36 -0
  312. package/skills/ce-pov/references/cross-model-panel.md +596 -0
  313. package/skills/ce-pov/references/followup.md +9 -0
  314. package/skills/ce-pov/references/grounding.md +47 -0
  315. package/skills/ce-pov/references/intake.md +61 -0
  316. package/skills/ce-pov/references/invocation.md +47 -0
  317. package/skills/ce-pov/references/method.md +68 -0
  318. package/skills/ce-pov/references/pov-schema.json +56 -0
  319. package/skills/ce-pov/references/report.md +7 -0
  320. package/skills/ce-pov/scripts/cross-model-pov.sh +1110 -0
  321. package/skills/ce-pov/scripts/peer-job-runner.py +2594 -0
  322. package/skills/ce-product-pulse/SKILL.md +81 -0
  323. package/skills/ce-product-pulse/agents/openai.yaml +2 -0
  324. package/skills/ce-product-pulse/references/config.md +20 -0
  325. package/skills/ce-product-pulse/references/interview.md +260 -0
  326. package/skills/ce-product-pulse/references/report-template.md +89 -0
  327. package/skills/ce-product-pulse/references/run.md +46 -0
  328. package/skills/ce-product-pulse/references/setup.md +43 -0
  329. package/skills/ce-promote/SKILL.md +90 -0
  330. package/skills/ce-promote/agents/openai.yaml +2 -0
  331. package/skills/ce-promote/references/spiral-cli.md +149 -0
  332. package/skills/ce-proof/SKILL.md +65 -0
  333. package/skills/ce-proof/references/api.md +176 -0
  334. package/skills/ce-proof/references/workflows.md +124 -0
  335. package/skills/ce-prototype/SKILL.md +59 -0
  336. package/skills/ce-prototype/assets/annotate.css +206 -0
  337. package/skills/ce-prototype/assets/annotate.js +837 -0
  338. package/skills/ce-prototype/references/annotation-loop.md +32 -0
  339. package/skills/ce-prototype/references/build.md +39 -0
  340. package/skills/ce-prototype/references/craft-floor.md +43 -0
  341. package/skills/ce-prototype/references/preview.md +118 -0
  342. package/skills/ce-prototype/references/scoping.md +38 -0
  343. package/skills/ce-prototype/references/write-back.md +37 -0
  344. package/skills/ce-prototype/scripts/light-webserver.js +1275 -0
  345. package/skills/ce-resolve-pr-feedback/SKILL.md +60 -0
  346. package/skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +59 -0
  347. package/skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +141 -0
  348. package/skills/ce-resolve-pr-feedback/references/full-mode.md +336 -0
  349. package/skills/ce-resolve-pr-feedback/references/pipeline-mode.md +23 -0
  350. package/skills/ce-resolve-pr-feedback/references/resume.md +60 -0
  351. package/skills/ce-resolve-pr-feedback/references/return-to-caller.md +111 -0
  352. package/skills/ce-resolve-pr-feedback/references/targeted-mode.md +48 -0
  353. package/skills/ce-resolve-pr-feedback/scripts/get-pr-comments +176 -0
  354. package/skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +84 -0
  355. package/skills/ce-resolve-pr-feedback/scripts/pending-feedback.py +287 -0
  356. package/skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +57 -0
  357. package/skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
  358. package/skills/ce-retune/SKILL.md +42 -0
  359. package/skills/ce-retune/agents/openai.yaml +2 -0
  360. package/skills/ce-retune/references/baseline-mining.md +122 -0
  361. package/skills/ce-retune/references/corpus-audit.md +128 -0
  362. package/skills/ce-retune/references/cut-passes.md +129 -0
  363. package/skills/ce-retune/references/halt-taxonomy.md +123 -0
  364. package/skills/ce-retune/references/noise-floor.md +131 -0
  365. package/skills/ce-retune/references/workflow-shapes.md +73 -0
  366. package/skills/ce-riffrec-feedback-analysis/SKILL.md +33 -0
  367. package/skills/ce-riffrec-feedback-analysis/references/analyzer.md +18 -0
  368. package/skills/ce-riffrec-feedback-analysis/references/compound-engineering-feedback-format.md +116 -0
  369. package/skills/ce-riffrec-feedback-analysis/references/extensive-analysis.md +98 -0
  370. package/skills/ce-riffrec-feedback-analysis/references/install-riffrec.md +27 -0
  371. package/skills/ce-riffrec-feedback-analysis/references/quick-bug-report.md +38 -0
  372. package/skills/ce-riffrec-feedback-analysis/scripts/analyze_riffrec_zip.py +1306 -0
  373. package/skills/ce-setup/SKILL.md +103 -0
  374. package/skills/ce-setup/agents/openai.yaml +2 -0
  375. package/skills/ce-setup/assets/compounding-directive.md +11 -0
  376. package/skills/ce-setup/assets/noslop-directive.md +7 -0
  377. package/skills/ce-setup/assets/pack-rule-template.md +11 -0
  378. package/skills/ce-setup/references/acpx-cache-warm.md +14 -0
  379. package/skills/ce-setup/references/config-template.yaml +210 -0
  380. package/skills/ce-setup/references/legacy-codex-tool-map.md +15 -0
  381. package/skills/ce-setup/references/pack-scaffold.md +45 -0
  382. package/skills/ce-setup/references/repo-fixes.md +121 -0
  383. package/skills/ce-setup/scripts/check-health +929 -0
  384. package/skills/ce-setup/scripts/packs-resolve.py +737 -0
  385. package/skills/ce-simplify-code/SKILL.md +76 -0
  386. package/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
  387. package/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
  388. package/skills/ce-simplify-code/references/personas/comment-reviewer.md +24 -0
  389. package/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
  390. package/skills/ce-simplify-code/references/personas/test-value-reviewer.md +23 -0
  391. package/skills/ce-simplify-code/references/reviewer-dispatch.md +11 -0
  392. package/skills/ce-simplify-code/references/standalone-report.md +25 -0
  393. package/skills/ce-strategy/SKILL.md +61 -0
  394. package/skills/ce-strategy/references/grounding.md +22 -0
  395. package/skills/ce-strategy/references/interview.md +179 -0
  396. package/skills/ce-strategy/references/strategy-template.md +94 -0
  397. package/skills/ce-strategy/references/update-run.md +33 -0
  398. package/skills/ce-sweep/SKILL.md +93 -0
  399. package/skills/ce-sweep/agents/openai.yaml +2 -0
  400. package/skills/ce-sweep/references/agents/media-analyzer.md +54 -0
  401. package/skills/ce-sweep/references/interview.md +208 -0
  402. package/skills/ce-sweep/references/model-tiers.md +13 -0
  403. package/skills/ce-sweep/references/plan-template.md +62 -0
  404. package/skills/ce-sweep/references/run.md +126 -0
  405. package/skills/ce-sweep/references/sources/email.md +57 -0
  406. package/skills/ce-sweep/references/sources/github-issues.md +59 -0
  407. package/skills/ce-sweep/references/sources/slack.md +61 -0
  408. package/skills/ce-sweep/references/state-schema.md +157 -0
  409. package/skills/ce-sweep/references/subagent-template.md +60 -0
  410. package/skills/ce-sweep/scripts/analyze_riffrec_zip.py +1306 -0
  411. package/skills/ce-sweep/scripts/sweep-state.py +793 -0
  412. package/skills/ce-test-browser/SKILL.md +50 -0
  413. package/skills/ce-test-browser/references/agent-browser-driver.md +47 -0
  414. package/skills/ce-test-browser/references/pipeline-orchestration.md +45 -0
  415. package/skills/ce-test-browser/references/route-and-report.md +129 -0
  416. package/skills/ce-test-browser/scripts/resolve-port.sh +41 -0
  417. package/skills/ce-test-xcode/SKILL.md +26 -0
  418. package/skills/ce-test-xcode/agents/openai.yaml +2 -0
  419. package/skills/ce-test-xcode/references/setup-and-build.md +32 -0
  420. package/skills/ce-test-xcode/references/test-and-report.md +64 -0
  421. package/skills/ce-work/SKILL.md +62 -0
  422. package/skills/ce-work/references/agents/figma-design-sync.md +27 -0
  423. package/skills/ce-work/references/agents/implementation-worker.md +17 -0
  424. package/skills/ce-work/references/cross-model-execution.md +191 -0
  425. package/skills/ce-work/references/execution-engines.md +64 -0
  426. package/skills/ce-work/references/execution-strategy.md +73 -0
  427. package/skills/ce-work/references/implementation-loop.md +107 -0
  428. package/skills/ce-work/references/implementation-result-schema.json +52 -0
  429. package/skills/ce-work/references/input-triage.md +50 -0
  430. package/skills/ce-work/references/non-code-execution.md +23 -0
  431. package/skills/ce-work/references/return-to-caller.md +33 -0
  432. package/skills/ce-work/references/review-findings-followup.md +82 -0
  433. package/skills/ce-work/references/shipping-workflow.md +89 -0
  434. package/skills/ce-work/references/tracker-defer.md +150 -0
  435. package/skills/ce-work/references/work-intake.md +51 -0
  436. package/skills/ce-work/references/workspace-setup.md +27 -0
  437. package/skills/ce-work/scripts/__pycache__/unit_workspace_jobs.cpython-314.pyc +0 -0
  438. package/skills/ce-work/scripts/__pycache__/unit_workspace_state.cpython-314.pyc +0 -0
  439. package/skills/ce-work/scripts/cross-model-work.sh +1385 -0
  440. package/skills/ce-work/scripts/peer-job-runner.py +2594 -0
  441. package/skills/ce-work/scripts/unit-workspace.py +215 -0
  442. package/skills/ce-work/scripts/unit_workspace_ignored.py +93 -0
  443. package/skills/ce-work/scripts/unit_workspace_integration.py +677 -0
  444. package/skills/ce-work/scripts/unit_workspace_jobs.py +1216 -0
  445. package/skills/ce-work/scripts/unit_workspace_lifecycle.py +981 -0
  446. package/skills/ce-work/scripts/unit_workspace_state.py +1379 -0
  447. package/skills/ce-work/scripts/unit_workspace_transaction.py +612 -0
  448. package/skills/ce-worktree/SKILL.md +54 -0
  449. package/skills/lfg/SKILL.md +59 -0
  450. package/skills/lfg/references/debug-return.md +25 -0
  451. package/skills/lfg/references/intake.md +30 -0
  452. package/skills/lfg/references/next-work-handoff.md +96 -0
  453. package/skills/lfg/references/plan-brief.md +36 -0
  454. package/skills/lfg/references/review-followup.md +78 -0
  455. package/skills/lfg/references/shipping.md +65 -0
  456. package/skills/lfg/references/stage-routing.md +49 -0
  457. package/skills/lfg/references/task-visibility.md +11 -0
  458. package/skills/lfg/references/tracker-defer.md +150 -0
  459. package/skills/lfg/references/work-return.md +27 -0
  460. package/skills/wtf/SKILL.md +33 -0
  461. package/skills/wtf/agents/openai.yaml +2 -0
@@ -0,0 +1,1270 @@
1
+ #!/usr/bin/env bash
2
+ # cross-model-doc-review.sh
3
+ #
4
+ # Runs ONE ce-doc-review judgment persona through ONE or more DIFFERENT model
5
+ # PROVIDERS than the host (the "peer(s)") in separate, read-only, least-privilege
6
+ # processes, and writes each peer's findings as JSON into the run dir. Each peer
7
+ # gets the same canonical persona brief the in-process reviewer uses
8
+ # (references/personas/<persona-file>.md) so it is genuinely "that persona, on a
9
+ # different model." One invocation per persona is required because each lens
10
+ # carries its own persona brief and produces its own <lens>-<provider>.json
11
+ # return that folds in and fingerprints against its in-process twin.
12
+ #
13
+ # Independence is by PROVIDER, not CLI brand. A provider is reached by a ROUTE:
14
+ # its dedicated CLI, or (for fixed grok-cursor / composer routes) cursor-agent.
15
+ # Every route runs through acpx (the Agent Client Protocol client). All
16
+ # activated lenses run on ONE model per provider at high reasoning, except codex
17
+ # and native grok on extra-high; Cursor's grok preset is high and composer's
18
+ # fast tier is its ceiling (accepted exceptions).
19
+ #
20
+ # Usage:
21
+ # cross-model-doc-review.sh <host-serving-family> <candidates> <reviewer-name> \
22
+ # <document-path> <document-type> <origin> <run-dir>
23
+ #
24
+ # <host-serving-family>
25
+ # the peer-key of the host's OWN serving family, attested by
26
+ # the calling skill (it knows its harness). A peer-key, never
27
+ # a provider name: openai->codex, anthropic->claude,
28
+ # xai->grok, cursor/composer->composer, google->gemini.
29
+ # Excluded from selection when attested. `unknown` is allowed,
30
+ # but any returned review remains non-independent and cannot
31
+ # promote agreement.
32
+ # <candidates> comma-separated ordered provider keys to consider, e.g.
33
+ # "codex,claude,grok,antigravity". The skill front-loads any
34
+ # resolved preference (conversation > CE config cascade >
35
+ # project-instructions-in-context); the script excludes the
36
+ # host, applies the CROSS_MODEL_PEERS allowlist, and walks this
37
+ # order picking the first available provider(s) up to
38
+ # CROSS_MODEL_MAX_PEERS.
39
+ # <reviewer-name> one of the three trio lenses: security-lens | adversarial |
40
+ # product-lens. The SHORT name the in-process persona emits; it
41
+ # forces the fold-in reviewer field to <reviewer-name>-<provider>
42
+ # so cross-persona agreement in synthesis matches the in-process
43
+ # twin. The persona-brief filename is DERIVED from it (not a
44
+ # caller argument) so a caller cannot point the brief read at an
45
+ # arbitrary path.
46
+ # <document-path> the document under review (embedded into the peer prompt)
47
+ # <document-type> requirements | plan | unified-requirements | unified-plan
48
+ # <origin> the Origin context slot (a path, product_contract_source:<v>,
49
+ # or the literal token none)
50
+ # <run-dir> an existing dir; output -> <run-dir>/<reviewer-name>-<provider>.json
51
+ #
52
+ # Test/introspection mode (no model call, no side effects):
53
+ # cross-model-doc-review.sh --emit-adapter <route>
54
+ # prints the exact argv the given route would run (route in:
55
+ # codex | claude | grok-cli | grok-cursor | cursor | composer | opencode |
56
+ # antigravity).
57
+ # Both this mode and the live run build their argv from adapter_argv(), so
58
+ # the route-safety test asserts on the same command string the peer runs.
59
+ #
60
+ # Self-locates its sibling reference files via BASH_SOURCE (NOT the CWD, which is
61
+ # the user's project on every host). The agent passes the values above.
62
+ #
63
+ # NON-BLOCKING BY DESIGN: every failure logs to stderr and exits 0 without an
64
+ # output file. The cross-model pass is additive and must never fail the review;
65
+ # the caller detects success purely by the presence of the output file(s).
66
+ #
67
+ # DATA-EGRESS NOTE: this embeds the full document content into an external model
68
+ # CLI prompt, so document content is transmitted to each peer provider. The log
69
+ # lines below record every send so the egress is auditable even in headless mode.
70
+
71
+ set -uo pipefail
72
+
73
+ # Survive SIGHUP when the orchestrator backgrounds this script and the parent
74
+ # shell exits (common on Cursor/Codex Bash tools). Without this, a detached
75
+ # peer process group can keep running while this script dies
76
+ # before normalize — leaving fold-in files with a bare `reviewer` field.
77
+ trap '' HUP
78
+
79
+ # Filled while a peer process group is live; TERM/INT handler (installed after
80
+ # reap() is defined) reaps it so an orchestrator kill cannot leave orphans.
81
+ ACTIVE_PEER_PID=""
82
+ PEER_ROUTE=""
83
+ SESSION_STALLED=false
84
+ PY_BIN=""
85
+
86
+ log() { printf '[cross-model-doc] %s\n' "$*" >&2; }
87
+ skip() { log "$*"; exit 0; } # non-blocking: announce reason, exit clean, no output
88
+
89
+ TRANSIENT_RETRY_DELAY_SECS="${CROSS_MODEL_TRANSIENT_RETRY_DELAY_SECS:-5}"
90
+ case "$TRANSIENT_RETRY_DELAY_SECS" in ''|*[!0-9]*) skip "transient retry delay must be an integer from 0 to 60; skipping" ;; esac
91
+ [ "$TRANSIENT_RETRY_DELAY_SECS" -le 60 ] || skip "transient retry delay must be an integer from 0 to 60; skipping"
92
+
93
+ # --- model + reasoning per provider ----------------------------------------
94
+ # ONE model per provider at high reasoning, except codex and native grok on
95
+ # extra-high. Concrete IDs are the CURRENT instance of the tier principle and
96
+ # the single maintenance point when model families change.
97
+ # A checkout may override the model (CROSS_MODEL_MODEL_OVERRIDE_TARGET +
98
+ # CROSS_MODEL_MODEL_OVERRIDE, same target/family only) and the reasoning effort
99
+ # (CROSS_MODEL_EFFORT_OVERRIDE, validated per route); both fail closed.
100
+ # codex: luna/xhigh is the benchmarked pick on API dollars (~0.30x sol-medium, tied
101
+ # detection, slower tail) -- docs/solutions/skill-design/benchmark-review-peer-model-and-reasoning-tier.md
102
+ M_CODEX="gpt-6-luna" # codex (reasoning_effort=xhigh)
103
+ M_CLAUDE="claude-opus-5-5" # claude (effort=high)
104
+ M_GROK="grok-4.7" # grok CLI (reasoning_effort=xhigh)
105
+ # Cursor's ACP server offers one preset per model and rejects effort variants,
106
+ # so grok-cursor runs at high/fast and composer at its fast tier.
107
+ M_GROK_CURSOR="grok-4.7[context=256k,reasoning_effort=high,fast=true]"
108
+ M_COMPOSER="composer-2.5[fast=true]"
109
+ # Antigravity offers only the models the account is entitled to. Argon is
110
+ # preferred; an account without it falls back once to Flash before any prompt.
111
+ M_ANTIGRAVITY="gemini-4-argon" # antigravity (thinking_level=high)
112
+ M_ANTIGRAVITY_FALLBACK="gemini-3.8-flash"
113
+ ANTIGRAVITY_MODEL="" # set by the fallback; empty requests M_ANTIGRAVITY
114
+
115
+ route_effort() { # <route> -> requested effort: the override where the route takes one, else editorial
116
+ if [ -n "${CROSS_MODEL_EFFORT_OVERRIDE:-}" ]; then
117
+ case "$1" in
118
+ codex|claude|grok-cli|antigravity|opencode) printf '%s' "$CROSS_MODEL_EFFORT_OVERRIDE"; return 0 ;;
119
+ esac
120
+ fi
121
+ case "$1" in
122
+ codex|grok-cli) printf 'xhigh' ;;
123
+ claude|antigravity) printf 'high' ;;
124
+ grok-cursor) printf 'model-implied-high' ;;
125
+ composer) printf 'fast' ;;
126
+ cursor|opencode) printf 'unverified' ;;
127
+ esac
128
+ }
129
+
130
+ route_model() { # <route> -> the M_* constant that route requests
131
+ local target
132
+ target="$(route_target "$1")"
133
+ if [ -n "${CROSS_MODEL_MODEL_OVERRIDE:-}" ] &&
134
+ [ "${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" = "$target" ] &&
135
+ [ "$target" != "cursor" ]; then
136
+ printf '%s' "$CROSS_MODEL_MODEL_OVERRIDE"
137
+ return 0
138
+ fi
139
+ case "$1" in
140
+ codex) printf '%s' "$M_CODEX" ;;
141
+ claude) printf '%s' "$M_CLAUDE" ;;
142
+ grok-cli) printf '%s' "$M_GROK" ;;
143
+ grok-cursor) printf '%s' "$M_GROK_CURSOR" ;;
144
+ cursor) printf 'auto' ;;
145
+ composer) printf '%s' "$M_COMPOSER" ;;
146
+ opencode) printf 'auto' ;;
147
+ antigravity) printf '%s' "${ANTIGRAVITY_MODEL:-$M_ANTIGRAVITY}" ;;
148
+ esac
149
+ }
150
+
151
+ route_target() {
152
+ case "$1" in
153
+ codex|claude|cursor|composer|antigravity) printf '%s' "$1" ;;
154
+ grok-cli|grok-cursor) printf 'grok' ;;
155
+ opencode) printf 'opencode' ;;
156
+ esac
157
+ }
158
+
159
+ route_harness() {
160
+ case "$1" in
161
+ codex) printf 'codex' ;;
162
+ claude) printf 'claude' ;;
163
+ grok-cli) printf 'grok' ;;
164
+ grok-cursor|cursor|composer) printf 'cursor-agent' ;;
165
+ opencode) printf 'opencode' ;;
166
+ antigravity) printf 'antigravity' ;;
167
+ esac
168
+ }
169
+
170
+ target_serving_family() {
171
+ case "$1" in
172
+ codex|claude|grok|composer) printf '%s' "$1" ;;
173
+ antigravity) printf 'gemini' ;;
174
+ cursor) printf 'unknown' ;;
175
+ opencode) printf 'unknown' ;;
176
+ esac
177
+ }
178
+
179
+ route_available() {
180
+ case "$1" in
181
+ codex) command -v codex >/dev/null 2>&1 ;;
182
+ claude) command -v claude >/dev/null 2>&1 ;;
183
+ grok-cli) command -v grok >/dev/null 2>&1 ;;
184
+ grok-cursor|cursor|composer) command -v cursor-agent >/dev/null 2>&1 ;;
185
+ opencode) command -v opencode >/dev/null 2>&1 ;;
186
+ antigravity) command -v agy_acp_server.par >/dev/null 2>&1 ;;
187
+ *) return 1 ;;
188
+ esac
189
+ }
190
+
191
+ # --- acpx transport (keep byte-identical across migrated peer workers) -----
192
+ # Every route reaches its agent through acpx, which speaks the Agent Client
193
+ # Protocol and prints the JSON-RPC stream, so one parser covers every CLI.
194
+ # The calling worker supplies route_available, route_model, and log.
195
+ ACPX_VERSION="0.21.0"
196
+ ACPX_UNAVAILABLE="" # pre-egress reason; ACPX_SCOPE says whether every route shares it
197
+ ACPX_SCOPE=""
198
+ ACPX_NPM_REFRESH=false
199
+
200
+ acpx_agent() { # <route> -> acpx built-in agent name
201
+ case "$1" in
202
+ codex) printf 'codex' ;;
203
+ claude) printf 'claude' ;;
204
+ grok-cli) printf 'grok-build' ;;
205
+ grok-cursor|cursor|composer) printf 'cursor' ;;
206
+ opencode) printf 'opencode' ;;
207
+ antigravity) printf 'antigravity' ;;
208
+ *) return 1 ;;
209
+ esac
210
+ }
211
+
212
+ # acpx launches Google's Antigravity ACP runtime by name. It is a separate
213
+ # download from the agy CLI and needs its localharness_external helper from the
214
+ # same release, beside it or named by ANTIGRAVITY_HARNESS_PATH.
215
+ acpx_antigravity_helper() {
216
+ local runtime
217
+ if [ -n "${ANTIGRAVITY_HARNESS_PATH:-}" ]; then printf '%s' "$ANTIGRAVITY_HARNESS_PATH"; return 0; fi
218
+ runtime="$(command -v agy_acp_server.par 2>/dev/null)" || return 1
219
+ printf '%s/localharness_external' "$(dirname "$runtime")"
220
+ }
221
+
222
+ # The runtime reads skills and settings from GEMINI_HOME, so every run gets a
223
+ # private one, deleted with the caller's scratch. A file-backed login (no macOS
224
+ # keychain) is read only from there, so it is copied in; refreshes are not
225
+ # written back, so concurrent peers cannot clobber the user's token.
226
+ acpx_antigravity_home() { # <private-dir> -> GEMINI_HOME path
227
+ local home="$1/gemini-home"
228
+ local token="${GEMINI_HOME:-${HOME:-}/.gemini}/antigravity-acp/acp_token.json"
229
+ (umask 077; mkdir -p "$home/antigravity-acp") || return 1
230
+ printf '{"auth":{"type":"oauth-personal"}}\n' > "$home/antigravity-acp/settings.json" || return 1
231
+ if [ -f "$token" ]; then
232
+ (umask 077; cp "$token" "$home/antigravity-acp/acp_token.json") || return 1
233
+ fi
234
+ printf '%s' "$home"
235
+ }
236
+
237
+ acpx_unavailable() { # <shared|route> <reason>
238
+ ACPX_SCOPE="$1"; ACPX_UNAVAILABLE="$2"
239
+ return 1
240
+ }
241
+
242
+ # acpx lets ~/.acpx/config.json and <cwd>/.acpxrc.json replace a built-in
243
+ # agent's launch command; a route that would run something else is skipped.
244
+ # opencode launches the installed CLI by path, which no config entry overrides.
245
+ acpx_config_guard() { # <route> <cwd>
246
+ local agent file
247
+ [ "$1" = opencode ] && return 0
248
+ agent="$(acpx_agent "$1")" || return 1
249
+ for file in "${HOME:-}/.acpx/config.json" "$2/.acpxrc.json"; do
250
+ [ -e "$file" ] || continue
251
+ if ! jq -e --arg a "$agent" '(.agents // {}) | keys | map(ascii_downcase) | index($a) == null' "$file" >/dev/null 2>&1; then
252
+ acpx_unavailable route "$file overrides or cannot be checked for the '$agent' agent launch"
253
+ return 1
254
+ fi
255
+ done
256
+ }
257
+
258
+ acpx_preflight() { # <route> <cwd>; nothing is sent to a provider here
259
+ local v major minor
260
+ ACPX_UNAVAILABLE=""; ACPX_SCOPE=""
261
+ command -v node >/dev/null 2>&1 || { acpx_unavailable shared "node not found; acpx needs Node 22.13 or newer"; return 1; }
262
+ v="$(node -p 'process.versions.node' 2>/dev/null)"
263
+ major="${v%%.*}"; minor="${v#*.}"; minor="${minor%%.*}"
264
+ case "$major$minor" in ''|*[!0-9]*) acpx_unavailable shared "cannot read the Node version; acpx needs Node 22.13 or newer"; return 1 ;; esac
265
+ if [ "$major" -lt 22 ] || { [ "$major" -eq 22 ] && [ "$minor" -lt 13 ]; }; then
266
+ acpx_unavailable shared "Node $v is too old; acpx needs Node 22.13 or newer"
267
+ return 1
268
+ fi
269
+ command -v npx >/dev/null 2>&1 || { acpx_unavailable shared "npx not found; acpx runs through npx"; return 1; }
270
+ if [ "$1" = antigravity ]; then
271
+ case "$(uname -s 2>/dev/null)" in
272
+ MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "the Antigravity route is not yet supported on native Windows"; return 1 ;;
273
+ esac
274
+ route_available "$1" || { acpx_unavailable route "Antigravity's ACP runtime (agy_acp_server.par) is not on PATH; it is a separate download from the agy CLI (run ce-setup for the link)"; return 1; }
275
+ [ -x "$(acpx_antigravity_helper)" ] || { acpx_unavailable route "Antigravity's localharness_external helper is not beside agy_acp_server.par or at ANTIGRAVITY_HARNESS_PATH; install both from the same release (run ce-setup for the link)"; return 1; }
276
+ fi
277
+ route_available "$1" || { acpx_unavailable route "the agent CLI for route '$1' is not installed"; return 1; }
278
+ if [ "$1" = opencode ]; then
279
+ case "$(uname -s 2>/dev/null)" in
280
+ MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "opencode launches through acpx's raw --agent command, which acpx rejects on native Windows"; return 1 ;;
281
+ esac
282
+ fi
283
+ acpx_config_guard "$1" "$2"
284
+ }
285
+
286
+ # Claude's ACP adapter always loads project settings, so the launch goes
287
+ # through a wrapper that adds --safe-mode.
288
+ acpx_claude_wrapper() { # <private-dir> -> wrapper path
289
+ local real wrapper="$1/claude-safe-mode"
290
+ real="$(command -v claude)" || return 1
291
+ printf '#!/bin/sh\nexec %s --safe-mode "$@"\n' "$(printf '%q' "$real")" > "$wrapper" && chmod 700 "$wrapper" || return 1
292
+ printf '%s' "$wrapper"
293
+ }
294
+
295
+ # npm resolves offline-first, so cached metadata older than the acpx pin or a
296
+ # version an adapter's dependencies need fails with ETARGET before anything is
297
+ # sent. One launch with fresh metadata heals the cache. npm ignores
298
+ # prefer_online while prefer_offline is set, so the refresh turns it off.
299
+ acpx_stale_npm_metadata() { # <stderr-log> <stream-log>
300
+ [ "$ACPX_NPM_REFRESH" = true ] && return 1
301
+ grep -qE 'ETARGET|notarget|No matching version found' "$1" "$2" 2>/dev/null
302
+ }
303
+
304
+ # Prints the NUL-delimited argv prefix through `npx acpx@<pin>` and its global
305
+ # flags; the worker appends permission flags, `--model`, the agent, and exec options.
306
+ # The codex and claude adapters are pointed at the installed CLIs so the pin
307
+ # governs the transport, not which models the agent can serve.
308
+ acpx_base_argv() { # <route> <cwd> <mcp-config> <timeout-secs> <claude-wrapper> <gemini-home>
309
+ printf '%s\0' env
310
+ # An ambient Google API key or cloud project would bill it instead of the
311
+ # Antigravity subscription. env takes -u only before its first assignment.
312
+ case "$1" in
313
+ antigravity) printf '%s\0' -u GEMINI_API_KEY -u GOOGLE_API_KEY -u GOOGLE_APPLICATION_CREDENTIALS \
314
+ -u GOOGLE_CLOUD_PROJECT -u GOOGLE_CLOUD_LOCATION -u GOOGLE_GENAI_USE_VERTEXAI -u AGY_LLM_GATEWAY_URL ;;
315
+ esac
316
+ if [ "$ACPX_NPM_REFRESH" = true ]; then
317
+ printf '%s\0' npm_config_prefer_offline=false npm_config_fetch_retries=0 npm_config_prefer_online=true
318
+ else
319
+ printf '%s\0' npm_config_prefer_offline=true npm_config_fetch_retries=0
320
+ fi
321
+ case "$1" in
322
+ # codex-acp ignores app-server CLI flags.
323
+ codex) printf '%s\0' "CODEX_PATH=$(command -v codex)" 'CODEX_CONFIG={"features":{"plugins":false}}' ;;
324
+ claude) printf '%s\0' "CLAUDE_CODE_EXECUTABLE=$5" ;;
325
+ antigravity) printf '%s\0' "GEMINI_HOME=$6" "ANTIGRAVITY_HARNESS_PATH=$(acpx_antigravity_helper)" ;;
326
+ esac
327
+ printf '%s\0' npx -y "acpx@$ACPX_VERSION" --cwd "$2" --format json --mcp-config "$3" --timeout "$4"
328
+ }
329
+
330
+ acpx_agent_argv() { # <route>
331
+ # The preflight refuses opencode when it is not installed; the bare name keeps
332
+ # --emit-adapter output readable on a machine without it.
333
+ if [ "$1" = opencode ]; then printf '%s\0' --agent "$(command -v opencode || printf opencode) acp" exec
334
+ else printf '%s\0' "$(acpx_agent "$1")" exec; fi
335
+ }
336
+
337
+ # The run's outcome is the result of its own session/prompt request, matched by
338
+ # id because --model adds a session/set_model request before the prompt.
339
+ acpx_outcome() { # <stream-log> -> end_turn | <other stopReason> | error | incomplete | not-sent
340
+ local id outcome
341
+ id="$(jq -R 'fromjson? | select(.method == "session/prompt") | .id' "$1" 2>/dev/null | tail -1)"
342
+ [ -n "$id" ] || { printf 'not-sent'; return 0; }
343
+ outcome="$(jq -rR --argjson id "$id" 'fromjson? | select(.method == null and .id == $id) | if .error then "error" else (.result.stopReason // "error") end' "$1" 2>/dev/null | tail -1)"
344
+ printf '%s' "${outcome:-incomplete}"
345
+ }
346
+
347
+ # Antigravity holds session/new open while it waits for a browser sign-in it
348
+ # will never get, so a run without a usable login would otherwise sit out the
349
+ # whole idle cap. Nothing has been sent while the session is pending.
350
+ ACPX_SESSION_START_SECS="${CROSS_MODEL_SESSION_START_SECS:-60}"
351
+ acpx_session_state() { # <stream-log> -> starting | pending | open
352
+ local id
353
+ jq -eR 'fromjson? | select(.result.protocolVersion? != null)' "$1" >/dev/null 2>&1 || { printf 'starting'; return 0; }
354
+ id="$(jq -R 'fromjson? | select(.method == "session/new") | .id' "$1" 2>/dev/null | tail -1)"
355
+ if [ -n "$id" ] && jq -eR --argjson id "$id" 'fromjson? | select(.method == null and .id == $id)' "$1" >/dev/null 2>&1; then
356
+ printf 'open'
357
+ else
358
+ printf 'pending'
359
+ fi
360
+ }
361
+
362
+ acpx_text() { # <stream-log> <outfile>: the agent's reply text
363
+ jq -jR 'fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty' "$1" > "$2" 2>/dev/null
364
+ }
365
+
366
+ # Model ids the adapter reports in the prompt result's _meta. acpx documents
367
+ # _meta as adapter-defined, so these are the adapter's assertion, not proof.
368
+ acpx_served_models() { # <stream-log> -> "<model> <tokens>" lines
369
+ jq -rR 'fromjson? | select(.result.stopReason?) | .result._meta // {} |
370
+ ((.quota.model_usage // [])[] | "\(.model) \(.token_count.totalTokens // 0)"),
371
+ (.modelId // empty | "\(.) 0")' "$1" 2>/dev/null
372
+ }
373
+
374
+ # Bounded failure evidence from the stream. The stream echoes the outbound
375
+ # prompt, so only error messages and the stop reason are read from it.
376
+ acpx_failure_evidence() { # <stream-log>
377
+ local evidence
378
+ evidence="$(jq -rR 'fromjson? | (.error.message? // empty), (.result.stopReason? // empty | select(. != "end_turn") | "stopReason=" + .)' "$1" 2>/dev/null | tr '\n' ' ')"
379
+ evidence="${evidence% }"
380
+ [ "${#evidence}" -gt 300 ] && evidence="${evidence:0:147} ... ${evidence: -147}"
381
+ printf '%s' "$evidence"
382
+ }
383
+
384
+ # codex-acp streams a provider rejection (such as a model the account may not
385
+ # use) as reply text and still ends the turn with end_turn, so only the reply
386
+ # carries it. Bounded inside jq so a long reply never enters a bash string.
387
+ acpx_reply_evidence() { # <stream-log>
388
+ jq -nrR '[inputs | fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty]
389
+ | join("") | gsub("\\s+"; " ") | ltrimstr(" ") | rtrimstr(" ")
390
+ | if length > 300 then .[0:147] + " ... " + .[-147:] else . end' "$1" 2>/dev/null
391
+ }
392
+
393
+ acpx_advertised_models() { # <stream-log>
394
+ jq -nrR '[inputs | fromjson? | .result.models.availableModels[]?.modelId | sub("\\[.*$"; "")]
395
+ | reduce .[] as $m ([]; if any(.[]; . == $m) then . else . + [$m] end)
396
+ | join(", ") | if length > 300 then .[0:296] + " ..." else . end' "$1" 2>/dev/null
397
+ }
398
+
399
+ # Exit 5 is acpx's permission refusal. Paired with stopReason cancelled, the
400
+ # refusal ended the turn. Exit 5 with end_turn means the turn finished. A
401
+ # cancelled turn with any other exit is an interrupt.
402
+ acpx_permission_cancel_reason() { # <stream-log> <exit-code>
403
+ [ "$(acpx_outcome "$1")" = cancelled ] && [ "$2" = 5 ] || return 0
404
+ printf '%s' "permission request cancelled the turn"
405
+ }
406
+
407
+ acpx_node_startup_line() { # <stderr-log>
408
+ local line=""
409
+ line="$(grep -m1 -E '^Error ' "$1" 2>/dev/null)"
410
+ [ -n "$line" ] || line="$(grep -m1 -E '^node:' "$1" 2>/dev/null)"
411
+ printf '%s' "$line"
412
+ }
413
+ # --- end acpx transport -----------------------------------------------------
414
+
415
+ # --- model-identity receipt ---------------------------------------------------
416
+ # "Which model ran" comes from the adapter's _meta report, recorded as the
417
+ # served id; it is never filled from the requested value. Prefer the served id
418
+ # in the requested family (Claude also reports an auxiliary Haiku); otherwise
419
+ # record the model that used the most tokens and warn about the mismatch.
420
+ MODEL_ACTUAL="unverified"
421
+ extract_model_receipt() { # <route>; reads $PEERLOG, sets MODEL_ACTUAL
422
+ local requested family served m tokens most=-1 heaviest=""
423
+ MODEL_ACTUAL="unverified"
424
+ requested="$(route_model "$1")"
425
+ family="${requested%%\[*}"
426
+ case "$family" in
427
+ fable|opus|sonnet|haiku) family="claude-$family" ;;
428
+ esac
429
+ served="$(acpx_served_models "$PEERLOG")"
430
+ [ -n "$served" ] || return 0
431
+ while read -r m tokens; do
432
+ [ -n "$m" ] || continue
433
+ case "$m" in
434
+ "$family"|"$family"-*|"$family"\[*) MODEL_ACTUAL="$m"; return 0 ;;
435
+ esac
436
+ if [ "${tokens:-0}" -gt "$most" ]; then most="${tokens:-0}"; heaviest="$m"; fi
437
+ done <<EOF
438
+ $served
439
+ EOF
440
+ MODEL_ACTUAL="$heaviest"
441
+ # A route that requested no model (Cursor default, OpenCode auto) has nothing to mismatch.
442
+ [ "$requested" = auto ] && return 0
443
+ log "WARNING: model mismatch - requested $requested, adapter reported $MODEL_ACTUAL; reconcile must surface this"
444
+ }
445
+
446
+ # --- adapter argv (single source of truth for route flags) -----------------
447
+ # Zero-tool intent on every route: the peer runs from an empty per-peer
448
+ # workspace, acpx denies every permission request, and each adapter runs in its
449
+ # read-only mode where it has one (codex mode=read-only, claude mode=default,
450
+ # cursor mode=ask, opencode mode=plan with edits and web denied). Tools that
451
+ # need no permission stay usable; references/cross-model-review.md records
452
+ # which routes can still read files or reach the web. PEER_WORKDIR /
453
+ # PROMPT_FILE / MCP_CONFIG / CLAUDE_WRAPPER are resolved by the caller
454
+ # (placeholders in --emit-adapter mode).
455
+ adapter_argv() {
456
+ local route="$1" model
457
+ acpx_agent "$route" >/dev/null || return 1
458
+ model="$(route_model "$route")"
459
+ case "$route" in
460
+ # Denying bash or read outright makes OpenCode's free tier reject the
461
+ # session over ACP, so those stay behind plan mode and --deny-all.
462
+ opencode) printf '%s\0' env OPENCODE_DISABLE_PROJECT_CONFIG=1 \
463
+ 'OPENCODE_CONFIG_CONTENT={"permission":{"edit":"deny","webfetch":"deny","websearch":"deny","task":"deny"}}' ;;
464
+ esac
465
+ acpx_base_argv "$route" "$PEER_WORKDIR" "$MCP_CONFIG" "$HARD_SECS" "$CLAUDE_WRAPPER" "$GEMINI_HOME_DIR"
466
+ printf '%s\0' --deny-all
467
+ case "$route" in
468
+ claude) printf '%s\0' --max-turns 15 ;;
469
+ esac
470
+ [ "$model" = auto ] || printf '%s\0' --model "$model"
471
+ acpx_agent_argv "$route"
472
+ case "$route" in
473
+ codex) printf '%s\0' --config-option mode=read-only --config-option "reasoning_effort=$(route_effort codex)" ;;
474
+ # Claude otherwise starts in the user's default permission mode, which can
475
+ # skip permission requests entirely and leave --deny-all nothing to deny.
476
+ claude) printf '%s\0' --config-option mode=default --config-option "effort=$(route_effort claude)" ;;
477
+ grok-cli) printf '%s\0' --config-option "reasoning_effort=$(route_effort grok-cli)" ;;
478
+ grok-cursor|cursor|composer) printf '%s\0' --config-option mode=ask ;;
479
+ opencode) printf '%s\0' --config-option mode=plan
480
+ [ -z "${CROSS_MODEL_EFFORT_OVERRIDE:-}" ] || printf '%s\0' --config-option "effort=$(route_effort opencode)" ;;
481
+ antigravity) printf '%s\0' --config-option mode=default --config-option "thinking_level=$(route_effort antigravity)" ;;
482
+ esac
483
+ printf '%s\0' --file "$PROMPT_FILE"
484
+ }
485
+
486
+ # Accept a host-discovered replacement only for its declared target and model
487
+ # family. An override for another target is ignored rather than leaking across
488
+ # routes; an unbound or cross-family override is invalid for its own route.
489
+ # A codex id may carry the serving provider's own namespace (openai.gpt-...,
490
+ # openai/gpt-...) when the CLI routes through a non-default model_provider; the
491
+ # family segment after the namespace is still checked.
492
+ validate_model_override() {
493
+ local route="$1" override="${CROSS_MODEL_MODEL_OVERRIDE:-}" override_target="${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" target
494
+ [ -n "$override" ] || { [ -z "$override_target" ]; return; }
495
+ [ -n "$override_target" ] || return 1
496
+ target="$(route_target "$route")" || return 1
497
+ [ "$override_target" = "$target" ] || return 0
498
+ [ "$target" != "cursor" ] || return 1
499
+ case "$route:$override" in
500
+ codex:gpt-*|codex:o[0-9]*|codex:*[./]gpt-*|codex:*[./]o[0-9]*|claude:fable|claude:opus|claude:sonnet|claude:haiku|claude:claude-*|grok-cli:grok-*|grok-cursor:grok-*|grok-cursor:cursor-grok-*|composer:composer-*|opencode:*/*|antigravity:gemini-*) ;;
501
+ *) return 1 ;;
502
+ esac
503
+ }
504
+
505
+ # Accept an effort override only where the route's ACP adapter exposes an
506
+ # effort option and the value is one it advertises (claude effort:
507
+ # low|medium|high|xhigh|max; codex reasoning_effort: low|medium|high|xhigh|max|ultra;
508
+ # grok reasoning_effort: low|medium|high|xhigh; antigravity thinking_level:
509
+ # low|medium|high, checked 2026-10-14; opencode 2.x effort: none|low|medium|
510
+ # high|xhigh|max, checked 2026-10-23). Checked 2026-10-06 against the
511
+ # session options each adapter reports through acpx 0.19.4. Codex levels vary
512
+ # and OpenCode levels vary per model, and OpenCode 1.x has no effort option,
513
+ # so a listed level can still be refused before the prompt is sent.
514
+ # Cursor routes fix effort in the model preset, so any override there is
515
+ # invalid for the route rather than silently dropped. Empty means "no override".
516
+ validate_effort_override() {
517
+ local route="$1" effort="${CROSS_MODEL_EFFORT_OVERRIDE:-}"
518
+ [ -n "$effort" ] || return 0
519
+ case "$route:$effort" in
520
+ claude:low|claude:medium|claude:high|claude:xhigh|claude:max) ;;
521
+ codex:low|codex:medium|codex:high|codex:xhigh|codex:max|codex:ultra) ;;
522
+ grok-cli:low|grok-cli:medium|grok-cli:high|grok-cli:xhigh) ;;
523
+ antigravity:low|antigravity:medium|antigravity:high) ;;
524
+ opencode:none|opencode:low|opencode:medium|opencode:high|opencode:xhigh|opencode:max) ;;
525
+ *) return 1 ;;
526
+ esac
527
+ }
528
+
529
+ # --- --emit-adapter <route>: print the argv, no model call, no side effects --
530
+ if [ "${1:-}" = "--emit-adapter" ]; then
531
+ PEER_WORKDIR="<peer-workdir>"
532
+ PROMPT_FILE="<prompt-file>"; MCP_CONFIG="<mcp-config>"; CLAUDE_WRAPPER="<claude-safe-mode-wrapper>"; GEMINI_HOME_DIR="<gemini-home>"
533
+ HARD_SECS="${CROSS_MODEL_HARD_SECS:-1200}"
534
+ route="${2:-}"
535
+ validate_model_override "$route" 2>/dev/null || { echo "model override '${CROSS_MODEL_MODEL_OVERRIDE:-}' not compatible with route '$route'" >&2; exit 2; }
536
+ validate_effort_override "$route" 2>/dev/null || { echo "effort override '${CROSS_MODEL_EFFORT_OVERRIDE:-}' not compatible with route '$route'" >&2; exit 2; }
537
+ # adapter_argv emits NUL-delimited argv (can't be captured in a shell var), so
538
+ # validate the route first, then render for humans with NUL -> space.
539
+ adapter_argv "$route" >/dev/null 2>&1 || { echo "unknown route '$route' (want codex|claude|grok-cli|grok-cursor|cursor|composer|opencode|antigravity)" >&2; exit 2; }
540
+ adapter_argv "$route" | tr '\0' ' '; echo
541
+ exit 0
542
+ fi
543
+
544
+ HOST_PROVIDER="${1:-}"
545
+ HOST_HARNESS="${CROSS_MODEL_HOST_HARNESS:-unknown}"
546
+ CANDIDATES="${2:-}"
547
+ REVIEWER_NAME="${3:-}"
548
+ DOC_PATH="${4:-}"
549
+ DOC_TYPE="${5:-}"
550
+ ORIGIN="${6:-}"
551
+ RUN_DIR="${7:-}"
552
+
553
+ # --- validate inputs -------------------------------------------------------
554
+ [ -n "$REVIEWER_NAME" ] || skip "no reviewer-name given; skipping"
555
+ [ -n "$DOC_PATH" ] && [ -f "$DOC_PATH" ] || skip "document '${DOC_PATH:-<empty>}' not readable on disk; skipping"
556
+ : "${DOC_TYPE:=unified-plan}"
557
+ : "${ORIGIN:=none}"
558
+ [ -n "$RUN_DIR" ] || skip "run-dir not given; skipping"
559
+ # Create the scratch run-dir rather than skipping when it doesn't exist yet:
560
+ # ce-doc-review (unlike ce-code-review) has no pre-existing run-artifact dir, and
561
+ # the caller passes the fresh absolute run dir resolved by the skill.
562
+ # Requiring it to pre-exist would silently no-op the whole pass (no fold-in files).
563
+ mkdir -p "$RUN_DIR" 2>/dev/null
564
+ [ -d "$RUN_DIR" ] || skip "run-dir '$RUN_DIR' could not be created; skipping"
565
+ command -v jq >/dev/null 2>&1 || skip "jq not installed; skipping"
566
+
567
+ # Validate the host identity tuple. An unknown serving family is allowed, but
568
+ # normalization marks every result non-independent.
569
+ case "$HOST_PROVIDER" in
570
+ codex|claude|grok|composer|gemini|deepseek|unknown) ;;
571
+ *) skip "host serving family '${HOST_PROVIDER:-<empty>}' invalid (want codex|claude|grok|composer|gemini|deepseek|unknown); skipping cross-model pass" ;;
572
+ esac
573
+ case "$HOST_HARNESS" in
574
+ codex|claude|grok|cursor|opencode|antigravity|omp|dsh|unknown) ;;
575
+ *) skip "host harness '$HOST_HARNESS' invalid (want codex|claude|grok|cursor|opencode|antigravity|omp|dsh|unknown); skipping cross-model pass" ;;
576
+ esac
577
+ [ "$HOST_PROVIDER" != "unknown" ] || skip "host serving family unattested; automatic cross-model review skipped"
578
+
579
+ # --- derive persona-brief filename from the allowlisted reviewer-name -------
580
+ # Never a caller argument -> no path-traversal / arbitrary-file-read surface.
581
+ case "$REVIEWER_NAME" in
582
+ security-lens) PERSONA_FILE="security-lens-reviewer" ;;
583
+ adversarial) PERSONA_FILE="adversarial-document-reviewer" ;;
584
+ product-lens) PERSONA_FILE="product-lens-reviewer" ;;
585
+ whole-doc) PERSONA_FILE="whole-doc-reviewer" ;; # broad whole-document sweep (R20/U9); embeds the full doc, no in-process twin
586
+ *) skip "reviewer-name '$REVIEWER_NAME' is not a cross-model reviewer (want security-lens|adversarial|product-lens|whole-doc); skipping" ;;
587
+ esac
588
+
589
+ # --- self-locate skill root + canonical sibling files ----------------------
590
+ SKILL_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || skip "cannot resolve skill root; skipping"
591
+ PERSONA="$SKILL_ROOT/references/personas/$PERSONA_FILE.md"
592
+ SCHEMA="$SKILL_ROOT/references/findings-schema.json"
593
+ [ -f "$PERSONA" ] || skip "persona brief not found at $PERSONA; skipping"
594
+ [ -f "$SCHEMA" ] || skip "findings schema not found at $SCHEMA; skipping"
595
+ SCHEMA_CONTENT="$(cat "$SCHEMA")" || skip "cannot read findings schema; skipping"
596
+
597
+ # The peer adapts on the same context slots (Document type / Origin) the in-process
598
+ # reviewer does, but the trio persona briefs only define adaptation for the bare
599
+ # `requirements`/`plan` values. The canonical context-slot rules -- which map
600
+ # `unified-*` onto their base branch, carry the unified slice-suppression rules, and
601
+ # define how to read non-path Origin values -- live only in the subagent template, so
602
+ # extract them from there (single source of truth) and fold them into the peer prompt.
603
+ # Best-effort: a missing block degrades unified/Origin scoping but must not fail the pass.
604
+ TEMPLATE="$SKILL_ROOT/references/subagent-template.md"
605
+ CONTEXT_SLOT_RULES="$(awk '/<context-slots-rules>/{f=1} f; /<\/context-slots-rules>/{if(f)exit}' "$TEMPLATE" 2>/dev/null)"
606
+ [ -n "$CONTEXT_SLOT_RULES" ] || log "context-slot rules not found in $TEMPLATE; peer prompt will omit unified/Origin adaptation rules"
607
+
608
+ # The trio persona briefs defer their confidence rubric + false-positive catalog to
609
+ # the template's <output-contract> block, which every in-process reviewer receives.
610
+ # The isolated peer can't resolve that reference on its own, so embed it too --
611
+ # otherwise the peer calibrates anchors / suppresses false positives differently from
612
+ # its in-process twin, weakening the cross-model agreement signal (R13 parity).
613
+ OUTPUT_CONTRACT_RULES="$(awk '/<output-contract>/{f=1} f; /<\/output-contract>/{if(f)exit}' "$TEMPLATE" 2>/dev/null)"
614
+ [ -n "$OUTPUT_CONTRACT_RULES" ] || log "output-contract not found in $TEMPLATE; peer prompt omits the shared confidence rubric / FP catalog (calibration may differ from the twin)"
615
+
616
+ # --- resolve which provider(s) to run (exclude host, allowlist, availability) --
617
+ ALLOW="${CROSS_MODEL_PEERS:-}" # optional egress allowlist (R19)
618
+ MAX_PEERS="${CROSS_MODEL_MAX_PEERS:-1}" # default 1; clamped 0..2 (hard cap)
619
+ case "$MAX_PEERS" in ''|*[!0-9]*) MAX_PEERS=1 ;; esac
620
+ [ "$MAX_PEERS" -gt 2 ] && MAX_PEERS=2
621
+
622
+ in_csv() { case ",$2," in *",$1,"*) return 0 ;; *) return 1 ;; esac; }
623
+ # The cursor-agent route egresses content through Cursor even when the *model* is
624
+ # grok (grok-via-cursor-agent). CROSS_MODEL_PEERS is an egress boundary (R19), not
625
+ # just a model-provider filter, so the grok->cursor-agent transport is off-limits
626
+ # under an allowlist that does not sanction Cursor. Cursor egress is sanctioned when
627
+ # no allowlist is set, or when 'composer' (the Cursor-native provider) is allowlisted
628
+ # -- either way the user has accepted that content may reach Cursor.
629
+ cursor_egress_ok() { [ -z "$ALLOW" ] || in_csv cursor "$ALLOW" || in_csv composer "$ALLOW"; }
630
+
631
+ # Soft size gate: peer prompt embeds the full document. Over-budget docs skip
632
+ # cleanly (R11) rather than collapsing silently inside the provider context window.
633
+ MAX_DOC_CHARS="${CROSS_MODEL_MAX_DOC_CHARS:-200000}"
634
+ case "$MAX_DOC_CHARS" in ''|*[!0-9]*) MAX_DOC_CHARS=200000 ;; esac
635
+ DOC_CHARS="$(wc -c <"$DOC_PATH" | tr -d '[:space:]')"
636
+ if [ "$DOC_CHARS" -gt "$MAX_DOC_CHARS" ]; then
637
+ skip "document is ${DOC_CHARS} bytes (limit ${MAX_DOC_CHARS}); skipping cross-model pass rather than truncating"
638
+ fi
639
+
640
+ # The Codex desktop app (Codex.app, or ChatGPT.app since the July 2026 merger)
641
+ # ships `codex` at Contents/Resources without linking it onto PATH (#1272).
642
+ # Append, never prepend, so a PATH-installed CLI stays authoritative.
643
+ # CROSS_MODEL_CODEX_APP_DIRS (colon-separated) overrides the probed dirs.
644
+ if ! command -v codex >/dev/null 2>&1; then
645
+ OLDIFS="$IFS"; IFS=':'
646
+ for d in ${CROSS_MODEL_CODEX_APP_DIRS-"${HOME:-}/Applications/ChatGPT.app/Contents/Resources:/Applications/ChatGPT.app/Contents/Resources:${HOME:-}/Applications/Codex.app/Contents/Resources:/Applications/Codex.app/Contents/Resources"}; do
647
+ if [ -n "$d" ] && [ -x "$d/codex" ]; then PATH="${PATH:+$PATH:}$d"; export PATH; break; fi
648
+ done
649
+ IFS="$OLDIFS"
650
+ fi
651
+
652
+ provider_available() {
653
+ case "$1" in
654
+ codex) command -v codex >/dev/null 2>&1 ;;
655
+ claude) command -v claude >/dev/null 2>&1 ;;
656
+ grok) command -v grok >/dev/null 2>&1 || { cursor_egress_ok && command -v cursor-agent >/dev/null 2>&1; } ;;
657
+ cursor) command -v cursor-agent >/dev/null 2>&1 ;;
658
+ composer) command -v cursor-agent >/dev/null 2>&1 ;;
659
+ opencode) command -v opencode >/dev/null 2>&1 ;;
660
+ antigravity) command -v agy_acp_server.par >/dev/null 2>&1 ;;
661
+ *) return 1 ;;
662
+ esac
663
+ }
664
+
665
+ # Collect the ordered list of reachable candidates (installed, allowlisted,
666
+ # non-host, deduped). Discovery reports the first MAX_PEERS; live egress uses the
667
+ # host-sanctioned fixed route's eligible target. `command -v` proves only local
668
+ # route availability; a fixed route that later fails returns to the host instead
669
+ # of changing recipients here.
670
+ # `for p in $CANDIDATES` splits the CSV once at loop start under IFS=',', so IFS
671
+ # stays comma for the whole loop; nothing in the body does IFS-sensitive splitting.
672
+ SELECTED="" # space-separated ordered reachable candidates (bash 3.2-safe)
673
+ OLDIFS="$IFS"; IFS=','
674
+ for p in $CANDIDATES; do
675
+ p="$(printf '%s' "$p" | tr -d '[:space:]')"
676
+ [ -n "$p" ] || continue
677
+ case "$p" in codex|claude|grok|cursor|composer|opencode|antigravity) ;; *) log "ignoring unknown target '$p' in candidates"; continue ;; esac
678
+ [ "$HOST_PROVIDER" != "unknown" ] && [ "$(target_serving_family "$p")" = "$HOST_PROVIDER" ] && continue
679
+ # An Antigravity host can serve several families; never send it to itself.
680
+ [ "$p" = antigravity ] && [ "$HOST_HARNESS" = antigravity ] && continue
681
+ case " $SELECTED " in *" $p "*) continue ;; esac # dedup
682
+ if [ -n "$ALLOW" ] && ! in_csv "$p" "$ALLOW"; then log "provider '$p' not in CROSS_MODEL_PEERS allowlist; skipping"; continue; fi
683
+ if ! provider_available "$p"; then log "provider '$p' has no installed route; skipping"; continue; fi
684
+ SELECTED="$SELECTED $p"
685
+ done
686
+ IFS="$OLDIFS"
687
+ SELECTED="$(printf '%s' "$SELECTED" | sed 's/^ *//')"
688
+
689
+ [ "$MAX_PEERS" -ge 1 ] || skip "CROSS_MODEL_MAX_PEERS=0; cross-model pass disabled"
690
+ [ -n "$SELECTED" ] || skip "no different-provider peer reachable (host=$HOST_PROVIDER, candidates='$CANDIDATES'); the pass needs a peer agent CLI on PATH (codex, claude, grok, cursor-agent, opencode, or the Antigravity ACP runtime), not an API key alone; skipping"
691
+ log "reachable cross-model candidates for lens $REVIEWER_NAME: $SELECTED (host $HOST_PROVIDER excluded; up to $MAX_PEERS successful peer(s))"
692
+
693
+ # first_n <max> <space-separated list> -> the first <max> tokens.
694
+ first_n() {
695
+ local max="$1"; shift; local n=0 out=""
696
+ for t in "$@"; do [ "$n" -ge "$max" ] && break; out="$out $t"; n=$((n + 1)); done
697
+ printf '%s' "${out# }"
698
+ }
699
+
700
+ # Diagnostic: resolve selection only, no model call, no side effects (used by the
701
+ # selection tests, which stub the route CLIs on PATH). Prints the fixed peer set
702
+ # (the first MAX_PEERS reachable candidates).
703
+ if [ -n "${CROSS_MODEL_DRY_RUN:-}" ]; then
704
+ printf 'RESOLVED_PEERS: %s\n' "$(first_n "$MAX_PEERS" $SELECTED)"
705
+ exit 0
706
+ fi
707
+
708
+ # --- compose the peer prompt from the canonical persona (single source) ----
709
+ # The full findings schema is embedded so the peer knows every required field.
710
+ # The document content is embedded directly inside the <review-context> block,
711
+ # with the same context slots the in-process persona adapts on. The reviewer
712
+ # field is normalized to <reviewer-name>-<provider> after the run, so the prompt
713
+ # asks only for the short name.
714
+ # Private scratch holds the prompt, the ACP stream, and the launch files. It is
715
+ # kept apart from the peer's empty working directory (created per provider).
716
+ SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/xmodel-doc-scratch-XXXXXX")" || skip "private peer scratch unavailable; skipping"
717
+ PEER_WORKDIR=""
718
+ RAW_OUT=""
719
+ cleanup_temp() {
720
+ rm -rf "$SCRATCH"
721
+ [ -n "$PEER_WORKDIR" ] && rm -rf "$PEER_WORKDIR"
722
+ }
723
+ trap 'cleanup_temp' EXIT
724
+ chmod 700 "$SCRATCH" 2>/dev/null || skip "cannot make peer scratch private; skipping"
725
+ PROMPT_FILE="$SCRATCH/prompt.md"
726
+ # acpx prints the ACP stream on stdout; npm and adapter diagnostics go to stderr.
727
+ PEERLOG="$SCRATCH/stdout.log"
728
+ PEERERR="$SCRATCH/stderr.log"
729
+ TEXT_OUT="$SCRATCH/reply.txt"
730
+ MCP_CONFIG="$SCRATCH/mcp.json"
731
+ printf '{"mcpServers":[]}\n' > "$MCP_CONFIG"
732
+ CLAUDE_WRAPPER=""
733
+ GEMINI_HOME_DIR=""
734
+ RUN_SUCCEEDED=false
735
+ PROVIDER_OUTCOME="failed"
736
+ # Basename only in the peer prompt: content is already embedded (KTD3). An absolute
737
+ # path would give a read-capable route a repo coordinate to walk from.
738
+ DOC_BASENAME="$(basename "$DOC_PATH")"
739
+ {
740
+ cat "$PERSONA"
741
+ printf '\n\n---\n\n'
742
+ # Shared output-contract (confidence rubric + FP catalog) the persona brief defers
743
+ # to, so the peer calibrates like its in-process twin.
744
+ [ -n "$OUTPUT_CONTRACT_RULES" ] && printf '%s\n\n' "$OUTPUT_CONTRACT_RULES"
745
+ printf 'This is an authorized document review of the maintainer\047s own repository.\n'
746
+ # The whole document is embedded below. Say so, so a peer never goes looking for the
747
+ # prompt or the document on disk: a read outside its workspace is a permission
748
+ # request that is denied, and a peer that stops to explain the denial returns no JSON.
749
+ printf 'The complete document is embedded below under "Document content". Do not call tools or read files; if any read is denied, ignore it and answer from the embedded text.\n'
750
+ printf 'Return ONE JSON object and nothing else (no prose, no code fence) matching this schema:\n\n'
751
+ printf '%s' "$SCHEMA_CONTENT"
752
+ printf '\n\nSet the top-level "reviewer" field to "%s" (it will be namespaced to the peer provider on fold-in).\n' "$REVIEWER_NAME"
753
+ printf '\n<review-context>\n'
754
+ printf 'Document type: %s\n' "$DOC_TYPE"
755
+ printf 'Document path: %s\n' "$DOC_BASENAME"
756
+ printf 'Origin: %s\n\n' "$ORIGIN"
757
+ printf '<prior-decisions>\nRound 1 — no prior decisions.\n</prior-decisions>\n\n'
758
+ printf 'Document content:\n'
759
+ cat "$DOC_PATH"
760
+ printf '\n</review-context>\n'
761
+ [ -n "$CONTEXT_SLOT_RULES" ] && printf '\n%s\n' "$CONTEXT_SLOT_RULES"
762
+ } > "$PROMPT_FILE"
763
+ # codex-acp's only refusal option is cancel, which ends the turn.
764
+ if [ "${CROSS_MODEL_FIXED_ROUTE:-}" = codex ]; then
765
+ printf '\nYour sandbox is read-only and no approval is available: never request escalated or out-of-sandbox permissions (for example to launch a browser, use the network, or write outside the repository). Any permission request ends this review with no result. When a check needs more access than the sandbox gives, reason from the source instead and name the unrun check in residual_risks.\n' >> "$PROMPT_FILE"
766
+ fi
767
+ chmod 600 "$PROMPT_FILE" "$MCP_CONFIG" 2>/dev/null || skip "cannot make peer scratch files private; skipping"
768
+
769
+ # --- run machinery -----------------------------------------------------------
770
+ # Every route streams the ACP session, so the idle cap is the liveness guard and
771
+ # HARD_SECS backstops a peer that stays productive past any useful budget. acpx
772
+ # gets the same budget as --timeout, but it applies that per phase, so this
773
+ # script's wall clock stays authoritative. The idle cap must exceed the peer's
774
+ # worst-case silent turn: a slow xhigh reasoning turn (Luna p95 ~242s, max
775
+ # ~419s) can go quiet past a low cap and be reaped before it answers.
776
+ #
777
+ # HARD_SECS is the ONE knob for the whole peer budget: the runner supervisor
778
+ # window and the orchestrator's shared deadline both derive from it (see
779
+ # references/cross-model-review.md), so raising it here raises all three.
780
+ IDLE_SECS="${CROSS_MODEL_IDLE_SECS:-480}"
781
+ HARD_SECS="${CROSS_MODEL_HARD_SECS:-1200}"
782
+
783
+ # Reap a backgrounded job's whole process group: TERM, then KILL after a grace.
784
+ # True while $1 is a live (non-zombie) process. kill -0 succeeds on zombies
785
+ # until wait reaps them, so idle polls must not treat zombies as still running.
786
+ # macOS/BSD often report defunct state as "Z+" (not bare "Z").
787
+ # Match peer-job-runner._pid_running: empty state after ps means not alive
788
+ # (avoids zombie spin). Fall back to kill -0 only when ps itself is missing.
789
+ peer_alive() {
790
+ local st
791
+ kill -0 "$1" 2>/dev/null || return 1
792
+ if ! command -v ps >/dev/null 2>&1; then
793
+ return 0
794
+ fi
795
+ # Git Bash's ps has no -o; fall back to its -p lookup, which has no zombie state.
796
+ if ! st="$(ps -o state= -p "$1" 2>/dev/null)"; then
797
+ ps -p "$1" >/dev/null 2>&1
798
+ return
799
+ fi
800
+ st="$(printf '%s' "$st" | tr -d ' \n')"
801
+ [ -n "$st" ] || return 1
802
+ [ "${st#Z}" = "$st" ]
803
+ }
804
+
805
+ reap() {
806
+ # Signal the process group and grace-poll without wait(). The caller alone
807
+ # wait()s the leader so PEER_EXIT is the real exit status — a second
808
+ # wait here would fail after we already reaped and mark healthy exits as
809
+ # timed-out (#1270 Bugbot). No background KILL timer: orphaned timers can
810
+ # hit recycled PIDs under bun --parallel.
811
+ local pid="$1" grp
812
+ if kill -TERM -- -"$pid" 2>/dev/null; then grp=1; else kill -TERM "$pid" 2>/dev/null || true; grp=0; fi
813
+ for _ in 1 2 3 4 5; do
814
+ if ! peer_alive "$pid"; then
815
+ # Leader exited/zombied — sweep any group survivors; caller wait()s.
816
+ [ "$grp" = 1 ] && kill -KILL -- -"$pid" 2>/dev/null || true
817
+ return 0
818
+ fi
819
+ sleep 1
820
+ done
821
+ if [ "$grp" = 1 ]; then kill -KILL -- -"$pid" 2>/dev/null; else kill -KILL "$pid" 2>/dev/null; fi
822
+ }
823
+
824
+ # TERM/INT: reap the live peer group, then exit cleanly (HUP remains ignored).
825
+ on_term() {
826
+ if [ -n "${_HEARTBEAT_PID:-}" ]; then
827
+ stop_heartbeat
828
+ fi
829
+ if [ -n "${ACTIVE_PEER_PID:-}" ]; then
830
+ log "received TERM/INT; reaping peer process group $ACTIVE_PEER_PID"
831
+ _term_peer="$ACTIVE_PEER_PID"
832
+ reap "$_term_peer" 2>/dev/null || true
833
+ # reap only signals the group; wait reaps the leader so it cannot orphan.
834
+ wait "$_term_peer" 2>/dev/null || true
835
+ ACTIVE_PEER_PID=""
836
+ fi
837
+ exit 0
838
+ }
839
+ trap 'on_term' TERM INT
840
+
841
+ # Build the CMD array for a route (bash 3.2-safe: no mapfile).
842
+ build_cmd() {
843
+ CMD=()
844
+ # NUL-delimited so a token containing spaces or newlines stays ONE argv element.
845
+ while IFS= read -r -d '' tok; do CMD+=("$tok"); done < <(adapter_argv "$1")
846
+ }
847
+
848
+ # --- liveness heartbeat -----------------------------------------------------
849
+ # The peer CLI streams into $PEERLOG (private), so nothing reaches this script's
850
+ # own stdout/stderr during a long model call. An outer supervisor that watches
851
+ # THIS process's output for liveness (the peer-job runner's out.log byte-growth
852
+ # idle window) would mistake a healthy multi-minute run for a wedge. A background
853
+ # writer emits one stderr line every CROSS_MODEL_HEARTBEAT_SECS (default 60s) so
854
+ # that liveness is visible; it is torn down as soon as the foreground wait returns,
855
+ # so it adds no latency to a fast run. Keep this block byte-identical across
856
+ # cross-model-adversarial-review.sh and cross-model-doc-review.sh (kernel parity).
857
+ _HEARTBEAT_PID=""
858
+ start_heartbeat() {
859
+ local every="${CROSS_MODEL_HEARTBEAT_SECS:-60}" parent_pid="$$"
860
+ # Floor to 1s: a non-numeric or 0 value would make `sleep` return instantly and
861
+ # spin the loop, flooding out.log into the runner's byte cap.
862
+ case "$every" in ''|*[!0-9]*) every=60 ;; esac; [ "$every" -lt 1 ] && every=1
863
+ _HEARTBEAT_READY=0
864
+ trap '_HEARTBEAT_READY=1' USR1
865
+ # Callers restore set +m after launching the peer, so without this the
866
+ # heartbeat inherits the worker pgid and kill -- -PID cannot reach the sleep.
867
+ local prev_m; case "$-" in *m*) prev_m=1;; *) prev_m=0;; esac
868
+ set -m
869
+ ( local t0 n sleeper=""
870
+ trap 'kill "${sleeper:-}" 2>/dev/null || true; exit 0' TERM INT
871
+ kill -USR1 "$parent_pid"
872
+ t0="$(date +%s)"
873
+ while kill -0 "$parent_pid" 2>/dev/null; do
874
+ sleep "$every" & sleeper=$!
875
+ wait "$sleeper" 2>/dev/null || exit 0
876
+ sleeper=""
877
+ kill -0 "$parent_pid" 2>/dev/null || break
878
+ n="$(date +%s)"; log "peer alive ($(( n - t0 ))s elapsed)"
879
+ done ) &
880
+ _HEARTBEAT_PID=$!
881
+ [ "$prev_m" = 0 ] && set +m
882
+ while [ "$_HEARTBEAT_READY" != 1 ] && kill -0 "$_HEARTBEAT_PID" 2>/dev/null; do sleep 0.01 || true; done
883
+ trap - USR1
884
+ }
885
+ stop_heartbeat() {
886
+ if [ -n "$_HEARTBEAT_PID" ]; then
887
+ # Leader-only TERM is deferred until the inner `wait $sleeper` returns, so
888
+ # the default 60s interval would block this wait. Signal the process group.
889
+ kill -- -"$_HEARTBEAT_PID" 2>/dev/null || kill "$_HEARTBEAT_PID" 2>/dev/null || true
890
+ wait "$_HEARTBEAT_PID" 2>/dev/null || true
891
+ fi
892
+ _HEARTBEAT_PID=""
893
+ }
894
+
895
+ run_peer_cmd() { # CMD already built for PEER_ROUTE; streams to PEERLOG, diagnostics to PEERERR
896
+ local prev; case "$-" in *m*) prev=1;; *) prev=0;; esac
897
+ SESSION_STALLED=false
898
+ set -m
899
+ # Start npx from private scratch: npx resolves packages from its working
900
+ # directory's node_modules and .npmrc first, and acpx gets the agent's cwd from --cwd.
901
+ ( cd "$(dirname "$PEERLOG")" && exec "${CMD[@]}" ) < /dev/null > "$PEERLOG" 2>"$PEERERR" &
902
+ local pid=$!
903
+ ACTIVE_PEER_PID="$pid"
904
+ [ "$prev" = 0 ] && set +m
905
+ start_heartbeat
906
+ local start last=-1 lastchg now size session=open
907
+ [ "$PEER_ROUTE" = antigravity ] && session=starting
908
+ start="$(date +%s)"; lastchg="$start"
909
+ while peer_alive "$pid"; do
910
+ now="$(date +%s)"; size="$(wc -c <"$PEERLOG" 2>/dev/null || echo 0)"
911
+ [ "$size" != "$last" ] && { last="$size"; lastchg="$now"; }
912
+ if [ $(( now - lastchg )) -ge "$IDLE_SECS" ]; then
913
+ log "peer output idle ${IDLE_SECS}s; reaping peer process group"; reap "$pid"; break
914
+ fi
915
+ if [ $(( now - start )) -ge "$HARD_SECS" ]; then
916
+ log "peer exceeded hard cap ${HARD_SECS}s; reaping peer process group"; reap "$pid"; break
917
+ fi
918
+ if [ "$session" != open ] && [ $(( now - start )) -ge "$ACPX_SESSION_START_SECS" ]; then
919
+ session="$(acpx_session_state "$PEERLOG")"
920
+ if [ "$session" = pending ]; then
921
+ SESSION_STALLED=true
922
+ log "Antigravity opened no session within ${ACPX_SESSION_START_SECS}s; reaping peer process group"; reap "$pid"; break
923
+ fi
924
+ fi
925
+ sleep 1
926
+ done
927
+ wait "$pid" 2>/dev/null
928
+ PEER_EXIT=$?
929
+ # Sweep any survivor the provider left in its OWN process group. `set -m` puts
930
+ # the provider in a separate pgid, and on a clean worker exit the runner's
931
+ # final sweep only kills the worker's pgid while a group-orphan reparents off
932
+ # the worker's process tree -- so it must be reaped here, where the pgid is
933
+ # known. reap() returns immediately when the group is already empty.
934
+ reap "$pid" 2>/dev/null || true
935
+ stop_heartbeat
936
+ ACTIVE_PEER_PID=""
937
+ }
938
+
939
+ resolve_python() {
940
+ for c in python3 python py; do
941
+ command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { printf '%s\n' "$c"; return; }
942
+ done
943
+ }
944
+
945
+ # Decode each {...} object in raw stdout via raw_decode (string/escape-aware,
946
+ # unlike brace counting) and keep the last one shaped like findings. Envelope
947
+ # routes nest that object inside a JSON *string* field, so string values that
948
+ # could hold one are re-scanned rather than skipped.
949
+ recover_findings_json() { # <logfile> <outfile>
950
+ local py="${PY_BIN:-}"
951
+ [ -n "$py" ] || return 1
952
+ "$py" - "$1" "$2" <<'PY' 2>/dev/null
953
+ import sys, json
954
+ txt = open(sys.argv[1], encoding="utf-8", errors="replace").read()
955
+ # Any selectable object carries a `findings` key; if the raw text has none,
956
+ # there is nothing to recover. Skip the scan — raw_decode probing every `{` is
957
+ # O(n^2) on brace-dense non-findings stdout (error/crash dumps). Match the bare
958
+ # word, not `"findings"`: nested inside an envelope's JSON string the key
959
+ # arrives escaped as \"findings\", which the quoted form does not match.
960
+ if 'findings' not in txt: sys.exit(0)
961
+ dec = json.JSONDecoder()
962
+ # (obj, depth) — depth>0 means recovered from inside a JSON string (envelope .text)
963
+ found = []
964
+
965
+ def scan(text, depth):
966
+ i = 0
967
+ while True:
968
+ j = text.find('{', i)
969
+ if j < 0: break
970
+ try:
971
+ obj, end = dec.raw_decode(text, j)
972
+ except Exception:
973
+ i = j + 1
974
+ continue
975
+ if isinstance(obj, dict):
976
+ # structuredOutput is grok-cli's spelling of the same field.
977
+ for cand in (obj, obj.get("structured_output"), obj.get("structuredOutput")):
978
+ if isinstance(cand, dict) and isinstance(cand.get("findings"), list):
979
+ found.append((cand, depth))
980
+ # An envelope route (grok-cli's `.text`) returns the review as a JSON
981
+ # *string*, whose `{` were never candidates here — raw_decode consumed
982
+ # the envelope whole and moved past it. Re-scan its strings so a
983
+ # wrapped review is recovered instead of reported as "no usable
984
+ # output". Unconditional: an envelope can carry its own empty
985
+ # `findings` beside the string holding the real one. The `findings`
986
+ # substring test bounds the nested scan's cost.
987
+ if depth < 3:
988
+ for v in obj.values():
989
+ if isinstance(v, str) and 'findings' in v:
990
+ scan(v, depth + 1)
991
+ i = end
992
+
993
+ scan(txt, 0)
994
+ # Nested (string-unwrapped) candidates are the grok .text stub case: order of
995
+ # empty vs populated is not guaranteed, so prefer a populated review. Top-level
996
+ # sequential objects (codex/noisy stdout) keep last-shaped-wins — a final
997
+ # findings:[] after an earlier draft must not revive the draft.
998
+ nested = [o for o, d in found if d > 0]
999
+ top = [o for o, d in found if d == 0]
1000
+ if nested:
1001
+ nested_pick = next((o for o in reversed(nested) if o["findings"]), nested[-1])
1002
+ if nested_pick["findings"]:
1003
+ best = nested_pick
1004
+ elif top:
1005
+ best = top[-1]
1006
+ else:
1007
+ best = nested_pick
1008
+ elif top:
1009
+ best = top[-1]
1010
+ else:
1011
+ best = None
1012
+ if best is not None: open(sys.argv[2], "w").write(json.dumps(best))
1013
+ PY
1014
+ [ -s "$2" ]
1015
+ }
1016
+
1017
+ bounded_failure_evidence() { # <logfile>; bounded head+tail of a plain-text log
1018
+ local path="$1" evidence
1019
+ # bash 3.2 rewrites newlines in a large string superlinearly, so read only the
1020
+ # ends of a long log instead of the whole file.
1021
+ if [ "$(wc -c <"$path")" -le 600 ]; then evidence="$(cat "$path")"
1022
+ else IFS= read -r -d '' -n 300 evidence <"$path"; evidence="$evidence ... $(tail -c 300 "$path")"; fi
1023
+ evidence="${evidence//$'\n'/ }"
1024
+ if [ "${#evidence}" -gt 300 ]; then
1025
+ evidence="${evidence:0:147} ... ${evidence: -147}"
1026
+ fi
1027
+ printf '%s' "$evidence"
1028
+ }
1029
+
1030
+ # A transient provider-capacity response (HTTP 529), unlike an account or
1031
+ # session quota. Claude's ACP adapter reports one its CLI could not ride out as
1032
+ # the prompt's own error with data.errorKind "overloaded"; the other adapters
1033
+ # retry overloads internally and have no distinct form, so they never match.
1034
+ provider_overloaded() {
1035
+ [ "$PROVIDER_OUTCOME" = "overloaded" ]
1036
+ }
1037
+
1038
+ # Run one route for a provider; leaves a schema-shaped (pre-normalization) $RAW_OUT on success.
1039
+ # Success is the prompt's own end_turn result, not acpx's exit status.
1040
+ attempt_route() { # <provider> <route>
1041
+ local provider="$1" route="$2" outcome reason started remaining node_line cancel_reason
1042
+ : > "$PEERLOG"; : > "$PEERERR"; rm -f "$RAW_OUT" "$OUT" "$TEXT_OUT"
1043
+ RUN_SUCCEEDED=false
1044
+ PROVIDER_OUTCOME="failed"
1045
+ build_cmd "$route"
1046
+ PEER_ROUTE="$route"
1047
+ log "peer run: provider=$provider route=$route model=$(route_model "$route") (effort $(route_effort "$route")) transport=acpx@$ACPX_VERSION lens=$REVIEWER_NAME read-only least-privilege (idle ${IDLE_SECS}s / hard ${HARD_SECS}s); full document content egresses to this provider via this route"
1048
+ started="$(date +%s)"
1049
+ run_peer_cmd
1050
+ if acpx_stale_npm_metadata "$PEERERR" "$PEERLOG" && [ "$(acpx_outcome "$PEERLOG")" = not-sent ]; then
1051
+ remaining=$(( HARD_SECS - ( $(date +%s) - started ) ))
1052
+ if [ "$remaining" -gt 0 ]; then
1053
+ log "npm resolved against stale cached package metadata; retrying the same route once with fresh metadata in the ${remaining}s left (nothing was sent)"
1054
+ ACPX_NPM_REFRESH=true
1055
+ HARD_SECS="$remaining"
1056
+ build_cmd "$route"
1057
+ run_peer_cmd
1058
+ fi
1059
+ fi
1060
+ outcome="$(acpx_outcome "$PEERLOG")"
1061
+ case "$outcome" in
1062
+ end_turn)
1063
+ RUN_SUCCEEDED=true
1064
+ PROVIDER_OUTCOME="ok"
1065
+ acpx_text "$PEERLOG" "$TEXT_OUT" && recover_findings_json "$TEXT_OUT" "$RAW_OUT"
1066
+ extract_model_receipt "$route"
1067
+ ;;
1068
+ not-sent)
1069
+ # Nothing reached the provider: npm could not fetch acpx (every route
1070
+ # fails alike), or the adapter refused before the prompt (this route only).
1071
+ reason="$(grep -m1 '^npm error' "$PEERERR" 2>/dev/null)"
1072
+ if [ -n "$reason" ]; then
1073
+ PRE_EGRESS="transport unavailable (pre-egress, shared): ${reason:0:200}"
1074
+ elif [ "$SESSION_STALLED" = true ]; then
1075
+ PRE_EGRESS="transport unavailable (pre-egress, route): Antigravity is waiting for a Google sign-in; the agy CLI's sign-in does not carry over, so sign the ACP runtime in once: put {\"auth\":{\"type\":\"oauth-personal\"}} in ${GEMINI_HOME:-~/.gemini}/antigravity-acp/settings.json, run npx acpx@$ACPX_VERSION antigravity exec ok in a terminal and finish the browser sign-in, then retry"
1076
+ else
1077
+ node_line="$(acpx_node_startup_line "$PEERERR")"
1078
+ if [ -n "$node_line" ]; then
1079
+ PRE_EGRESS="transport unavailable (pre-egress, route): ${node_line:0:200}"
1080
+ else
1081
+ reason="$(acpx_failure_evidence "$PEERLOG")"
1082
+ PRE_EGRESS="transport unavailable (pre-egress, route): ${reason:-acpx exited $PEER_EXIT before sending the prompt}"
1083
+ fi
1084
+ fi
1085
+ ;;
1086
+ *)
1087
+ if [ "$outcome" = error ] && jq -eR 'fromjson? | select(.error.data.errorKind? == "overloaded")' "$PEERLOG" >/dev/null 2>&1; then
1088
+ PROVIDER_OUTCOME="overloaded"
1089
+ fi
1090
+ cancel_reason="$(acpx_permission_cancel_reason "$PEERLOG" "$PEER_EXIT")"
1091
+ if [ -n "$cancel_reason" ]; then
1092
+ log "peer run ended: $cancel_reason (acpx exit $PEER_EXIT)"
1093
+ else
1094
+ log "peer run ended with $outcome (acpx exit $PEER_EXIT)"
1095
+ fi
1096
+ ;;
1097
+ esac
1098
+ }
1099
+
1100
+ # Run one host-resolved provider through its fixed route.
1101
+ run_provider() { # <provider>
1102
+ local provider="$1" primary="" fixed="${CROSS_MODEL_FIXED_ROUTE:-}"
1103
+ local provider_deadline remaining
1104
+ OUT="$RUN_DIR/$REVIEWER_NAME-$provider.json"
1105
+ RAW_OUT="$SCRATCH/$REVIEWER_NAME-$provider.raw.json"
1106
+ [ -n "$fixed" ] || { log "host must resolve one fixed route before egress; skipping"; rm -f "$OUT"; return 0; }
1107
+ [ "$(route_target "$fixed")" = "$provider" ] || { log "fixed route '$fixed' does not match target '$provider'; skipping"; rm -f "$OUT"; return 0; }
1108
+ if [ "$fixed" = "grok-cursor" ] && ! cursor_egress_ok; then
1109
+ log "fixed route 'grok-cursor' requires Cursor intermediary sanction; skipping"
1110
+ rm -f "$OUT"
1111
+ return 0
1112
+ fi
1113
+ primary="$fixed"
1114
+ PY_BIN="$(resolve_python)"
1115
+ [ -n "$PY_BIN" ] || { log "working Python 3 interpreter required to recover peer findings; skipping"; rm -f "$OUT"; return 0; }
1116
+ validate_model_override "$primary" || { log "model override '${CROSS_MODEL_MODEL_OVERRIDE:-}' not compatible with route '$primary'; skipping"; rm -f "$OUT"; return 0; }
1117
+ validate_effort_override "$primary" || { log "effort override '${CROSS_MODEL_EFFORT_OVERRIDE:-}' not compatible with route '$primary'; skipping"; rm -f "$OUT"; return 0; }
1118
+ case "$HARD_SECS" in
1119
+ ''|0*|*[!0-9]*) log "peer hard budget must be a positive integer; skipping"; rm -f "$OUT"; return 0 ;;
1120
+ esac
1121
+ # Per-peer empty workspace, kept SEPARATE from the shared fold-in dir (RUN_DIR)
1122
+ # and from private scratch: it is the peer's working directory, so a peer has
1123
+ # no path handle to RUN_DIR or to another lens's published artifact from it.
1124
+ PEER_WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/xmodel-doc-peer-XXXXXX")" || { PEER_WORKDIR=""; log "provider $provider workspace isolation unavailable; skipping"; rm -f "$OUT"; return 0; }
1125
+ acpx_preflight "$primary" "$PEER_WORKDIR" || { log "transport unavailable (pre-egress, $ACPX_SCOPE): $ACPX_UNAVAILABLE"; rm -f "$OUT"; return 0; }
1126
+ if [ "$primary" = claude ]; then
1127
+ CLAUDE_WRAPPER="$(acpx_claude_wrapper "$SCRATCH")" || { log "transport unavailable (pre-egress, route): cannot prepare the Claude --safe-mode launcher"; rm -f "$OUT"; return 0; }
1128
+ fi
1129
+ if [ "$primary" = antigravity ]; then
1130
+ GEMINI_HOME_DIR="$(acpx_antigravity_home "$SCRATCH")" || { log "transport unavailable (pre-egress, route): cannot prepare a private Antigravity home"; rm -f "$OUT"; return 0; }
1131
+ fi
1132
+ # Track the route that actually produced the fold-in, so the artifact records
1133
+ # whether a grok return went out directly (grok-cli -> xAI) or through Cursor
1134
+ # (grok-cursor -> Cursor also received the full document). The <lens>-<provider>
1135
+ # filename alone cannot encode that intermediary.
1136
+ ACTUAL_ROUTE="$primary"
1137
+ provider_deadline=$(( $(date +%s) + HARD_SECS ))
1138
+ PRE_EGRESS=""
1139
+ attempt_route "$provider" "$primary"
1140
+ # A configured model is exact; only the editorial Argon request falls back.
1141
+ if [ "$primary" = antigravity ] && [ "$(route_model antigravity)" = "$M_ANTIGRAVITY" ] &&
1142
+ [ "${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" != antigravity ]; then
1143
+ case "$PRE_EGRESS" in
1144
+ *"did not advertise"*)
1145
+ # The fallback shares the original deadline, as the overload retry does.
1146
+ remaining=$(( provider_deadline - $(date +%s) ))
1147
+ if [ "$remaining" -gt 0 ]; then
1148
+ log "Antigravity does not offer $M_ANTIGRAVITY on this account; falling back to $M_ANTIGRAVITY_FALLBACK"
1149
+ ANTIGRAVITY_MODEL="$M_ANTIGRAVITY_FALLBACK"
1150
+ PRE_EGRESS=""
1151
+ HARD_SECS="$remaining"
1152
+ attempt_route "$provider" "$primary"
1153
+ fi
1154
+ ;;
1155
+ esac
1156
+ fi
1157
+ if [ -n "$PRE_EGRESS" ]; then
1158
+ log "$PRE_EGRESS"
1159
+ rm -f "$OUT"
1160
+ return 0
1161
+ fi
1162
+ if [ ! -s "$RAW_OUT" ] && provider_overloaded; then
1163
+ remaining=$(( provider_deadline - $(date +%s) ))
1164
+ if [ "$remaining" -le "$TRANSIENT_RETRY_DELAY_SECS" ]; then
1165
+ log "provider overload 529; shared peer budget spent, not retrying"
1166
+ else
1167
+ log "provider overload 529; retrying same route once after ${TRANSIENT_RETRY_DELAY_SECS}s"
1168
+ sleep "$TRANSIENT_RETRY_DELAY_SECS"
1169
+ remaining=$(( provider_deadline - $(date +%s) ))
1170
+ if [ "$remaining" -gt 0 ]; then
1171
+ HARD_SECS="$remaining"
1172
+ attempt_route "$provider" "$primary"
1173
+ [ -z "$PRE_EGRESS" ] || log "$PRE_EGRESS"
1174
+ fi
1175
+ fi
1176
+ fi
1177
+
1178
+ # --- normalize + validate against the synthesis reviewer-return contract ---
1179
+ # Force reviewer = <reviewer-name>-<provider>; backfill soft arrays; drop the
1180
+ # file if findings is not an array. Peer findings fold in as a corroboration
1181
+ # signal only -- synthesis (references/synthesis-and-presentation.md) never
1182
+ # auto-applies them and caps the cross-model bonus at one anchor step.
1183
+ # Downgrade any peer finding's autofix_class from safe_auto to gated_auto: R18
1184
+ # forbids a peer from granting silent-apply authority, and enforcing it here (not
1185
+ # only in synthesis prose) means a peer cannot self-authorize a Phase 4 auto-apply
1186
+ # regardless of what it returns. gated_auto preserves the peer's proposed fix but
1187
+ # routes it through user confirmation.
1188
+ # Publish ONLY the normalized OUT into RUN_DIR. RAW_OUT lives in private scratch
1189
+ # and is never a fold-in artifact — if this script dies before normalize
1190
+ # (orphaned launch), synthesis finds no .json in RUN_DIR.
1191
+ rm -f "$OUT"
1192
+ if [ "$RUN_SUCCEEDED" = true ] && [ -s "$RAW_OUT" ]; then
1193
+ _norm="$SCRATCH/normalized.json"
1194
+ case "$ACTUAL_ROUTE:$MODEL_ACTUAL" in
1195
+ cursor:*) _target_family="unknown" ;;
1196
+ composer:unverified|grok-cursor:unverified) _target_family="unknown" ;;
1197
+ *) _target_family="$(target_serving_family "$provider")" ;;
1198
+ esac
1199
+ _independent=false
1200
+ [ "$HOST_PROVIDER" != "unknown" ] && [ "$_target_family" != "unknown" ] && [ "$HOST_PROVIDER" != "$_target_family" ] && _independent=true
1201
+ if jq --arg r "$REVIEWER_NAME-$provider" --arg route "$ACTUAL_ROUTE" \
1202
+ --arg target "$provider" --arg harness "$(route_harness "$ACTUAL_ROUTE")" \
1203
+ --arg family "$_target_family" --argjson independent "$_independent" \
1204
+ --arg mreq "$(route_model "$ACTUAL_ROUTE")" --arg mact "$MODEL_ACTUAL" \
1205
+ --arg ereq "$(route_effort "$ACTUAL_ROUTE")" \
1206
+ 'if (.findings|type)=="array"
1207
+ then { reviewer: $r,
1208
+ cross_model_route: $route,
1209
+ cross_model_target: $target,
1210
+ cross_model_harness: $harness,
1211
+ serving_family: $family,
1212
+ independence_verified: $independent,
1213
+ model_requested: $mreq,
1214
+ model_actual: $mact,
1215
+ effort_requested: $ereq,
1216
+ findings: [ .findings[] | if (.autofix_class? == "safe_auto") then .autofix_class = "gated_auto" else . end ],
1217
+ residual_risks: (.residual_risks // []),
1218
+ deferred_questions: (.deferred_questions // []) }
1219
+ else empty end' \
1220
+ "$RAW_OUT" > "$_norm" 2>/dev/null; then
1221
+ mv "$_norm" "$OUT"
1222
+ else
1223
+ rm -f "$_norm"
1224
+ fi
1225
+ rm -f "$RAW_OUT"
1226
+ fi
1227
+ if [ -s "$OUT" ] && jq -e '(.reviewer|type=="string") and (.findings|type=="array") and (.residual_risks|type=="array") and (.deferred_questions|type=="array")' "$OUT" >/dev/null 2>&1; then
1228
+ n="$(jq '.findings | length' "$OUT" 2>/dev/null || echo '?')"
1229
+ log "wrote $n finding(s) to $OUT (reviewer $REVIEWER_NAME-$provider)"
1230
+ else
1231
+ log "provider $provider produced no usable schema-shaped output; skipping fold-in"
1232
+ # Surface bounded peer output so the orchestrator can
1233
+ # reason about WHY it was skipped (quota/usage-limit exhaustion vs an ordinary
1234
+ # empty review) and, in a repeated-pass session, deprioritize an exhausted
1235
+ # route. Harness-agnostic: the agent classifies from the text; this only makes
1236
+ # the evidence visible in out.log. Provider errors arrive as ACP error
1237
+ # messages or as reply text on stdout; npm and adapter diagnostics on stderr.
1238
+ _pt="$(acpx_permission_cancel_reason "$PEERLOG" "$PEER_EXIT")"
1239
+ [ -n "$_pt" ] || _pt="$(acpx_failure_evidence "$PEERLOG")"
1240
+ [ -n "$_pt" ] && log " peer skip evidence: $_pt"
1241
+ _pr="$(acpx_reply_evidence "$PEERLOG")"
1242
+ [ -n "$_pr" ] && log " peer skip evidence (reply): $_pr"
1243
+ _pm="$(acpx_advertised_models "$PEERLOG")"
1244
+ [ -n "$_pm" ] && log " peer skip evidence (advertised models): $_pm"
1245
+ if [ -s "$PEERERR" ]; then
1246
+ _pe="$(bounded_failure_evidence "$PEERERR")"
1247
+ log " peer skip evidence (stderr): $_pe"
1248
+ fi
1249
+ rm -f "$OUT" "$RAW_OUT"
1250
+ fi
1251
+ # Tear down the per-peer workspace (never RUN_DIR, which holds the published OUT).
1252
+ [ -n "$PEER_WORKDIR" ] && rm -rf "$PEER_WORKDIR"
1253
+ PEER_WORKDIR=""
1254
+ }
1255
+
1256
+ # --- run the host-sanctioned fixed target -----------------------------------
1257
+ # Discovery preserves caller order and MAX_PEERS, but live egress is already
1258
+ # frozen to one route. Dispatch that route's target directly so a later eligible
1259
+ # candidate is not discarded by the discovery-order cap. run_provider never
1260
+ # changes recipients after dispatch.
1261
+ FIXED_TARGET="$(route_target "${CROSS_MODEL_FIXED_ROUTE:-}")"
1262
+ if [ -n "$FIXED_TARGET" ]; then
1263
+ case " $SELECTED " in
1264
+ *" $FIXED_TARGET "*) run_provider "$FIXED_TARGET" ;;
1265
+ *) log "fixed route '${CROSS_MODEL_FIXED_ROUTE:-}' target '$FIXED_TARGET' is not an eligible reachable candidate; skipping" ;;
1266
+ esac
1267
+ else
1268
+ log "host must resolve one fixed route before egress; skipping"
1269
+ fi
1270
+ exit 0