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,1110 @@
1
+ #!/usr/bin/env bash
2
+ # cross-model-pov.sh
3
+ #
4
+ # Runs one pre-sanctioned different-model route in a read-only, least-privilege
5
+ # process and writes its POV as JSON into the run dir.
6
+ # Every peer receives the canonical POV persona, schema, and a caller-prepared
7
+ # subject payload. The peer also receives the caller-declared repository read
8
+ # scope; private prompt/result scratch stays outside that repository.
9
+ #
10
+ # Independence is by PROVIDER, not CLI brand. A provider is reached by a ROUTE:
11
+ # its dedicated CLI, or (for the fixed grok-cursor / composer routes) cursor-agent. All
12
+ # peer runs on ONE model at HIGH reasoning (composer's
13
+ # -fast tier is its ceiling, an accepted exception).
14
+ #
15
+ # Usage:
16
+ # cross-model-pov.sh <host-serving-family> <fixed-route> <subject-payload> <run-dir>
17
+ #
18
+ # <host-serving-family>
19
+ # the peer-key of the host's OWN serving family, attested by
20
+ # the calling skill (it knows its harness). A peer-key, never
21
+ # a provider name: openai->codex, anthropic->claude,
22
+ # xai->grok, cursor/composer->composer, google->gemini.
23
+ # Used to verify independence. `unknown` is allowed for an
24
+ # explicitly named peer, but its receipt remains unverified;
25
+ # automatic discovery must exclude it before calling this worker.
26
+ # <fixed-route> one host-resolved and pre-sanctioned route: codex, claude,
27
+ # grok-cli, grok-cursor, cursor, composer, opencode, or
28
+ # antigravity. A route failure
29
+ # returns no artifact; only the host may disclose and retry a
30
+ # different recipient.
31
+ # <subject-payload> framed question plus any conversation-only subject material.
32
+ # Point to repository files instead of copying their contents;
33
+ # the peer grounds itself from the shared working tree.
34
+ # <run-dir> existing private dir outside the repository; output ->
35
+ # <run-dir>/pov-<target>.json, where <target> is the resolved
36
+ # <fixed-route> target (grok-cli/grok-cursor both collapse to
37
+ # grok) -- NOT the <host-serving-family> key.
38
+ #
39
+ # Test/introspection mode (no model call, no side effects):
40
+ # cross-model-pov.sh --emit-adapter <route>
41
+ # prints the exact argv the given route would run (route in:
42
+ # codex | claude | grok-cli | grok-cursor | cursor | composer | opencode |
43
+ # antigravity). Both this mode and the
44
+ # live run build their argv from adapter_argv(), so the U7 route-safety test
45
+ # asserts on the same command string the peer actually runs.
46
+ #
47
+ # Self-locates its sibling reference files via BASH_SOURCE (NOT the CWD, which is
48
+ # the user's project on every host). The agent passes the values above.
49
+ #
50
+ # NON-BLOCKING BY DESIGN: every failure logs to stderr and exits 0 without an
51
+ # output file. The cross-model pass is additive and must never fail the POV;
52
+ # the caller detects success purely by the presence of the output file(s).
53
+ #
54
+ # DATA-EGRESS NOTE: this embeds the prepared subject payload into an external
55
+ # model CLI prompt. The caller must disclose its content scope and actual provider
56
+ # before launch; route receipts let it reconcile the fixed target afterward.
57
+
58
+ set -uo pipefail
59
+
60
+ # Survive SIGHUP when the orchestrator backgrounds this script and the parent
61
+ # shell exits (common on Cursor/Codex Bash tools). Without this, a detached
62
+ # peer process group can keep running while this script dies
63
+ # before normalize — leaving fold-in files without route/model receipts.
64
+ trap '' HUP
65
+
66
+ # Filled while a peer process group is live; TERM/INT handler (installed after
67
+ # reap() is defined) reaps it so an orchestrator kill cannot leave orphans.
68
+ ACTIVE_PEER_PID=""
69
+ PEER_WORKDIR=""
70
+ PROMPT_FILE=""
71
+ PEERLOG=""
72
+ PEERERR=""
73
+ RAW_OUT=""
74
+ RUN_SUCCEEDED=false
75
+ PEER_ROUTE=""
76
+ SESSION_STALLED=false
77
+
78
+ cleanup_private_scratch() {
79
+ [ -n "${PEER_WORKDIR:-}" ] && rm -rf "$PEER_WORKDIR"
80
+ PEER_WORKDIR=""
81
+ }
82
+
83
+ log() { printf '[cross-model-pov] %s\n' "$*" >&2; }
84
+ skip() { log "$*"; exit 0; } # non-blocking: announce reason, exit clean, no output
85
+
86
+ # --- model + reasoning per provider ----------------------------------------
87
+ # ONE model per provider at its editorial tier (native Grok is xhigh; Codex and Claude stay high). Concrete IDs are the CURRENT instance of the tier principle
88
+ # and the single maintenance point when model families change.
89
+ M_CODEX="gpt-6.1-sol" # codex (reasoning_effort=high)
90
+ M_CLAUDE="claude-opus-5-5" # claude (effort=high)
91
+ M_GROK="grok-4.7" # grok CLI (reasoning_effort=xhigh)
92
+ # Cursor's ACP server offers one preset per model and rejects effort variants,
93
+ # so grok-cursor runs at high/fast and composer at its fast tier.
94
+ M_GROK_CURSOR="grok-4.7[context=256k,reasoning_effort=high,fast=true]"
95
+ M_COMPOSER="composer-2.5[fast=true]"
96
+ # Antigravity offers only the models the account is entitled to. Argon is
97
+ # preferred; an account without it falls back once to Flash before any prompt,
98
+ # and the non-final retry reuses whichever model was selected.
99
+ M_ANTIGRAVITY="gemini-4-argon" # antigravity (thinking_level=high)
100
+ M_ANTIGRAVITY_FALLBACK="gemini-3.8-flash"
101
+ ANTIGRAVITY_MODEL="" # set by the fallback; empty requests M_ANTIGRAVITY
102
+
103
+ route_model() { # <route> -> the M_* constant that route requests
104
+ local target
105
+ target="$(route_target "$1")"
106
+ if [ -n "${CROSS_MODEL_MODEL_OVERRIDE:-}" ] &&
107
+ [ "${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" = "$target" ] &&
108
+ [ "$target" != "cursor" ]; then
109
+ printf '%s' "$CROSS_MODEL_MODEL_OVERRIDE"
110
+ return 0
111
+ fi
112
+ case "$1" in
113
+ codex) printf '%s' "$M_CODEX" ;;
114
+ claude) printf '%s' "$M_CLAUDE" ;;
115
+ grok-cli) printf '%s' "$M_GROK" ;;
116
+ grok-cursor) printf '%s' "$M_GROK_CURSOR" ;;
117
+ cursor) printf 'auto' ;;
118
+ composer) printf '%s' "$M_COMPOSER" ;;
119
+ opencode) printf 'auto' ;;
120
+ antigravity) printf '%s' "${ANTIGRAVITY_MODEL:-$M_ANTIGRAVITY}" ;;
121
+ esac
122
+ }
123
+
124
+ route_target() {
125
+ case "$1" in
126
+ codex|claude|cursor|composer|antigravity) printf '%s' "$1" ;;
127
+ grok-cli|grok-cursor) printf 'grok' ;;
128
+ opencode) printf 'opencode' ;;
129
+ esac
130
+ }
131
+
132
+ route_harness() {
133
+ case "$1" in
134
+ codex) printf 'codex' ;;
135
+ claude) printf 'claude' ;;
136
+ grok-cli) printf 'grok' ;;
137
+ grok-cursor|cursor|composer) printf 'cursor-agent' ;;
138
+ opencode) printf 'opencode' ;;
139
+ antigravity) printf 'antigravity' ;;
140
+ esac
141
+ }
142
+
143
+ target_serving_family() {
144
+ case "$1" in
145
+ codex|claude|grok|composer) printf '%s' "$1" ;;
146
+ antigravity) printf 'gemini' ;;
147
+ cursor) printf 'unknown' ;;
148
+ opencode) printf 'unknown' ;;
149
+ esac
150
+ }
151
+
152
+ route_available() {
153
+ case "$1" in
154
+ codex) command -v codex >/dev/null 2>&1 ;;
155
+ claude) command -v claude >/dev/null 2>&1 ;;
156
+ grok-cli) command -v grok >/dev/null 2>&1 ;;
157
+ grok-cursor|cursor|composer) command -v cursor-agent >/dev/null 2>&1 ;;
158
+ opencode) command -v opencode >/dev/null 2>&1 ;;
159
+ antigravity) command -v agy_acp_server.par >/dev/null 2>&1 ;;
160
+ *) return 1 ;;
161
+ esac
162
+ }
163
+
164
+ # --- acpx transport (keep byte-identical across migrated peer workers) -----
165
+ # Every route reaches its agent through acpx, which speaks the Agent Client
166
+ # Protocol and prints the JSON-RPC stream, so one parser covers every CLI.
167
+ # The calling worker supplies route_available, route_model, and log.
168
+ ACPX_VERSION="0.21.0"
169
+ ACPX_UNAVAILABLE="" # pre-egress reason; ACPX_SCOPE says whether every route shares it
170
+ ACPX_SCOPE=""
171
+ ACPX_NPM_REFRESH=false
172
+
173
+ acpx_agent() { # <route> -> acpx built-in agent name
174
+ case "$1" in
175
+ codex) printf 'codex' ;;
176
+ claude) printf 'claude' ;;
177
+ grok-cli) printf 'grok-build' ;;
178
+ grok-cursor|cursor|composer) printf 'cursor' ;;
179
+ opencode) printf 'opencode' ;;
180
+ antigravity) printf 'antigravity' ;;
181
+ *) return 1 ;;
182
+ esac
183
+ }
184
+
185
+ # acpx launches Google's Antigravity ACP runtime by name. It is a separate
186
+ # download from the agy CLI and needs its localharness_external helper from the
187
+ # same release, beside it or named by ANTIGRAVITY_HARNESS_PATH.
188
+ acpx_antigravity_helper() {
189
+ local runtime
190
+ if [ -n "${ANTIGRAVITY_HARNESS_PATH:-}" ]; then printf '%s' "$ANTIGRAVITY_HARNESS_PATH"; return 0; fi
191
+ runtime="$(command -v agy_acp_server.par 2>/dev/null)" || return 1
192
+ printf '%s/localharness_external' "$(dirname "$runtime")"
193
+ }
194
+
195
+ # The runtime reads skills and settings from GEMINI_HOME, so every run gets a
196
+ # private one, deleted with the caller's scratch. A file-backed login (no macOS
197
+ # keychain) is read only from there, so it is copied in; refreshes are not
198
+ # written back, so concurrent peers cannot clobber the user's token.
199
+ acpx_antigravity_home() { # <private-dir> -> GEMINI_HOME path
200
+ local home="$1/gemini-home"
201
+ local token="${GEMINI_HOME:-${HOME:-}/.gemini}/antigravity-acp/acp_token.json"
202
+ (umask 077; mkdir -p "$home/antigravity-acp") || return 1
203
+ printf '{"auth":{"type":"oauth-personal"}}\n' > "$home/antigravity-acp/settings.json" || return 1
204
+ if [ -f "$token" ]; then
205
+ (umask 077; cp "$token" "$home/antigravity-acp/acp_token.json") || return 1
206
+ fi
207
+ printf '%s' "$home"
208
+ }
209
+
210
+ acpx_unavailable() { # <shared|route> <reason>
211
+ ACPX_SCOPE="$1"; ACPX_UNAVAILABLE="$2"
212
+ return 1
213
+ }
214
+
215
+ # acpx lets ~/.acpx/config.json and <cwd>/.acpxrc.json replace a built-in
216
+ # agent's launch command; a route that would run something else is skipped.
217
+ # opencode launches the installed CLI by path, which no config entry overrides.
218
+ acpx_config_guard() { # <route> <cwd>
219
+ local agent file
220
+ [ "$1" = opencode ] && return 0
221
+ agent="$(acpx_agent "$1")" || return 1
222
+ for file in "${HOME:-}/.acpx/config.json" "$2/.acpxrc.json"; do
223
+ [ -e "$file" ] || continue
224
+ if ! jq -e --arg a "$agent" '(.agents // {}) | keys | map(ascii_downcase) | index($a) == null' "$file" >/dev/null 2>&1; then
225
+ acpx_unavailable route "$file overrides or cannot be checked for the '$agent' agent launch"
226
+ return 1
227
+ fi
228
+ done
229
+ }
230
+
231
+ acpx_preflight() { # <route> <cwd>; nothing is sent to a provider here
232
+ local v major minor
233
+ ACPX_UNAVAILABLE=""; ACPX_SCOPE=""
234
+ command -v node >/dev/null 2>&1 || { acpx_unavailable shared "node not found; acpx needs Node 22.13 or newer"; return 1; }
235
+ v="$(node -p 'process.versions.node' 2>/dev/null)"
236
+ major="${v%%.*}"; minor="${v#*.}"; minor="${minor%%.*}"
237
+ case "$major$minor" in ''|*[!0-9]*) acpx_unavailable shared "cannot read the Node version; acpx needs Node 22.13 or newer"; return 1 ;; esac
238
+ if [ "$major" -lt 22 ] || { [ "$major" -eq 22 ] && [ "$minor" -lt 13 ]; }; then
239
+ acpx_unavailable shared "Node $v is too old; acpx needs Node 22.13 or newer"
240
+ return 1
241
+ fi
242
+ command -v npx >/dev/null 2>&1 || { acpx_unavailable shared "npx not found; acpx runs through npx"; return 1; }
243
+ if [ "$1" = antigravity ]; then
244
+ case "$(uname -s 2>/dev/null)" in
245
+ MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "the Antigravity route is not yet supported on native Windows"; return 1 ;;
246
+ esac
247
+ 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; }
248
+ [ -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; }
249
+ fi
250
+ route_available "$1" || { acpx_unavailable route "the agent CLI for route '$1' is not installed"; return 1; }
251
+ if [ "$1" = opencode ]; then
252
+ case "$(uname -s 2>/dev/null)" in
253
+ MINGW*|MSYS*|CYGWIN*) acpx_unavailable route "opencode launches through acpx's raw --agent command, which acpx rejects on native Windows"; return 1 ;;
254
+ esac
255
+ fi
256
+ acpx_config_guard "$1" "$2"
257
+ }
258
+
259
+ # Claude's ACP adapter always loads project settings, so the launch goes
260
+ # through a wrapper that adds --safe-mode.
261
+ acpx_claude_wrapper() { # <private-dir> -> wrapper path
262
+ local real wrapper="$1/claude-safe-mode"
263
+ real="$(command -v claude)" || return 1
264
+ printf '#!/bin/sh\nexec %s --safe-mode "$@"\n' "$(printf '%q' "$real")" > "$wrapper" && chmod 700 "$wrapper" || return 1
265
+ printf '%s' "$wrapper"
266
+ }
267
+
268
+ # npm resolves offline-first, so cached metadata older than the acpx pin or a
269
+ # version an adapter's dependencies need fails with ETARGET before anything is
270
+ # sent. One launch with fresh metadata heals the cache. npm ignores
271
+ # prefer_online while prefer_offline is set, so the refresh turns it off.
272
+ acpx_stale_npm_metadata() { # <stderr-log> <stream-log>
273
+ [ "$ACPX_NPM_REFRESH" = true ] && return 1
274
+ grep -qE 'ETARGET|notarget|No matching version found' "$1" "$2" 2>/dev/null
275
+ }
276
+
277
+ # Prints the NUL-delimited argv prefix through `npx acpx@<pin>` and its global
278
+ # flags; the worker appends permission flags, `--model`, the agent, and exec options.
279
+ # The codex and claude adapters are pointed at the installed CLIs so the pin
280
+ # governs the transport, not which models the agent can serve.
281
+ acpx_base_argv() { # <route> <cwd> <mcp-config> <timeout-secs> <claude-wrapper> <gemini-home>
282
+ printf '%s\0' env
283
+ # An ambient Google API key or cloud project would bill it instead of the
284
+ # Antigravity subscription. env takes -u only before its first assignment.
285
+ case "$1" in
286
+ antigravity) printf '%s\0' -u GEMINI_API_KEY -u GOOGLE_API_KEY -u GOOGLE_APPLICATION_CREDENTIALS \
287
+ -u GOOGLE_CLOUD_PROJECT -u GOOGLE_CLOUD_LOCATION -u GOOGLE_GENAI_USE_VERTEXAI -u AGY_LLM_GATEWAY_URL ;;
288
+ esac
289
+ if [ "$ACPX_NPM_REFRESH" = true ]; then
290
+ printf '%s\0' npm_config_prefer_offline=false npm_config_fetch_retries=0 npm_config_prefer_online=true
291
+ else
292
+ printf '%s\0' npm_config_prefer_offline=true npm_config_fetch_retries=0
293
+ fi
294
+ case "$1" in
295
+ # codex-acp ignores app-server CLI flags.
296
+ codex) printf '%s\0' "CODEX_PATH=$(command -v codex)" 'CODEX_CONFIG={"features":{"plugins":false}}' ;;
297
+ claude) printf '%s\0' "CLAUDE_CODE_EXECUTABLE=$5" ;;
298
+ antigravity) printf '%s\0' "GEMINI_HOME=$6" "ANTIGRAVITY_HARNESS_PATH=$(acpx_antigravity_helper)" ;;
299
+ esac
300
+ printf '%s\0' npx -y "acpx@$ACPX_VERSION" --cwd "$2" --format json --mcp-config "$3" --timeout "$4"
301
+ }
302
+
303
+ acpx_agent_argv() { # <route>
304
+ # The preflight refuses opencode when it is not installed; the bare name keeps
305
+ # --emit-adapter output readable on a machine without it.
306
+ if [ "$1" = opencode ]; then printf '%s\0' --agent "$(command -v opencode || printf opencode) acp" exec
307
+ else printf '%s\0' "$(acpx_agent "$1")" exec; fi
308
+ }
309
+
310
+ # The run's outcome is the result of its own session/prompt request, matched by
311
+ # id because --model adds a session/set_model request before the prompt.
312
+ acpx_outcome() { # <stream-log> -> end_turn | <other stopReason> | error | incomplete | not-sent
313
+ local id outcome
314
+ id="$(jq -R 'fromjson? | select(.method == "session/prompt") | .id' "$1" 2>/dev/null | tail -1)"
315
+ [ -n "$id" ] || { printf 'not-sent'; return 0; }
316
+ 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)"
317
+ printf '%s' "${outcome:-incomplete}"
318
+ }
319
+
320
+ # Antigravity holds session/new open while it waits for a browser sign-in it
321
+ # will never get, so a run without a usable login would otherwise sit out the
322
+ # whole idle cap. Nothing has been sent while the session is pending.
323
+ ACPX_SESSION_START_SECS="${CROSS_MODEL_SESSION_START_SECS:-60}"
324
+ acpx_session_state() { # <stream-log> -> starting | pending | open
325
+ local id
326
+ jq -eR 'fromjson? | select(.result.protocolVersion? != null)' "$1" >/dev/null 2>&1 || { printf 'starting'; return 0; }
327
+ id="$(jq -R 'fromjson? | select(.method == "session/new") | .id' "$1" 2>/dev/null | tail -1)"
328
+ if [ -n "$id" ] && jq -eR --argjson id "$id" 'fromjson? | select(.method == null and .id == $id)' "$1" >/dev/null 2>&1; then
329
+ printf 'open'
330
+ else
331
+ printf 'pending'
332
+ fi
333
+ }
334
+
335
+ acpx_text() { # <stream-log> <outfile>: the agent's reply text
336
+ jq -jR 'fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty' "$1" > "$2" 2>/dev/null
337
+ }
338
+
339
+ # Model ids the adapter reports in the prompt result's _meta. acpx documents
340
+ # _meta as adapter-defined, so these are the adapter's assertion, not proof.
341
+ acpx_served_models() { # <stream-log> -> "<model> <tokens>" lines
342
+ jq -rR 'fromjson? | select(.result.stopReason?) | .result._meta // {} |
343
+ ((.quota.model_usage // [])[] | "\(.model) \(.token_count.totalTokens // 0)"),
344
+ (.modelId // empty | "\(.) 0")' "$1" 2>/dev/null
345
+ }
346
+
347
+ # Bounded failure evidence from the stream. The stream echoes the outbound
348
+ # prompt, so only error messages and the stop reason are read from it.
349
+ acpx_failure_evidence() { # <stream-log>
350
+ local evidence
351
+ evidence="$(jq -rR 'fromjson? | (.error.message? // empty), (.result.stopReason? // empty | select(. != "end_turn") | "stopReason=" + .)' "$1" 2>/dev/null | tr '\n' ' ')"
352
+ evidence="${evidence% }"
353
+ [ "${#evidence}" -gt 300 ] && evidence="${evidence:0:147} ... ${evidence: -147}"
354
+ printf '%s' "$evidence"
355
+ }
356
+
357
+ # codex-acp streams a provider rejection (such as a model the account may not
358
+ # use) as reply text and still ends the turn with end_turn, so only the reply
359
+ # carries it. Bounded inside jq so a long reply never enters a bash string.
360
+ acpx_reply_evidence() { # <stream-log>
361
+ jq -nrR '[inputs | fromjson? | select(.method == "session/update" and .params.update.sessionUpdate == "agent_message_chunk") | .params.update.content.text // empty]
362
+ | join("") | gsub("\\s+"; " ") | ltrimstr(" ") | rtrimstr(" ")
363
+ | if length > 300 then .[0:147] + " ... " + .[-147:] else . end' "$1" 2>/dev/null
364
+ }
365
+
366
+ acpx_advertised_models() { # <stream-log>
367
+ jq -nrR '[inputs | fromjson? | .result.models.availableModels[]?.modelId | sub("\\[.*$"; "")]
368
+ | reduce .[] as $m ([]; if any(.[]; . == $m) then . else . + [$m] end)
369
+ | join(", ") | if length > 300 then .[0:296] + " ..." else . end' "$1" 2>/dev/null
370
+ }
371
+
372
+ # Exit 5 is acpx's permission refusal. Paired with stopReason cancelled, the
373
+ # refusal ended the turn. Exit 5 with end_turn means the turn finished. A
374
+ # cancelled turn with any other exit is an interrupt.
375
+ acpx_permission_cancel_reason() { # <stream-log> <exit-code>
376
+ [ "$(acpx_outcome "$1")" = cancelled ] && [ "$2" = 5 ] || return 0
377
+ printf '%s' "permission request cancelled the turn"
378
+ }
379
+
380
+ acpx_node_startup_line() { # <stderr-log>
381
+ local line=""
382
+ line="$(grep -m1 -E '^Error ' "$1" 2>/dev/null)"
383
+ [ -n "$line" ] || line="$(grep -m1 -E '^node:' "$1" 2>/dev/null)"
384
+ printf '%s' "$line"
385
+ }
386
+ # --- end acpx transport -----------------------------------------------------
387
+
388
+ # --- model-identity receipt ---------------------------------------------------
389
+ # "Which model ran" comes from the adapter's _meta report, recorded as the
390
+ # served id; it is never filled from the requested value. Prefer the served id
391
+ # in the requested family (Claude also reports an auxiliary Haiku); otherwise
392
+ # record the model that used the most tokens and warn about the mismatch.
393
+ MODEL_ACTUAL="unverified"
394
+ extract_model_receipt() { # <route>; reads $PEERLOG, sets MODEL_ACTUAL
395
+ local requested family served m tokens most=-1 heaviest=""
396
+ MODEL_ACTUAL="unverified"
397
+ requested="$(route_model "$1")"
398
+ family="${requested%%\[*}"
399
+ case "$family" in
400
+ fable|opus|sonnet|haiku) family="claude-$family" ;;
401
+ esac
402
+ served="$(acpx_served_models "$PEERLOG")"
403
+ [ -n "$served" ] || return 0
404
+ while read -r m tokens; do
405
+ [ -n "$m" ] || continue
406
+ case "$m" in
407
+ "$family"|"$family"-*|"$family"\[*) MODEL_ACTUAL="$m"; return 0 ;;
408
+ esac
409
+ if [ "${tokens:-0}" -gt "$most" ]; then most="${tokens:-0}"; heaviest="$m"; fi
410
+ done <<EOF
411
+ $served
412
+ EOF
413
+ MODEL_ACTUAL="$heaviest"
414
+ # A route that requested no model (Cursor default, OpenCode auto) has nothing to mismatch.
415
+ [ "$requested" = auto ] && return 0
416
+ log "WARNING: model mismatch - requested $requested, adapter reported $MODEL_ACTUAL; reconcile must surface this"
417
+ }
418
+
419
+ # --- adapter argv (single source of truth for route flags) -----------------
420
+ # Read-only intent on every route: reads are approved, every other permission
421
+ # request is denied, and each adapter runs in its read-only mode where it has
422
+ # one (codex mode=read-only, claude mode=default, cursor mode=ask, opencode
423
+ # mode=plan with edits denied, antigravity mode=default). Claude's --allowed-tools is an auto-approve
424
+ # list, not a restriction: its other tools still ask and are denied. The
425
+ # codex adapter does not enforce its read-only mode, so write denial is best
426
+ # effort. PEER_WORKDIR / PROMPT_FILE / MCP_CONFIG / CLAUDE_WRAPPER / GEMINI_HOME_DIR are resolved
427
+ # by the caller (placeholders in --emit-adapter mode).
428
+ adapter_argv() {
429
+ local route="$1" model
430
+ acpx_agent "$route" >/dev/null || return 1
431
+ model="$(route_model "$route")"
432
+ case "$route" in
433
+ # OpenCode's plan mode keeps the peer read-only; denying bash outright makes
434
+ # its free tier reject the session over ACP.
435
+ opencode) printf '%s\0' env OPENCODE_DISABLE_PROJECT_CONFIG=1 \
436
+ 'OPENCODE_CONFIG_CONTENT={"permission":{"edit":"deny","webfetch":"deny","task":"deny"}}' ;;
437
+ esac
438
+ acpx_base_argv "$route" "$READ_ROOT" "$MCP_CONFIG" "$HARD_SECS" "$CLAUDE_WRAPPER" "$GEMINI_HOME_DIR"
439
+ printf '%s\0' --approve-reads --non-interactive-permissions deny
440
+ case "$route" in
441
+ claude) printf '%s\0' --allowed-tools Read,Glob,Grep,WebSearch,WebFetch --max-turns 15 ;;
442
+ esac
443
+ [ "$model" = auto ] || printf '%s\0' --model "$model"
444
+ acpx_agent_argv "$route"
445
+ case "$route" in
446
+ codex) printf '%s\0' --config-option mode=read-only --config-option reasoning_effort=high ;;
447
+ claude) printf '%s\0' --config-option mode=default --config-option effort=high ;;
448
+ grok-cli) printf '%s\0' --config-option reasoning_effort=xhigh ;;
449
+ grok-cursor|cursor|composer) printf '%s\0' --config-option mode=ask ;;
450
+ opencode) printf '%s\0' --config-option mode=plan ;;
451
+ antigravity) printf '%s\0' --config-option mode=default --config-option thinking_level=high ;;
452
+ esac
453
+ printf '%s\0' --file "$PROMPT_FILE"
454
+ }
455
+
456
+ # The host may replace a stale concrete model only within the fixed route's
457
+ # target family. Values are passed as one argv token; they never enter eval.
458
+ # A codex id may carry the serving provider's own namespace (openai.gpt-...)
459
+ # when the CLI routes through a non-default model_provider.
460
+ apply_model_override() {
461
+ local route="$1" override="${CROSS_MODEL_MODEL_OVERRIDE:-}" override_target="${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" target
462
+ [ -n "$override" ] || { [ -z "$override_target" ]; return; }
463
+ target="$(route_target "$route")" || return 1
464
+ [ "$override_target" = "$target" ] || return 1
465
+ [ "$target" != "cursor" ] || return 1
466
+ case "$route:$override" in
467
+ codex:gpt-*|codex:o[0-9]*|codex:*[./]gpt-*|codex:*[./]o[0-9]* ) ;;
468
+ claude:fable|claude:opus|claude:sonnet|claude:haiku|claude:claude-* ) ;;
469
+ grok-cli:grok-* ) ;;
470
+ grok-cursor:grok-*|grok-cursor:cursor-grok-* ) ;;
471
+ composer:composer-* ) ;;
472
+ opencode:*/* ) ;;
473
+ antigravity:gemini-* ) ;;
474
+ *) return 1 ;;
475
+ esac
476
+ }
477
+
478
+ # --- --emit-adapter <route>: print the argv, no model call, no side effects --
479
+ if [ "${1:-}" = "--emit-adapter" ]; then
480
+ READ_ROOT="<read-root>"
481
+ PROMPT_FILE="<prompt-file>"; MCP_CONFIG="<mcp-config>"; CLAUDE_WRAPPER="<claude-safe-mode-wrapper>"; GEMINI_HOME_DIR="<gemini-home>"
482
+ HARD_SECS="${CROSS_MODEL_HARD_SECS:-600}"
483
+ route="${2:-}"
484
+ apply_model_override "$route" 2>/dev/null || { echo "model override '${CROSS_MODEL_MODEL_OVERRIDE:-}' not compatible with route '$route'" >&2; exit 2; }
485
+ # adapter_argv emits NUL-delimited argv (can't be captured in a shell var), so
486
+ # validate the route first, then render for humans with NUL -> space.
487
+ 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; }
488
+ adapter_argv "$route" | tr '\0' ' '; echo
489
+ exit 0
490
+ fi
491
+
492
+ HOST_PROVIDER="${1:-unknown}"
493
+ HOST_HARNESS="${CROSS_MODEL_HOST_HARNESS:-unknown}"
494
+ FIXED_ROUTE="${2:-}"
495
+ PAYLOAD_PATH="${3:-}"
496
+ RUN_DIR="${4:-}"
497
+
498
+ # --- validate inputs -------------------------------------------------------
499
+ [ -n "$PAYLOAD_PATH" ] && [ -f "$PAYLOAD_PATH" ] || skip "subject payload '${PAYLOAD_PATH:-<empty>}' not readable on disk; skipping"
500
+ READ_ROOT="${CROSS_MODEL_READ_ROOT:-$(pwd -P)}"
501
+ [ -d "$READ_ROOT" ] || skip "declared repository/read root '$READ_ROOT' is not a directory"
502
+ READ_ROOT="$(cd "$READ_ROOT" && pwd -P)" || skip "cannot resolve repository/read root '$READ_ROOT'"
503
+ if [ -n "${CROSS_MODEL_REPO_ROOT:-}" ]; then
504
+ REPO_ROOT="$CROSS_MODEL_REPO_ROOT"
505
+ elif command -v git >/dev/null 2>&1 && _git_root="$(git -C "$READ_ROOT" rev-parse --show-toplevel 2>/dev/null)"; then
506
+ REPO_ROOT="$_git_root"
507
+ else
508
+ REPO_ROOT="$(pwd -P)"
509
+ fi
510
+ [ -d "$REPO_ROOT" ] || skip "declared repository root '$REPO_ROOT' is not a directory"
511
+ REPO_ROOT="$(cd "$REPO_ROOT" && pwd -P)" || skip "cannot resolve repository root '$REPO_ROOT'"
512
+ case "$READ_ROOT/" in "$REPO_ROOT/"*) ;; *) skip "read root '$READ_ROOT' is outside repository root '$REPO_ROOT'" ;; esac
513
+
514
+ [ -n "$RUN_DIR" ] || skip "run-dir not given; skipping"
515
+ if [ -d "$RUN_DIR" ]; then
516
+ RUN_DIR_RESOLVED="$(cd "$RUN_DIR" && pwd -P)" || skip "cannot resolve run-dir '$RUN_DIR'"
517
+ else
518
+ RUN_PARENT="$(dirname "$RUN_DIR")"
519
+ RUN_BASENAME="$(basename "$RUN_DIR")"
520
+ [ -d "$RUN_PARENT" ] || skip "run-dir parent '$RUN_PARENT' is not a directory"
521
+ RUN_PARENT="$(cd "$RUN_PARENT" && pwd -P)" || skip "cannot resolve run-dir parent '$RUN_PARENT'"
522
+ RUN_DIR_RESOLVED="$RUN_PARENT/$RUN_BASENAME"
523
+ fi
524
+ case "$RUN_DIR_RESOLVED/" in "$REPO_ROOT/"*) skip "run-dir must be outside the repository" ;; esac
525
+ [ -d "$RUN_DIR_RESOLVED" ] || skip "run-dir '$RUN_DIR' must already exist"
526
+ RUN_DIR="$RUN_DIR_RESOLVED"
527
+ chmod 700 "$RUN_DIR" 2>/dev/null || skip "run-dir '$RUN_DIR' could not be made private"
528
+ command -v jq >/dev/null 2>&1 || skip "jq not installed; skipping"
529
+ INCLUDE_PATHS="${CROSS_MODEL_INCLUDE_PATHS:-}"
530
+ EXCLUDE_PATHS="${CROSS_MODEL_EXCLUDE_PATHS:-}"
531
+
532
+ case "$HOST_PROVIDER" in
533
+ codex|claude|grok|composer|gemini|deepseek|unknown) ;;
534
+ *) skip "host serving family '$HOST_PROVIDER' invalid (want codex|claude|grok|composer|gemini|deepseek|unknown)" ;;
535
+ esac
536
+ case "$HOST_HARNESS" in
537
+ codex|claude|grok|cursor|opencode|antigravity|omp|dsh|unknown) ;;
538
+ *) skip "host harness '$HOST_HARNESS' invalid (want codex|claude|grok|cursor|opencode|antigravity|omp|dsh|unknown)" ;;
539
+ esac
540
+
541
+ case "$FIXED_ROUTE" in
542
+ codex|claude|grok-cli|grok-cursor|cursor|composer|opencode|antigravity) ;;
543
+ *) skip "unknown fixed route '${FIXED_ROUTE:-<empty>}'; host must resolve one route before egress" ;;
544
+ esac
545
+ TARGET="$(route_target "$FIXED_ROUTE")" || skip "unknown fixed route '${FIXED_ROUTE:-<empty>}'; host must resolve one route before egress"
546
+ apply_model_override "$FIXED_ROUTE" || skip "model override '${CROSS_MODEL_MODEL_OVERRIDE:-}' not compatible with route '$FIXED_ROUTE'"
547
+ # An Antigravity host can serve several families; never send it to itself.
548
+ if [ "$FIXED_ROUTE" = antigravity ] && { [ "$HOST_PROVIDER" = gemini ] || [ "$HOST_HARNESS" = antigravity ]; }; then
549
+ skip "fixed route 'antigravity' is the host's own Gemini family or Antigravity harness; skipping before egress"
550
+ fi
551
+
552
+ # --- self-locate skill root + canonical sibling files ----------------------
553
+ SKILL_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || skip "cannot resolve skill root; skipping"
554
+ PERSONA="$SKILL_ROOT/references/agents/pov-peer.md"
555
+ SCHEMA="$SKILL_ROOT/references/pov-schema.json"
556
+ [ -f "$PERSONA" ] || skip "persona brief not found at $PERSONA; skipping"
557
+ [ -f "$SCHEMA" ] || skip "POV schema not found at $SCHEMA; skipping"
558
+ SCHEMA_CONTENT="$(cat "$SCHEMA")" || skip "cannot read POV schema; skipping"
559
+
560
+ # --- validate the host-resolved fixed route and egress allowlist -------------
561
+ ALLOW="${CROSS_MODEL_PEERS:-}" # optional egress allowlist (R19)
562
+
563
+ in_csv() { case ",$2," in *",$1,"*) return 0 ;; *) return 1 ;; esac; }
564
+ # Require a usable POV, not merely valid JSON. Error envelopes and incomplete
565
+ # objects fail the fixed route and return control to the host without publishing
566
+ # a cross-check artifact.
567
+ pov_shaped() { # <file>: schema-shaped POV (finality is a separate gate)
568
+ [ -s "$1" ] && jq -e \
569
+ '(.voice|type)=="string" and (.voice|length)>0 and (.position|type)=="string" and (.position|length)>0 and (.reasoning|type)=="string" and (.reasoning|length)>0 and (.evidence|type)=="array" and all(.evidence[]; type=="string" and length>0) and (.external_check=="ran" or .external_check=="unavailable") and (.mode=="independent" or .mode=="skeptic") and (.movement=="initial" or .movement=="moved" or .movement=="held")' \
570
+ "$1" >/dev/null 2>&1
571
+ }
572
+ out_missing_or_invalid() { ! pov_shaped "$RAW_OUT"; }
573
+
574
+ # A usable position is a settled answer to the framed question. The peer
575
+ # declares that itself through the schema's required `final` boolean: a
576
+ # schema-shaped artifact whose `final` is not true is non-final (a placeholder
577
+ # emitted before the peer finished inspecting -- observed on grok-cli in the
578
+ # #1402 panel, where the model returned its final schema object on turn one)
579
+ # and must not be published as a peer voice. Finality lives in the owned output
580
+ # contract, never in a phrase list over model prose.
581
+ out_final() { [ -s "$RAW_OUT" ] && jq -e '.final == true' "$RAW_OUT" >/dev/null 2>&1; }
582
+
583
+ # Backward-compatible matrix: legacy `composer` continues to sanction Cursor as
584
+ # the Grok intermediary, while the distinct Cursor-default target requires the
585
+ # new `cursor` key. Composer itself remains sanctioned by `composer`.
586
+ route_allowlisted() {
587
+ [ -z "$ALLOW" ] && return 0
588
+ case "$1" in
589
+ codex|claude|grok-cli) in_csv "$(route_target "$1")" "$ALLOW" ;;
590
+ cursor) in_csv cursor "$ALLOW" ;;
591
+ composer) in_csv composer "$ALLOW" ;;
592
+ grok-cursor)
593
+ in_csv grok "$ALLOW" && { in_csv cursor "$ALLOW" || in_csv composer "$ALLOW"; }
594
+ ;;
595
+ opencode|antigravity) in_csv "$1" "$ALLOW" ;;
596
+ *) return 1 ;;
597
+ esac
598
+ }
599
+
600
+ # Soft size gate: peer prompt embeds the full subject payload. Over-budget payloads skip
601
+ # cleanly (R11) rather than collapsing silently inside the provider context window.
602
+ MAX_PAYLOAD_CHARS="${CROSS_MODEL_MAX_PAYLOAD_CHARS:-200000}"
603
+ case "$MAX_PAYLOAD_CHARS" in ''|*[!0-9]*) MAX_PAYLOAD_CHARS=200000 ;; esac
604
+ PAYLOAD_CHARS="$(wc -c <"$PAYLOAD_PATH" | tr -d '[:space:]')"
605
+ if [ "$PAYLOAD_CHARS" -gt "$MAX_PAYLOAD_CHARS" ]; then
606
+ skip "subject payload is ${PAYLOAD_CHARS} bytes (limit ${MAX_PAYLOAD_CHARS}); skipping cross-model pass rather than truncating"
607
+ fi
608
+
609
+ # The Codex desktop app (Codex.app, or ChatGPT.app since the July 2026 merger)
610
+ # ships `codex` at Contents/Resources without linking it onto PATH (#1272).
611
+ # Append, never prepend, so a PATH-installed CLI stays authoritative.
612
+ # CROSS_MODEL_CODEX_APP_DIRS (colon-separated) overrides the probed dirs.
613
+ if ! command -v codex >/dev/null 2>&1; then
614
+ OLDIFS="$IFS"; IFS=':'
615
+ 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
616
+ if [ -n "$d" ] && [ -x "$d/codex" ]; then PATH="${PATH:+$PATH:}$d"; export PATH; break; fi
617
+ done
618
+ IFS="$OLDIFS"
619
+ fi
620
+
621
+ route_allowlisted "$FIXED_ROUTE" || skip "fixed route '$FIXED_ROUTE' is not fully sanctioned by CROSS_MODEL_PEERS; skipping before egress"
622
+ acpx_preflight "$FIXED_ROUTE" "$READ_ROOT" || skip "transport unavailable (pre-egress, $ACPX_SCOPE): $ACPX_UNAVAILABLE"
623
+ log "fixed cross-model POV route: target=$TARGET route=$FIXED_ROUTE (host $HOST_PROVIDER excluded)"
624
+
625
+ # --- compose the peer prompt from the canonical persona (single source) ----
626
+ # The payload is prepared by ce-pov and embeds the framed question plus any
627
+ # conversation-only subject material needed for this round. Repository evidence
628
+ # stays in the shared working tree for the peer to inspect directly.
629
+ SCRATCH_PARENT="${CROSS_MODEL_SCRATCH_PARENT:-${TMPDIR:-/tmp}}"
630
+ [ -d "$SCRATCH_PARENT" ] || mkdir -p "$SCRATCH_PARENT" 2>/dev/null || skip "private scratch parent '$SCRATCH_PARENT' unavailable"
631
+ SCRATCH_PARENT="$(cd "$SCRATCH_PARENT" && pwd -P)" || skip "cannot resolve private scratch parent"
632
+ case "$SCRATCH_PARENT/" in "$REPO_ROOT/"*) skip "private scratch parent must be outside the repository" ;; esac
633
+ if ! PEER_WORKDIR="$(mktemp -d "$SCRATCH_PARENT/xmodel-pov-peer-XXXXXX")"; then
634
+ skip "provider $TARGET workspace isolation unavailable; skipping provider"
635
+ fi
636
+ trap 'cleanup_private_scratch' EXIT
637
+ chmod 700 "$PEER_WORKDIR" 2>/dev/null || { cleanup_private_scratch; skip "cannot make peer scratch private"; }
638
+ PROMPT_FILE="$PEER_WORKDIR/prompt.md"
639
+ PEERLOG="$PEER_WORKDIR/stdout.log"
640
+ # acpx prints the ACP stream on stdout; npm and adapter diagnostics go to stderr.
641
+ PEERERR="$PEER_WORKDIR/stderr.log"
642
+ RAW_OUT="$PEER_WORKDIR/pov-$TARGET.raw.json"
643
+ TEXT_OUT="$PEER_WORKDIR/reply.txt"
644
+ MCP_CONFIG="$PEER_WORKDIR/mcp.json"
645
+ : > "$PROMPT_FILE"; : > "$PEERLOG"; : > "$PEERERR"
646
+ printf '{"mcpServers":[]}\n' > "$MCP_CONFIG"
647
+ chmod 600 "$PROMPT_FILE" "$PEERLOG" "$PEERERR" "$MCP_CONFIG" 2>/dev/null || { cleanup_private_scratch; skip "cannot make peer scratch files private"; }
648
+ CLAUDE_WRAPPER=""
649
+ if [ "$FIXED_ROUTE" = claude ]; then
650
+ CLAUDE_WRAPPER="$(acpx_claude_wrapper "$PEER_WORKDIR")" || { cleanup_private_scratch; skip "transport unavailable (pre-egress, route): cannot prepare the Claude --safe-mode launcher"; }
651
+ fi
652
+ GEMINI_HOME_DIR=""
653
+ if [ "$FIXED_ROUTE" = antigravity ]; then
654
+ GEMINI_HOME_DIR="$(acpx_antigravity_home "$PEER_WORKDIR")" || { cleanup_private_scratch; skip "transport unavailable (pre-egress, route): cannot prepare a private Antigravity home"; }
655
+ fi
656
+ {
657
+ cat "$PERSONA"
658
+ printf '\n\n---\n\n'
659
+ printf 'This is an authorized, read-only point-of-view cross-check on the maintainer\047s own project.\n'
660
+ printf 'Return ONE JSON object and nothing else (no prose, no code fence) matching this schema:\n\n'
661
+ printf '%s' "$SCHEMA_CONTENT"
662
+ printf '\n\nSet the top-level "voice" field to "peer" (it will be namespaced to the provider on fold-in).\n'
663
+ printf '\n<repository-read-scope enforcement="cooperative-unless-adapter-supported">\n'
664
+ printf 'root: %s\nincludes: %s\nexcludes: %s\n' "$READ_ROOT" "${INCLUDE_PATHS:-<all>}" "${EXCLUDE_PATHS:-<none>}"
665
+ printf '</repository-read-scope>\n'
666
+ printf '\n<subject-payload>\n'
667
+ cat "$PAYLOAD_PATH"
668
+ printf '\n</subject-payload>\n'
669
+ } > "$PROMPT_FILE"
670
+ # codex-acp's only refusal option is cancel, which ends the turn.
671
+ if [ "$FIXED_ROUTE" = codex ]; then
672
+ 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 reasoning.\n' >> "$PROMPT_FILE"
673
+ fi
674
+
675
+ # --- run machinery -----------------------------------------------------------
676
+ # Every route streams the ACP session, so the idle cap is the liveness guard and
677
+ # HARD_SECS backstops a peer that stays productive past any useful budget. acpx
678
+ # gets the same budget as --timeout, but it applies that per phase, so this
679
+ # script's wall clock stays authoritative. This skill's default HARD_SECS stays
680
+ # at 600s because its codex route runs the lower sol/high tier -- ce-code-review
681
+ # and ce-doc-review run luna/xhigh and default higher. `CROSS_MODEL_HARD_SECS`
682
+ # is shared across all three, and the orchestrator's aggregate deadline derives
683
+ # from it (see references/cross-model-panel.md), so a raised knob raises both windows.
684
+ IDLE_SECS="${CROSS_MODEL_IDLE_SECS:-180}"
685
+ HARD_SECS="${CROSS_MODEL_HARD_SECS:-600}"
686
+ RETRY_MIN_SECS="${CROSS_MODEL_RETRY_MIN_SECS:-60}" # least window worth spending on a non-final retry
687
+
688
+ # Reap a backgrounded job's whole process group: TERM, then KILL after a grace.
689
+ # True while $1 is a live (non-zombie) process. kill -0 succeeds on zombies
690
+ # until wait reaps them, so idle polls must not treat zombies as still running.
691
+ # macOS/BSD often report defunct state as "Z+" (not bare "Z").
692
+ # Match peer-job-runner._pid_running: empty state after ps means not alive
693
+ # (avoids zombie spin). Fall back to kill -0 only when ps itself is missing.
694
+ peer_alive() {
695
+ local st
696
+ kill -0 "$1" 2>/dev/null || return 1
697
+ if ! command -v ps >/dev/null 2>&1; then
698
+ return 0
699
+ fi
700
+ # Git Bash's ps has no -o; fall back to its -p lookup, which has no zombie state.
701
+ if ! st="$(ps -o state= -p "$1" 2>/dev/null)"; then
702
+ ps -p "$1" >/dev/null 2>&1
703
+ return
704
+ fi
705
+ st="$(printf '%s' "$st" | tr -d ' \n')"
706
+ [ -n "$st" ] || return 1
707
+ [ "${st#Z}" = "$st" ]
708
+ }
709
+
710
+ reap() {
711
+ # Signal the process group and grace-poll without wait(). The caller alone
712
+ # wait()s the leader so PEER_EXIT is the real exit status — a second
713
+ # wait here would fail after we already reaped and mark healthy exits as
714
+ # timed-out (#1270 Bugbot). No background KILL timer: orphaned timers can
715
+ # hit recycled PIDs under bun --parallel.
716
+ local pid="$1" grp
717
+ if kill -TERM -- -"$pid" 2>/dev/null; then grp=1; else kill -TERM "$pid" 2>/dev/null || true; grp=0; fi
718
+ for _ in 1 2 3 4 5; do
719
+ if ! peer_alive "$pid"; then
720
+ # Leader exited/zombied — sweep any group survivors; caller wait()s.
721
+ [ "$grp" = 1 ] && kill -KILL -- -"$pid" 2>/dev/null || true
722
+ return 0
723
+ fi
724
+ sleep 1
725
+ done
726
+ if [ "$grp" = 1 ]; then kill -KILL -- -"$pid" 2>/dev/null; else kill -KILL "$pid" 2>/dev/null; fi
727
+ }
728
+
729
+ # TERM/INT: reap the live peer group, then exit cleanly (HUP remains ignored).
730
+ on_term() {
731
+ if [ -n "${_HEARTBEAT_PID:-}" ]; then
732
+ stop_heartbeat
733
+ fi
734
+ if [ -n "${ACTIVE_PEER_PID:-}" ]; then
735
+ log "received TERM/INT; reaping peer process group $ACTIVE_PEER_PID"
736
+ _term_peer="$ACTIVE_PEER_PID"
737
+ reap "$_term_peer" 2>/dev/null || true
738
+ # reap only signals the group; wait reaps the leader so it cannot orphan.
739
+ wait "$_term_peer" 2>/dev/null || true
740
+ ACTIVE_PEER_PID=""
741
+ fi
742
+ exit 0
743
+ }
744
+ trap 'on_term' TERM INT
745
+
746
+ # Build the CMD array for a route (bash 3.2-safe: no mapfile).
747
+ build_cmd() {
748
+ CMD=()
749
+ # NUL-delimited so a token containing spaces or newlines stays ONE argv element.
750
+ while IFS= read -r -d '' tok; do CMD+=("$tok"); done < <(adapter_argv "$1")
751
+ }
752
+
753
+ # --- liveness heartbeat -----------------------------------------------------
754
+ # The peer CLI streams into $PEERLOG (private), so nothing reaches this script's
755
+ # own stdout/stderr during a long model call. An outer supervisor that watches
756
+ # THIS process's output for liveness (the peer-job runner's out.log byte-growth
757
+ # idle window) would mistake a healthy multi-minute run for a wedge. A background
758
+ # writer emits one stderr line every CROSS_MODEL_HEARTBEAT_SECS (default 60s) so
759
+ # that liveness is visible; it is torn down as soon as the foreground wait returns,
760
+ # so it adds no latency to a fast run. Keep this block byte-identical across
761
+ # the peer worker scripts (lifecycle parity).
762
+ _HEARTBEAT_PID=""
763
+ start_heartbeat() {
764
+ local every="${CROSS_MODEL_HEARTBEAT_SECS:-60}" parent_pid="$$"
765
+ # Floor to 1s: a non-numeric or 0 value would make `sleep` return instantly and
766
+ # spin the loop, flooding out.log into the runner's byte cap.
767
+ case "$every" in ''|*[!0-9]*) every=60 ;; esac; [ "$every" -lt 1 ] && every=1
768
+ _HEARTBEAT_READY=0
769
+ trap '_HEARTBEAT_READY=1' USR1
770
+ # Callers restore set +m after launching the peer, so without this the
771
+ # heartbeat inherits the worker pgid and kill -- -PID cannot reach the sleep.
772
+ local prev_m; case "$-" in *m*) prev_m=1;; *) prev_m=0;; esac
773
+ set -m
774
+ ( local t0 n sleeper=""
775
+ trap 'kill "${sleeper:-}" 2>/dev/null || true; exit 0' TERM INT
776
+ kill -USR1 "$parent_pid"
777
+ t0="$(date +%s)"
778
+ while kill -0 "$parent_pid" 2>/dev/null; do
779
+ sleep "$every" & sleeper=$!
780
+ wait "$sleeper" 2>/dev/null || exit 0
781
+ sleeper=""
782
+ kill -0 "$parent_pid" 2>/dev/null || break
783
+ n="$(date +%s)"; log "peer alive ($(( n - t0 ))s elapsed)"
784
+ done ) &
785
+ _HEARTBEAT_PID=$!
786
+ [ "$prev_m" = 0 ] && set +m
787
+ while [ "$_HEARTBEAT_READY" != 1 ] && kill -0 "$_HEARTBEAT_PID" 2>/dev/null; do sleep 0.01 || true; done
788
+ trap - USR1
789
+ }
790
+ stop_heartbeat() {
791
+ if [ -n "$_HEARTBEAT_PID" ]; then
792
+ # Leader-only TERM is deferred until the inner `wait $sleeper` returns, so
793
+ # the default 60s interval would block this wait. Signal the process group.
794
+ kill -- -"$_HEARTBEAT_PID" 2>/dev/null || kill "$_HEARTBEAT_PID" 2>/dev/null || true
795
+ wait "$_HEARTBEAT_PID" 2>/dev/null || true
796
+ fi
797
+ _HEARTBEAT_PID=""
798
+ }
799
+
800
+ run_peer_cmd() { # CMD already built for PEER_ROUTE; streams to PEERLOG, diagnostics to PEERERR
801
+ local prev; case "$-" in *m*) prev=1;; *) prev=0;; esac
802
+ SESSION_STALLED=false
803
+ set -m
804
+ # Start npx from private scratch: npx resolves packages from its working
805
+ # directory's node_modules and .npmrc first, and acpx gets the agent's cwd from --cwd.
806
+ ( cd "$(dirname "$PEERLOG")" && exec "${CMD[@]}" ) < /dev/null > "$PEERLOG" 2>"$PEERERR" &
807
+ local pid=$!
808
+ ACTIVE_PEER_PID="$pid"
809
+ [ "$prev" = 0 ] && set +m
810
+ start_heartbeat
811
+ local start last=-1 lastchg now size session=open
812
+ [ "$PEER_ROUTE" = antigravity ] && session=starting
813
+ start="$(date +%s)"; lastchg="$start"
814
+ while peer_alive "$pid"; do
815
+ now="$(date +%s)"; size="$(wc -c <"$PEERLOG" 2>/dev/null || echo 0)"
816
+ [ "$size" != "$last" ] && { last="$size"; lastchg="$now"; }
817
+ if [ $(( now - lastchg )) -ge "$IDLE_SECS" ]; then
818
+ log "peer output idle ${IDLE_SECS}s; reaping peer process group"; reap "$pid"; break
819
+ fi
820
+ if [ $(( now - start )) -ge "$HARD_SECS" ]; then
821
+ log "peer exceeded hard cap ${HARD_SECS}s; reaping peer process group"; reap "$pid"; break
822
+ fi
823
+ if [ "$session" != open ] && [ $(( now - start )) -ge "$ACPX_SESSION_START_SECS" ]; then
824
+ session="$(acpx_session_state "$PEERLOG")"
825
+ if [ "$session" = pending ]; then
826
+ SESSION_STALLED=true
827
+ log "Antigravity opened no session within ${ACPX_SESSION_START_SECS}s; reaping peer process group"; reap "$pid"; break
828
+ fi
829
+ fi
830
+ sleep 1
831
+ done
832
+ wait "$pid" 2>/dev/null
833
+ PEER_EXIT=$?
834
+ # Sweep any survivor the provider left in its OWN process group. `set -m` puts
835
+ # the provider in a separate pgid, and on a clean worker exit the runner's
836
+ # final sweep only kills the worker's pgid while a group-orphan reparents off
837
+ # the worker's process tree -- so it must be reaped here, where the pgid is
838
+ # known. reap() returns immediately when the group is already empty.
839
+ reap "$pid" 2>/dev/null || true
840
+ stop_heartbeat
841
+ ACTIVE_PEER_PID=""
842
+ }
843
+
844
+ # Recover a POV object from the agent's reply text, which may wrap it in prose or a fence.
845
+ recover_pov_json() { # <logfile> <outfile>
846
+ # Probe execution, not just PATH presence — Windows Store's python3 stub
847
+ # satisfies `command -v` then exits nonzero (see resolve-python convention).
848
+ local py
849
+ py="$(for c in python3 python py; do command -v "$c" >/dev/null 2>&1 && "$c" -c '' >/dev/null 2>&1 && { echo "$c"; break; }; done)"
850
+ [ -n "$py" ] || return 1
851
+ "$py" - "$1" "$2" <<'PY' 2>/dev/null
852
+ import sys, json
853
+ txt = open(sys.argv[1], encoding="utf-8", errors="replace").read()
854
+ best = None
855
+ best_score = -1
856
+ decoder = json.JSONDecoder()
857
+ def shaped(d):
858
+ # Mirror of pov_shaped() in the shell: the same field types and enums,
859
+ # so ranking cannot promote a fully keyed but invalid draft.
860
+ return (
861
+ isinstance(d.get("voice"), str) and d["voice"] != ""
862
+ and isinstance(d.get("position"), str) and d["position"] != ""
863
+ and isinstance(d.get("reasoning"), str) and d["reasoning"] != ""
864
+ and isinstance(d.get("evidence"), list)
865
+ and all(isinstance(e, str) and e != "" for e in d["evidence"])
866
+ and d.get("external_check") in ("ran", "unavailable")
867
+ and d.get("mode") in ("independent", "skeptic")
868
+ and d.get("movement") in ("initial", "moved", "held")
869
+ )
870
+
871
+ def score(d):
872
+ # Prefer a schema-shaped final POV over a shaped non-final one over any
873
+ # dict that merely carries a position; ties go to the later candidate.
874
+ if shaped(d) and d.get("final") is True:
875
+ return 2
876
+ if shaped(d):
877
+ return 1
878
+ return 0
879
+
880
+ def inspect(value):
881
+ global best, best_score
882
+ if isinstance(value, dict):
883
+ if "position" in value:
884
+ sc = score(value)
885
+ if sc >= best_score:
886
+ best, best_score = value, sc
887
+ for child in value.values():
888
+ inspect(child)
889
+ elif isinstance(value, list):
890
+ for child in value:
891
+ inspect(child)
892
+ elif isinstance(value, str):
893
+ for i, ch in enumerate(value):
894
+ if ch not in "{[":
895
+ continue
896
+ try:
897
+ child, _ = decoder.raw_decode(value, i)
898
+ inspect(child)
899
+ except Exception:
900
+ pass
901
+
902
+ inspect(txt)
903
+ if best is not None: open(sys.argv[2], "w").write(json.dumps(best))
904
+ PY
905
+ [ -s "$2" ]
906
+ }
907
+
908
+ bounded_failure_evidence() { # <logfile>; bounded head+tail of a plain-text log
909
+ local path="$1" evidence
910
+ # bash 3.2 rewrites newlines in a large string superlinearly, so read only the
911
+ # ends of a long log instead of the whole file.
912
+ if [ "$(wc -c <"$path")" -le 600 ]; then evidence="$(cat "$path")"
913
+ else IFS= read -r -d '' -n 300 evidence <"$path"; evidence="$evidence ... $(tail -c 300 "$path")"; fi
914
+ evidence="${evidence//$'\n'/ }"
915
+ if [ "${#evidence}" -gt 300 ]; then
916
+ evidence="${evidence:0:147} ... ${evidence: -147}"
917
+ fi
918
+ printf '%s' "$evidence"
919
+ }
920
+
921
+ # Run one route for a provider; leaves a schema-shaped (pre-normalization) $RAW_OUT on success.
922
+ # Success is the prompt's own end_turn result, not acpx's exit status.
923
+ attempt_route() { # <provider> <route>
924
+ local provider="$1" route="$2" outcome reason started remaining node_line cancel_reason
925
+ : > "$PEERLOG"; : > "$PEERERR"; rm -f "$RAW_OUT" "$OUT" "$TEXT_OUT"
926
+ RUN_SUCCEEDED=false
927
+ build_cmd "$route"
928
+ PEER_ROUTE="$route"
929
+ log "peer run: provider=$provider route=$route model=$(route_model "$route") transport=acpx@$ACPX_VERSION POV read-only least-privilege (idle ${IDLE_SECS}s / hard ${HARD_SECS}s)"
930
+ started="$(date +%s)"
931
+ run_peer_cmd
932
+ if acpx_stale_npm_metadata "$PEERERR" "$PEERLOG" && [ "$(acpx_outcome "$PEERLOG")" = not-sent ]; then
933
+ remaining=$(( HARD_SECS - ( $(date +%s) - started ) ))
934
+ if [ "$remaining" -gt 0 ]; then
935
+ log "npm resolved against stale cached package metadata; retrying the same route once with fresh metadata in the ${remaining}s left (nothing was sent)"
936
+ ACPX_NPM_REFRESH=true
937
+ HARD_SECS="$remaining"
938
+ ROUTE_STARTED_AT="$(date +%s)"
939
+ build_cmd "$route"
940
+ run_peer_cmd
941
+ fi
942
+ fi
943
+ outcome="$(acpx_outcome "$PEERLOG")"
944
+ case "$outcome" in
945
+ end_turn)
946
+ RUN_SUCCEEDED=true
947
+ acpx_text "$PEERLOG" "$TEXT_OUT" && recover_pov_json "$TEXT_OUT" "$RAW_OUT"
948
+ extract_model_receipt "$route"
949
+ ;;
950
+ not-sent)
951
+ # Nothing reached the provider: npm could not fetch acpx (every route
952
+ # fails alike), or the adapter refused before the prompt (this route only).
953
+ if grep -q '^npm error' "$PEERERR" 2>/dev/null; then
954
+ reason="$(grep -m1 '^npm error' "$PEERERR")"
955
+ PRE_EGRESS="transport unavailable (pre-egress, shared): ${reason:0:200}"
956
+ elif [ "$SESSION_STALLED" = true ]; then
957
+ 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"
958
+ else
959
+ node_line="$(acpx_node_startup_line "$PEERERR")"
960
+ if [ -n "$node_line" ]; then
961
+ PRE_EGRESS="transport unavailable (pre-egress, route): ${node_line:0:200}"
962
+ else
963
+ reason="$(acpx_failure_evidence "$PEERLOG")"
964
+ PRE_EGRESS="transport unavailable (pre-egress, route): ${reason:-acpx exited $PEER_EXIT before sending the prompt}"
965
+ fi
966
+ fi
967
+ ;;
968
+ *)
969
+ cancel_reason="$(acpx_permission_cancel_reason "$PEERLOG" "$PEER_EXIT")"
970
+ if [ -n "$cancel_reason" ]; then
971
+ log "peer run ended: $cancel_reason (acpx exit $PEER_EXIT)"
972
+ else
973
+ log "peer run ended with $outcome (acpx exit $PEER_EXIT)"
974
+ fi
975
+ ;;
976
+ esac
977
+ }
978
+
979
+ # Run the one fixed route. Any failure returns control to the host without
980
+ # trying a different target, provider, or intermediary.
981
+ run_fixed_route() {
982
+ local provider="$TARGET"
983
+ OUT="$RUN_DIR/pov-$provider.json"
984
+ ACTUAL_ROUTE="$FIXED_ROUTE"
985
+ ROUTE_STARTED_AT="$(date +%s)"
986
+ PRE_EGRESS=""
987
+ attempt_route "$provider" "$FIXED_ROUTE"
988
+ # A configured model is exact; only the editorial Argon request falls back.
989
+ if [ "$FIXED_ROUTE" = antigravity ] && [ "$(route_model antigravity)" = "$M_ANTIGRAVITY" ] &&
990
+ [ "${CROSS_MODEL_MODEL_OVERRIDE_TARGET:-}" != antigravity ]; then
991
+ case "$PRE_EGRESS" in
992
+ *"did not advertise"*)
993
+ # The fallback shares the route's window; the non-final retry below
994
+ # then measures what is left from this restart.
995
+ remaining=$(( HARD_SECS - ( $(date +%s) - ROUTE_STARTED_AT ) ))
996
+ if [ "$remaining" -gt 0 ]; then
997
+ log "Antigravity does not offer $M_ANTIGRAVITY on this account; falling back to $M_ANTIGRAVITY_FALLBACK"
998
+ ANTIGRAVITY_MODEL="$M_ANTIGRAVITY_FALLBACK"
999
+ PRE_EGRESS=""
1000
+ HARD_SECS="$remaining"
1001
+ ROUTE_STARTED_AT="$(date +%s)"
1002
+ attempt_route "$provider" "$FIXED_ROUTE"
1003
+ fi
1004
+ ;;
1005
+ esac
1006
+ fi
1007
+ [ -z "$PRE_EGRESS" ] || { cleanup_private_scratch; skip "$PRE_EGRESS"; }
1008
+ # One bounded retry on the same route, target, model, and scope; the only
1009
+ # change is a final-answer instruction. The retry gets only what is left of
1010
+ # this worker's HARD_SECS window so both attempts stay inside the panel's
1011
+ # aggregate deadline (cross-model-panel.md: CROSS_MODEL_HARD_SECS + 10s);
1012
+ # too little left means no retry. A second non-final position drops the
1013
+ # voice with skip evidence -- no route hopping.
1014
+ nonfinal_position=""
1015
+ if [ "$RUN_SUCCEEDED" = true ] && ! out_missing_or_invalid && ! out_final; then
1016
+ position="$(jq -r '.position' "$RAW_OUT" 2>/dev/null)"
1017
+ remaining=$(( HARD_SECS - ( $(date +%s) - ROUTE_STARTED_AT ) ))
1018
+ if [ "$remaining" -lt "$RETRY_MIN_SECS" ]; then
1019
+ log "peer returned a non-final position (\"${position:0:120}\") with ${remaining}s of the ${HARD_SECS}s window left; not retrying"
1020
+ nonfinal_position="$position"
1021
+ rm -f "$RAW_OUT"
1022
+ else
1023
+ log "peer returned a non-final position (\"${position:0:120}\"); retrying once on the same route with a final-answer requirement (${remaining}s left)"
1024
+ printf '\n\nYour previous response set final to false. This response is the final one: inspect the subject and shared working tree now, then return the settled position with its evidence and final set to true.\n' >> "$PROMPT_FILE"
1025
+ HARD_SECS="$remaining"
1026
+ attempt_route "$provider" "$FIXED_ROUTE"
1027
+ if [ "$RUN_SUCCEEDED" = true ] && ! out_missing_or_invalid && ! out_final; then
1028
+ nonfinal_position="$(jq -r '.position' "$RAW_OUT" 2>/dev/null)"
1029
+ rm -f "$RAW_OUT"
1030
+ fi
1031
+ fi
1032
+ fi
1033
+
1034
+ # --- normalize + validate against the peer POV contract ------------------
1035
+ # Force voice = peer-<provider>, preserve the POV fields, and add route/model
1036
+ # receipts from the route that actually ran. The peer never self-attributes an
1037
+ # unverifiable serving model.
1038
+ # Publish ONLY the normalized OUT into RUN_DIR. RAW_OUT lives in the per-peer
1039
+ # workspace and is never a fold-in artifact — if this script dies before normalize
1040
+ # (orphaned launch), synthesis finds no .json in RUN_DIR.
1041
+ rm -f "$OUT"
1042
+ if [ -s "$RAW_OUT" ]; then
1043
+ _norm="$PEER_WORKDIR/normalized.json"
1044
+ case "$ACTUAL_ROUTE:$MODEL_ACTUAL" in
1045
+ cursor:*) serving_family="unknown" ;;
1046
+ composer:unverified|grok-cursor:unverified) serving_family="unknown" ;;
1047
+ *) serving_family="$(target_serving_family "$provider")" ;;
1048
+ esac
1049
+ independence=false
1050
+ [ "$HOST_PROVIDER" != "unknown" ] && [ "$serving_family" != "unknown" ] && [ "$HOST_PROVIDER" != "$serving_family" ] && independence=true
1051
+ if jq --arg v "peer-$provider" --arg route "$ACTUAL_ROUTE" \
1052
+ --arg target "$provider" --arg harness "$(route_harness "$ACTUAL_ROUTE")" \
1053
+ --arg family "$serving_family" \
1054
+ --arg mreq "$(route_model "$ACTUAL_ROUTE")" --arg mact "$MODEL_ACTUAL" \
1055
+ --argjson independent "$independence" \
1056
+ 'if ((.voice|type)=="string" and (.voice|length)>0 and (.position|type)=="string" and (.position|length)>0 and (.reasoning|type)=="string" and (.reasoning|length)>0 and (.evidence|type)=="array" and all(.evidence[]; type=="string" and length>0) and (.external_check=="ran" or .external_check=="unavailable") and (.mode=="independent" or .mode=="skeptic") and (.movement=="initial" or .movement=="moved" or .movement=="held") and .final==true)
1057
+ then { voice: $v,
1058
+ cross_model_route: $route,
1059
+ cross_model_target: $target,
1060
+ cross_model_harness: $harness,
1061
+ serving_family: $family,
1062
+ model_requested: $mreq,
1063
+ model_actual: $mact,
1064
+ independence_verified: $independent,
1065
+ position: .position,
1066
+ reasoning: .reasoning,
1067
+ evidence: .evidence,
1068
+ external_check: .external_check,
1069
+ mode: .mode,
1070
+ movement: .movement,
1071
+ final: true }
1072
+ else empty end' \
1073
+ "$RAW_OUT" > "$_norm" 2>/dev/null; then
1074
+ mv "$_norm" "$OUT"
1075
+ chmod 600 "$OUT" 2>/dev/null || { rm -f "$OUT"; log "could not make result artifact private"; }
1076
+ else
1077
+ rm -f "$_norm"
1078
+ fi
1079
+ rm -f "$RAW_OUT"
1080
+ fi
1081
+ if [ -s "$OUT" ] && jq -e \
1082
+ '(.voice|type)=="string" and (.position|type)=="string" and (.position|length)>0 and (.reasoning|type)=="string" and (.reasoning|length)>0 and (.evidence|type)=="array" and all(.evidence[]; type=="string" and length>0) and (.external_check=="ran" or .external_check=="unavailable") and (.mode=="independent" or .mode=="skeptic") and (.movement=="initial" or .movement=="moved" or .movement=="held") and (.independence_verified|type)=="boolean"' \
1083
+ "$OUT" >/dev/null 2>&1; then
1084
+ log "wrote peer POV to $OUT (voice peer-$provider)"
1085
+ else
1086
+ log "provider $provider produced no usable schema-shaped output; skipping fold-in"
1087
+ [ -n "$nonfinal_position" ] && log " peer skip evidence: non-final position: ${nonfinal_position:0:200}"
1088
+ # Surface bounded, actionable peer evidence so the orchestrator can
1089
+ # reason about WHY it was skipped (quota/usage-limit exhaustion vs an ordinary
1090
+ # empty review) and, in a repeated-pass session, deprioritize an exhausted
1091
+ # route. Provider errors arrive as ACP error messages or as reply text on
1092
+ # stdout; npm and adapter diagnostics on stderr.
1093
+ _pt="$(acpx_permission_cancel_reason "$PEERLOG" "$PEER_EXIT")"
1094
+ [ -n "$_pt" ] || _pt="$(acpx_failure_evidence "$PEERLOG")"
1095
+ [ -n "$_pt" ] && log " peer skip evidence: $_pt"
1096
+ _pr="$(acpx_reply_evidence "$PEERLOG")"
1097
+ [ -n "$_pr" ] && log " peer skip evidence (reply): $_pr"
1098
+ _pm="$(acpx_advertised_models "$PEERLOG")"
1099
+ [ -n "$_pm" ] && log " peer skip evidence (advertised models): $_pm"
1100
+ if [ -s "$PEERERR" ]; then
1101
+ _pe="$(bounded_failure_evidence "$PEERERR")"
1102
+ log " peer skip evidence (stderr): $_pe"
1103
+ fi
1104
+ rm -f "$OUT" "$RAW_OUT"
1105
+ fi
1106
+ cleanup_private_scratch
1107
+ }
1108
+
1109
+ run_fixed_route
1110
+ exit 0