create-egregore 0.21.6 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (719) hide show
  1. package/assets/egregore-launcher.js +58 -6
  2. package/assets/egregore-launcher.sh +15 -2
  3. package/lib/claude-runtime.js +2 -0
  4. package/lib/codex-runtime.js +2 -0
  5. package/lib/setup.js +20 -3
  6. package/package.json +2 -2
  7. package/runtime/claude/.claude/context/commit-format.md +7 -4
  8. package/runtime/claude/.claude/context/handoff-claiming.md +32 -0
  9. package/runtime/claude/.claude/context/notification-consent.md +12 -9
  10. package/runtime/claude/.claude/hooks/boundary-check.sh +14 -331
  11. package/runtime/claude/.claude/hooks/memory-read-guard.sh +9 -1
  12. package/runtime/claude/.claude/rules/voice-bedrock.md +6 -2
  13. package/runtime/claude/.claude/settings.json +1 -1
  14. package/runtime/claude/.claude/skills/commit/SKILL.md +5 -3
  15. package/runtime/claude/.claude/skills/dark-mode/SKILL.md +48 -369
  16. package/runtime/claude/.claude/skills/records/SKILL.md +158 -0
  17. package/runtime/claude/.claude/skills/reflect/SKILL.md +50 -21
  18. package/runtime/claude/.claude/skills/review-pr/SKILL.md +4 -2
  19. package/runtime/claude/.claude/skills/save/SKILL.md +9 -3
  20. package/runtime/claude/.claude/skills/summon/SKILL.md +138 -207
  21. package/runtime/claude/.claude/skills/tui-design/SKILL.md +1 -1
  22. package/runtime/claude/.claude/skills/update/SKILL.md +1 -1
  23. package/runtime/claude/.claude/skills/view/SKILL.md +52 -16
  24. package/runtime/claude/.claude/skills/view/compose-scaffold.html +19 -13
  25. package/runtime/claude/CLAUDE.md +59 -95
  26. package/runtime/claude/bin/agent.sh +231 -7
  27. package/runtime/claude/bin/api-call.sh +44 -23
  28. package/runtime/claude/bin/artifact-register.sh +7 -1
  29. package/runtime/claude/bin/artifact-writeback.sh +1 -1
  30. package/runtime/claude/bin/artifacts.sh +66 -265
  31. package/runtime/claude/bin/attendant.sh +4 -1
  32. package/runtime/claude/bin/boundary-guard.cjs +1848 -0
  33. package/runtime/claude/bin/boundary-hook.sh +41 -0
  34. package/runtime/claude/bin/boundary.sh +15 -155
  35. package/runtime/claude/bin/capture-run.sh +32 -2
  36. package/runtime/claude/bin/codex-render-spec.mjs +24 -46
  37. package/runtime/claude/bin/codex-session-start.sh +19 -0
  38. package/runtime/claude/bin/codex-skill-render.mjs +18 -0
  39. package/runtime/claude/bin/config-get.sh +10 -1
  40. package/runtime/claude/bin/connect-refresh.sh +16 -0
  41. package/runtime/claude/bin/corpus_map.py +5 -0
  42. package/runtime/claude/bin/data/capabilities/claude.json +1480 -0
  43. package/runtime/claude/bin/genui.sh +19 -0
  44. package/runtime/claude/bin/graph-batch.sh +1 -1
  45. package/runtime/claude/bin/graph-op.sh +14 -0
  46. package/runtime/claude/bin/graph.sh +1 -1
  47. package/runtime/claude/bin/handoff-pr-backfill.sh +1 -1
  48. package/runtime/claude/bin/handoff-run.sh +11 -6
  49. package/runtime/claude/bin/ingest-item.sh +1 -1
  50. package/runtime/claude/bin/ingest-surface.html +22 -4
  51. package/runtime/claude/bin/issue.sh +1 -1
  52. package/runtime/claude/bin/knowledge.sh +1 -1
  53. package/runtime/claude/bin/lib/card-delivery.sh +329 -0
  54. package/runtime/claude/bin/lib/config.sh +148 -3
  55. package/runtime/claude/bin/lib/context.sh +38 -67
  56. package/runtime/claude/bin/lib/genui-entry.py +36 -0
  57. package/runtime/claude/bin/lib/git-message.sh +25 -6
  58. package/runtime/claude/bin/lib/git-sync.sh +12 -4
  59. package/runtime/claude/bin/lib/greeting.sh +255 -28
  60. package/runtime/claude/bin/lib/handoff_meta.py +294 -0
  61. package/runtime/claude/bin/lib/health-footer.sh +83 -2
  62. package/runtime/claude/bin/lib/identity.sh +31 -10
  63. package/runtime/claude/bin/lib/instance-registry.sh +71 -0
  64. package/runtime/claude/bin/lib/local_ci_receipt.py +249 -0
  65. package/runtime/claude/bin/lib/metrics.sh +121 -8
  66. package/runtime/claude/bin/lib/notices.sh +36 -0
  67. package/runtime/claude/bin/lib/record_outcomes.py +729 -0
  68. package/runtime/claude/bin/lib/reflexes.py +267 -0
  69. package/runtime/claude/bin/lib/retrieval_scorecard.py +624 -0
  70. package/runtime/claude/bin/lib/session-record-prompt.txt +18 -0
  71. package/runtime/claude/bin/lib/session_record.py +3747 -0
  72. package/runtime/claude/bin/lib/session_trail.py +1983 -0
  73. package/runtime/claude/bin/lib/settings-drift.sh +8 -5
  74. package/runtime/claude/bin/lib/startup-restart.sh +41 -0
  75. package/runtime/claude/bin/lib/worktree-links.sh +92 -0
  76. package/runtime/claude/bin/memory-push.sh +6 -0
  77. package/runtime/claude/bin/merge-gate.sh +498 -0
  78. package/runtime/claude/bin/notify.sh +5 -2
  79. package/runtime/claude/bin/page-access.sh +103 -0
  80. package/runtime/claude/bin/person.sh +13 -2
  81. package/runtime/claude/bin/pi-render-spec.mjs +0 -1
  82. package/runtime/claude/bin/pi-spirit-discovery.mjs +34 -0
  83. package/runtime/claude/bin/post-update.sh +181 -0
  84. package/runtime/claude/bin/prime-render-spec.mjs +0 -1
  85. package/runtime/claude/bin/publish-artifact.sh +89 -42
  86. package/runtime/claude/bin/qa-inventory.sh +2 -2
  87. package/runtime/claude/bin/quest.sh +1 -1
  88. package/runtime/claude/bin/records.sh +11 -0
  89. package/runtime/claude/bin/reflexes/05-vitals.sh +9 -0
  90. package/runtime/claude/bin/reflexes/10-open-items.sh +12 -0
  91. package/runtime/claude/bin/reflexes/15-retrieval.sh +8 -0
  92. package/runtime/claude/bin/reflexes/20-nightly.sh +100 -0
  93. package/runtime/claude/bin/reflexes/20-sandbox.sh +85 -0
  94. package/runtime/claude/bin/reflexes/30-cost.py +116 -0
  95. package/runtime/claude/bin/reflexes/40-shells.py +51 -0
  96. package/runtime/claude/bin/reflexes/README.md +61 -0
  97. package/runtime/claude/bin/reflexes.sh +7 -0
  98. package/runtime/claude/bin/runtime-identity.sh +140 -42
  99. package/runtime/claude/bin/screenshot.sh +77 -0
  100. package/runtime/claude/bin/scroll.sh +1 -1
  101. package/runtime/claude/bin/search.sh +31 -5
  102. package/runtime/claude/bin/session-autosave.sh +6 -3
  103. package/runtime/claude/bin/session-id.sh +147 -0
  104. package/runtime/claude/bin/session-log.sh +19 -0
  105. package/runtime/claude/bin/session-record.sh +26 -0
  106. package/runtime/claude/bin/session-start.sh +121 -151
  107. package/runtime/claude/bin/settings.sh +13 -8
  108. package/runtime/claude/bin/spirit-discovery-claude.sh +4 -0
  109. package/runtime/claude/bin/spirit-discovery-codex.sh +4 -0
  110. package/runtime/claude/bin/spirit-discovery-pi.sh +4 -0
  111. package/runtime/claude/bin/spirit-discovery-prime.sh +4 -0
  112. package/runtime/claude/bin/spirit-discovery.sh +4 -0
  113. package/runtime/claude/bin/startup-check.sh +1 -1
  114. package/runtime/claude/bin/telemetry.sh +11 -4
  115. package/runtime/claude/bin/test-baseline.sh +33 -5
  116. package/runtime/claude/bin/todo.sh +1 -1
  117. package/runtime/claude/bin/worktree-create.sh +15 -45
  118. package/runtime/claude/bin/worktree.sh +6 -0
  119. package/runtime/claude/docs/spirit-discovery.md +96 -0
  120. package/runtime/claude/egregore_runtime/__init__.py +18 -12
  121. package/runtime/claude/egregore_runtime/access.py +608 -0
  122. package/runtime/claude/egregore_runtime/access_cli.py +190 -0
  123. package/runtime/claude/egregore_runtime/adapters/invitation.py +5 -3
  124. package/runtime/claude/egregore_runtime/adapters/qmd.py +521 -47
  125. package/runtime/claude/egregore_runtime/admin_entry.py +56 -0
  126. package/runtime/claude/egregore_runtime/artifacts.py +222 -16
  127. package/runtime/claude/egregore_runtime/contracts.py +27 -0
  128. package/runtime/claude/egregore_runtime/eligibility.py +5 -12
  129. package/runtime/claude/egregore_runtime/episode_binding.py +16 -5
  130. package/runtime/claude/egregore_runtime/github_issues.py +2 -2
  131. package/runtime/claude/egregore_runtime/handoff_lookup.py +9 -20
  132. package/runtime/claude/egregore_runtime/harness_cli.py +674 -149
  133. package/runtime/claude/egregore_runtime/held.py +2436 -0
  134. package/runtime/claude/egregore_runtime/hosted_api.py +137 -0
  135. package/runtime/claude/egregore_runtime/identity.py +196 -65
  136. package/runtime/claude/egregore_runtime/identity_transfer.py +6 -2
  137. package/runtime/claude/egregore_runtime/ingest_cli.py +2 -2
  138. package/runtime/claude/egregore_runtime/ingest_index_cli.py +4 -16
  139. package/runtime/claude/egregore_runtime/installation.py +119 -0
  140. package/runtime/claude/egregore_runtime/investigation.py +64 -41
  141. package/runtime/claude/egregore_runtime/investigation_guidance.py +16 -11
  142. package/runtime/claude/egregore_runtime/issue_cli.py +51 -32
  143. package/runtime/claude/egregore_runtime/issue_reporting.py +4 -3
  144. package/runtime/claude/egregore_runtime/issue_triage_service.py +2 -2
  145. package/runtime/claude/egregore_runtime/issues.py +12 -6
  146. package/runtime/claude/egregore_runtime/knowledge.py +331 -5
  147. package/runtime/claude/egregore_runtime/knowledge_cli.py +57 -17
  148. package/runtime/claude/egregore_runtime/lifecycle.py +88 -22
  149. package/runtime/claude/egregore_runtime/lifecycle_cli.py +21 -13
  150. package/runtime/claude/egregore_runtime/line_ids.py +10 -0
  151. package/runtime/claude/egregore_runtime/memory_push.py +349 -0
  152. package/runtime/claude/egregore_runtime/observe.py +64 -25
  153. package/runtime/claude/egregore_runtime/observers.py +19 -0
  154. package/runtime/claude/egregore_runtime/optional_modules.py +13 -0
  155. package/runtime/claude/egregore_runtime/outputs_cli.py +321 -0
  156. package/runtime/claude/egregore_runtime/policy.py +651 -24
  157. package/runtime/claude/egregore_runtime/private_result.py +2 -1
  158. package/runtime/claude/egregore_runtime/projection.py +946 -0
  159. package/runtime/claude/egregore_runtime/quest_cli.py +20 -23
  160. package/runtime/claude/egregore_runtime/question_cli.py +15 -11
  161. package/runtime/claude/egregore_runtime/questions.py +10 -10
  162. package/runtime/claude/egregore_runtime/quests.py +10 -10
  163. package/runtime/claude/egregore_runtime/record_publish.py +548 -0
  164. package/runtime/claude/egregore_runtime/record_subject.py +564 -0
  165. package/runtime/claude/egregore_runtime/research_ingest.py +2 -4
  166. package/runtime/claude/egregore_runtime/runtime.py +169 -60
  167. package/runtime/claude/egregore_runtime/sandbox.py +194 -0
  168. package/runtime/claude/egregore_runtime/scroll_cli.py +22 -32
  169. package/runtime/claude/egregore_runtime/scrolls.py +23 -15
  170. package/runtime/claude/egregore_runtime/shadow.py +262 -0
  171. package/runtime/claude/egregore_runtime/spirit_discovery.py +487 -0
  172. package/runtime/claude/egregore_runtime/spirit_discovery_cli.py +25 -0
  173. package/runtime/claude/egregore_runtime/spirit_discovery_transport.py +18 -0
  174. package/runtime/claude/egregore_runtime/status.py +101 -44
  175. package/runtime/claude/egregore_runtime/status_cli.py +13 -5
  176. package/runtime/claude/egregore_runtime/sync.py +39 -21
  177. package/runtime/claude/egregore_runtime/taste.py +934 -0
  178. package/runtime/claude/egregore_runtime/taste_cli.py +242 -0
  179. package/runtime/claude/egregore_runtime/taste_rules.py +91 -0
  180. package/runtime/claude/egregore_runtime/telemetry.py +26 -3
  181. package/runtime/claude/egregore_runtime/todo_cli.py +15 -12
  182. package/runtime/claude/egregore_runtime/todos.py +3 -2
  183. package/runtime/claude/egregore_runtime/upgrade.py +85 -58
  184. package/runtime/claude/egregore_runtime/writeback.py +330 -24
  185. package/runtime/claude/manifest.json +65 -5
  186. package/runtime/codex/.claude/context/handoff-claiming.md +32 -0
  187. package/runtime/codex/.claude/context/notification-consent.md +12 -9
  188. package/runtime/codex/.claude/skills/view/SKILL.md +438 -0
  189. package/runtime/codex/.codex/hooks.json +11 -0
  190. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +1 -1
  191. package/runtime/codex/.codex/skills/records/SKILL.md +29 -0
  192. package/runtime/codex/.codex/skills/summon/SKILL.md +1 -1
  193. package/runtime/codex/.codex/skills/view/assets/compose-scaffold.html +19 -13
  194. package/runtime/codex/.codex/spec-manifest.json +13 -19
  195. package/runtime/codex/AGENTS.md +54 -105
  196. package/runtime/codex/bin/agent.sh +231 -7
  197. package/runtime/codex/bin/api-call.sh +44 -23
  198. package/runtime/codex/bin/artifact-register.sh +7 -1
  199. package/runtime/codex/bin/artifact-writeback.sh +1 -1
  200. package/runtime/codex/bin/artifacts.sh +66 -265
  201. package/runtime/codex/bin/attendant.sh +4 -1
  202. package/runtime/codex/bin/boundary-guard.cjs +1848 -0
  203. package/runtime/codex/bin/boundary-hook.sh +41 -0
  204. package/runtime/codex/bin/capture-run.sh +32 -2
  205. package/runtime/codex/bin/codex-render-spec.mjs +24 -46
  206. package/runtime/codex/bin/codex-session-start.sh +19 -0
  207. package/runtime/codex/bin/codex-skill-render.mjs +18 -0
  208. package/runtime/codex/bin/config-get.sh +10 -1
  209. package/runtime/codex/bin/connect-refresh.sh +16 -0
  210. package/runtime/codex/bin/corpus_map.py +5 -0
  211. package/runtime/codex/bin/data/capabilities/codex.json +1458 -0
  212. package/runtime/codex/bin/genui.sh +19 -0
  213. package/runtime/codex/bin/graph-batch.sh +1 -1
  214. package/runtime/codex/bin/graph-op.sh +14 -0
  215. package/runtime/codex/bin/graph.sh +1 -1
  216. package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
  217. package/runtime/codex/bin/handoff-run.sh +11 -6
  218. package/runtime/codex/bin/ingest-item.sh +1 -1
  219. package/runtime/codex/bin/ingest-surface.html +22 -4
  220. package/runtime/codex/bin/issue.sh +1 -1
  221. package/runtime/codex/bin/knowledge.sh +1 -1
  222. package/runtime/codex/bin/lib/card-delivery.sh +329 -0
  223. package/runtime/codex/bin/lib/config.sh +148 -3
  224. package/runtime/codex/bin/lib/context.sh +38 -67
  225. package/runtime/codex/bin/lib/genui-entry.py +36 -0
  226. package/runtime/codex/bin/lib/git-message.sh +25 -6
  227. package/runtime/codex/bin/lib/git-sync.sh +12 -4
  228. package/runtime/codex/bin/lib/greeting.sh +255 -28
  229. package/runtime/codex/bin/lib/handoff_meta.py +294 -0
  230. package/runtime/codex/bin/lib/health-footer.sh +83 -2
  231. package/runtime/codex/bin/lib/identity.sh +31 -10
  232. package/runtime/codex/bin/lib/instance-registry.sh +71 -0
  233. package/runtime/codex/bin/lib/local_ci_receipt.py +249 -0
  234. package/runtime/codex/bin/lib/metrics.sh +121 -8
  235. package/runtime/codex/bin/lib/notices.sh +36 -0
  236. package/runtime/codex/bin/lib/record_outcomes.py +729 -0
  237. package/runtime/codex/bin/lib/reflexes.py +267 -0
  238. package/runtime/codex/bin/lib/retrieval_scorecard.py +624 -0
  239. package/runtime/codex/bin/lib/session-record-prompt.txt +18 -0
  240. package/runtime/codex/bin/lib/session_record.py +3747 -0
  241. package/runtime/codex/bin/lib/session_trail.py +1983 -0
  242. package/runtime/codex/bin/lib/settings-drift.sh +8 -5
  243. package/runtime/codex/bin/lib/startup-restart.sh +41 -0
  244. package/runtime/codex/bin/lib/worktree-links.sh +92 -0
  245. package/runtime/codex/bin/memory-push.sh +6 -0
  246. package/runtime/codex/bin/merge-gate.sh +498 -0
  247. package/runtime/codex/bin/notify.sh +5 -2
  248. package/runtime/codex/bin/page-access.sh +103 -0
  249. package/runtime/codex/bin/person.sh +13 -2
  250. package/runtime/codex/bin/pi-render-spec.mjs +0 -1
  251. package/runtime/codex/bin/pi-spirit-discovery.mjs +34 -0
  252. package/runtime/codex/bin/post-update.sh +181 -0
  253. package/runtime/codex/bin/prime-render-spec.mjs +0 -1
  254. package/runtime/codex/bin/publish-artifact.sh +89 -42
  255. package/runtime/codex/bin/qa-inventory.sh +2 -2
  256. package/runtime/codex/bin/quest.sh +1 -1
  257. package/runtime/codex/bin/records.sh +11 -0
  258. package/runtime/codex/bin/reflexes/05-vitals.sh +9 -0
  259. package/runtime/codex/bin/reflexes/10-open-items.sh +12 -0
  260. package/runtime/codex/bin/reflexes/15-retrieval.sh +8 -0
  261. package/runtime/codex/bin/reflexes/20-nightly.sh +100 -0
  262. package/runtime/codex/bin/reflexes/20-sandbox.sh +85 -0
  263. package/runtime/codex/bin/reflexes/30-cost.py +116 -0
  264. package/runtime/codex/bin/reflexes/40-shells.py +51 -0
  265. package/runtime/codex/bin/reflexes/README.md +61 -0
  266. package/runtime/codex/bin/reflexes.sh +7 -0
  267. package/runtime/codex/bin/runtime-identity.sh +140 -42
  268. package/runtime/codex/bin/screenshot.sh +77 -0
  269. package/runtime/codex/bin/scroll.sh +1 -1
  270. package/runtime/codex/bin/search.sh +31 -5
  271. package/runtime/codex/bin/session-autosave.sh +6 -3
  272. package/runtime/codex/bin/session-id.sh +147 -0
  273. package/runtime/codex/bin/session-log.sh +19 -0
  274. package/runtime/codex/bin/session-record.sh +26 -0
  275. package/runtime/codex/bin/settings.sh +13 -8
  276. package/runtime/codex/bin/spirit-discovery-claude.sh +4 -0
  277. package/runtime/codex/bin/spirit-discovery-codex.sh +4 -0
  278. package/runtime/codex/bin/spirit-discovery-pi.sh +4 -0
  279. package/runtime/codex/bin/spirit-discovery-prime.sh +4 -0
  280. package/runtime/codex/bin/spirit-discovery.sh +4 -0
  281. package/runtime/codex/bin/startup-check.sh +1 -1
  282. package/runtime/codex/bin/telemetry.sh +11 -4
  283. package/runtime/codex/bin/test-baseline.sh +33 -5
  284. package/runtime/codex/bin/todo.sh +1 -1
  285. package/runtime/codex/bin/worktree-create.sh +15 -45
  286. package/runtime/codex/bin/worktree.sh +6 -0
  287. package/runtime/codex/docs/spirit-discovery.md +96 -0
  288. package/runtime/codex/egregore_runtime/__init__.py +18 -12
  289. package/runtime/codex/egregore_runtime/access.py +608 -0
  290. package/runtime/codex/egregore_runtime/access_cli.py +190 -0
  291. package/runtime/codex/egregore_runtime/adapters/invitation.py +5 -3
  292. package/runtime/codex/egregore_runtime/adapters/qmd.py +521 -47
  293. package/runtime/codex/egregore_runtime/admin_entry.py +56 -0
  294. package/runtime/codex/egregore_runtime/artifacts.py +222 -16
  295. package/runtime/codex/egregore_runtime/contracts.py +27 -0
  296. package/runtime/codex/egregore_runtime/eligibility.py +5 -12
  297. package/runtime/codex/egregore_runtime/episode_binding.py +16 -5
  298. package/runtime/codex/egregore_runtime/github_issues.py +2 -2
  299. package/runtime/codex/egregore_runtime/handoff_lookup.py +9 -20
  300. package/runtime/codex/egregore_runtime/harness_cli.py +674 -149
  301. package/runtime/codex/egregore_runtime/held.py +2436 -0
  302. package/runtime/codex/egregore_runtime/hosted_api.py +137 -0
  303. package/runtime/codex/egregore_runtime/identity.py +196 -65
  304. package/runtime/codex/egregore_runtime/identity_transfer.py +6 -2
  305. package/runtime/codex/egregore_runtime/ingest_cli.py +2 -2
  306. package/runtime/codex/egregore_runtime/ingest_index_cli.py +4 -16
  307. package/runtime/codex/egregore_runtime/installation.py +119 -0
  308. package/runtime/codex/egregore_runtime/investigation.py +64 -41
  309. package/runtime/codex/egregore_runtime/investigation_guidance.py +16 -11
  310. package/runtime/codex/egregore_runtime/issue_cli.py +51 -32
  311. package/runtime/codex/egregore_runtime/issue_reporting.py +4 -3
  312. package/runtime/codex/egregore_runtime/issue_triage_service.py +2 -2
  313. package/runtime/codex/egregore_runtime/issues.py +12 -6
  314. package/runtime/codex/egregore_runtime/knowledge.py +331 -5
  315. package/runtime/codex/egregore_runtime/knowledge_cli.py +57 -17
  316. package/runtime/codex/egregore_runtime/lifecycle.py +88 -22
  317. package/runtime/codex/egregore_runtime/lifecycle_cli.py +21 -13
  318. package/runtime/codex/egregore_runtime/line_ids.py +10 -0
  319. package/runtime/codex/egregore_runtime/memory_push.py +349 -0
  320. package/runtime/codex/egregore_runtime/observe.py +64 -25
  321. package/runtime/codex/egregore_runtime/observers.py +19 -0
  322. package/runtime/codex/egregore_runtime/optional_modules.py +13 -0
  323. package/runtime/codex/egregore_runtime/outputs_cli.py +321 -0
  324. package/runtime/codex/egregore_runtime/policy.py +651 -24
  325. package/runtime/codex/egregore_runtime/private_result.py +2 -1
  326. package/runtime/codex/egregore_runtime/projection.py +946 -0
  327. package/runtime/codex/egregore_runtime/quest_cli.py +20 -23
  328. package/runtime/codex/egregore_runtime/question_cli.py +15 -11
  329. package/runtime/codex/egregore_runtime/questions.py +10 -10
  330. package/runtime/codex/egregore_runtime/quests.py +10 -10
  331. package/runtime/codex/egregore_runtime/record_publish.py +548 -0
  332. package/runtime/codex/egregore_runtime/record_subject.py +564 -0
  333. package/runtime/codex/egregore_runtime/research_ingest.py +2 -4
  334. package/runtime/codex/egregore_runtime/runtime.py +169 -60
  335. package/runtime/codex/egregore_runtime/sandbox.py +194 -0
  336. package/runtime/codex/egregore_runtime/scroll_cli.py +22 -32
  337. package/runtime/codex/egregore_runtime/scrolls.py +23 -15
  338. package/runtime/codex/egregore_runtime/shadow.py +262 -0
  339. package/runtime/codex/egregore_runtime/spirit_discovery.py +487 -0
  340. package/runtime/codex/egregore_runtime/spirit_discovery_cli.py +25 -0
  341. package/runtime/codex/egregore_runtime/spirit_discovery_transport.py +18 -0
  342. package/runtime/codex/egregore_runtime/status.py +101 -44
  343. package/runtime/codex/egregore_runtime/status_cli.py +13 -5
  344. package/runtime/codex/egregore_runtime/sync.py +39 -21
  345. package/runtime/codex/egregore_runtime/taste.py +934 -0
  346. package/runtime/codex/egregore_runtime/taste_cli.py +242 -0
  347. package/runtime/codex/egregore_runtime/taste_rules.py +91 -0
  348. package/runtime/codex/egregore_runtime/telemetry.py +26 -3
  349. package/runtime/codex/egregore_runtime/todo_cli.py +15 -12
  350. package/runtime/codex/egregore_runtime/todos.py +3 -2
  351. package/runtime/codex/egregore_runtime/upgrade.py +85 -58
  352. package/runtime/codex/egregore_runtime/writeback.py +330 -24
  353. package/runtime/codex/manifest.json +66 -5
  354. package/runtime/pi/.claude/context/handoff-claiming.md +32 -0
  355. package/runtime/pi/.claude/context/notification-consent.md +12 -9
  356. package/runtime/pi/.claude/skills/view/SKILL.md +438 -0
  357. package/runtime/pi/.codex/hooks.json +11 -0
  358. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +1 -1
  359. package/runtime/pi/.codex/skills/records/SKILL.md +29 -0
  360. package/runtime/pi/.codex/skills/summon/SKILL.md +1 -1
  361. package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +19 -13
  362. package/runtime/pi/.codex/spec-manifest.json +13 -19
  363. package/runtime/pi/.pi/APPEND_SYSTEM.md +53 -99
  364. package/runtime/pi/.pi/extensions/egregore.ts +13 -1
  365. package/runtime/pi/.pi/spec-manifest.json +2 -2
  366. package/runtime/pi/AGENTS.md +54 -105
  367. package/runtime/pi/bin/agent.sh +231 -7
  368. package/runtime/pi/bin/api-call.sh +44 -23
  369. package/runtime/pi/bin/artifact-register.sh +7 -1
  370. package/runtime/pi/bin/artifact-writeback.sh +1 -1
  371. package/runtime/pi/bin/artifacts.sh +66 -265
  372. package/runtime/pi/bin/attendant.sh +4 -1
  373. package/runtime/pi/bin/boundary-guard.cjs +1848 -0
  374. package/runtime/pi/bin/boundary-hook.sh +41 -0
  375. package/runtime/pi/bin/capture-run.sh +32 -2
  376. package/runtime/pi/bin/codex-render-spec.mjs +24 -46
  377. package/runtime/pi/bin/codex-session-start.sh +19 -0
  378. package/runtime/pi/bin/codex-skill-render.mjs +18 -0
  379. package/runtime/pi/bin/config-get.sh +10 -1
  380. package/runtime/pi/bin/connect-refresh.sh +16 -0
  381. package/runtime/pi/bin/corpus_map.py +5 -0
  382. package/runtime/pi/bin/data/capabilities/pi.json +1458 -0
  383. package/runtime/pi/bin/genui.sh +19 -0
  384. package/runtime/pi/bin/graph-batch.sh +1 -1
  385. package/runtime/pi/bin/graph-op.sh +14 -0
  386. package/runtime/pi/bin/graph.sh +1 -1
  387. package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
  388. package/runtime/pi/bin/handoff-run.sh +11 -6
  389. package/runtime/pi/bin/ingest-item.sh +1 -1
  390. package/runtime/pi/bin/ingest-surface.html +22 -4
  391. package/runtime/pi/bin/issue.sh +1 -1
  392. package/runtime/pi/bin/knowledge.sh +1 -1
  393. package/runtime/pi/bin/lib/card-delivery.sh +329 -0
  394. package/runtime/pi/bin/lib/config.sh +148 -3
  395. package/runtime/pi/bin/lib/context.sh +38 -67
  396. package/runtime/pi/bin/lib/genui-entry.py +36 -0
  397. package/runtime/pi/bin/lib/git-message.sh +25 -6
  398. package/runtime/pi/bin/lib/git-sync.sh +12 -4
  399. package/runtime/pi/bin/lib/greeting.sh +255 -28
  400. package/runtime/pi/bin/lib/handoff_meta.py +294 -0
  401. package/runtime/pi/bin/lib/health-footer.sh +83 -2
  402. package/runtime/pi/bin/lib/identity.sh +31 -10
  403. package/runtime/pi/bin/lib/instance-registry.sh +71 -0
  404. package/runtime/pi/bin/lib/local_ci_receipt.py +249 -0
  405. package/runtime/pi/bin/lib/metrics.sh +121 -8
  406. package/runtime/pi/bin/lib/notices.sh +36 -0
  407. package/runtime/pi/bin/lib/record_outcomes.py +729 -0
  408. package/runtime/pi/bin/lib/reflexes.py +267 -0
  409. package/runtime/pi/bin/lib/retrieval_scorecard.py +624 -0
  410. package/runtime/pi/bin/lib/session-record-prompt.txt +18 -0
  411. package/runtime/pi/bin/lib/session_record.py +3747 -0
  412. package/runtime/pi/bin/lib/session_trail.py +1983 -0
  413. package/runtime/pi/bin/lib/settings-drift.sh +8 -5
  414. package/runtime/pi/bin/lib/startup-restart.sh +41 -0
  415. package/runtime/pi/bin/lib/worktree-links.sh +92 -0
  416. package/runtime/pi/bin/memory-push.sh +6 -0
  417. package/runtime/pi/bin/merge-gate.sh +498 -0
  418. package/runtime/pi/bin/notify.sh +5 -2
  419. package/runtime/pi/bin/page-access.sh +103 -0
  420. package/runtime/pi/bin/person.sh +13 -2
  421. package/runtime/pi/bin/pi-branch-policy.mjs +37 -0
  422. package/runtime/pi/bin/pi-observe-context.mjs +97 -4
  423. package/runtime/pi/bin/pi-render-spec.mjs +0 -1
  424. package/runtime/pi/bin/pi-session-start.sh +4 -1
  425. package/runtime/pi/bin/pi-spirit-discovery.mjs +34 -0
  426. package/runtime/pi/bin/post-update.sh +181 -0
  427. package/runtime/pi/bin/prime-render-spec.mjs +0 -1
  428. package/runtime/pi/bin/publish-artifact.sh +89 -42
  429. package/runtime/pi/bin/qa-inventory.sh +2 -2
  430. package/runtime/pi/bin/quest.sh +1 -1
  431. package/runtime/pi/bin/records.sh +11 -0
  432. package/runtime/pi/bin/reflexes/05-vitals.sh +9 -0
  433. package/runtime/pi/bin/reflexes/10-open-items.sh +12 -0
  434. package/runtime/pi/bin/reflexes/15-retrieval.sh +8 -0
  435. package/runtime/pi/bin/reflexes/20-nightly.sh +100 -0
  436. package/runtime/pi/bin/reflexes/20-sandbox.sh +85 -0
  437. package/runtime/pi/bin/reflexes/30-cost.py +116 -0
  438. package/runtime/pi/bin/reflexes/40-shells.py +51 -0
  439. package/runtime/pi/bin/reflexes/README.md +61 -0
  440. package/runtime/pi/bin/reflexes.sh +7 -0
  441. package/runtime/pi/bin/runtime-identity.sh +140 -42
  442. package/runtime/pi/bin/screenshot.sh +77 -0
  443. package/runtime/pi/bin/scroll.sh +1 -1
  444. package/runtime/pi/bin/search.sh +31 -5
  445. package/runtime/pi/bin/session-autosave.sh +6 -3
  446. package/runtime/pi/bin/session-id.sh +147 -0
  447. package/runtime/pi/bin/session-log.sh +19 -0
  448. package/runtime/pi/bin/session-record.sh +26 -0
  449. package/runtime/pi/bin/settings.sh +13 -8
  450. package/runtime/pi/bin/spirit-discovery-claude.sh +4 -0
  451. package/runtime/pi/bin/spirit-discovery-codex.sh +4 -0
  452. package/runtime/pi/bin/spirit-discovery-pi.sh +4 -0
  453. package/runtime/pi/bin/spirit-discovery-prime.sh +4 -0
  454. package/runtime/pi/bin/spirit-discovery.sh +4 -0
  455. package/runtime/pi/bin/startup-check.sh +1 -1
  456. package/runtime/pi/bin/telemetry.sh +11 -4
  457. package/runtime/pi/bin/test-baseline.sh +33 -5
  458. package/runtime/pi/bin/todo.sh +1 -1
  459. package/runtime/pi/bin/worktree-create.sh +15 -45
  460. package/runtime/pi/bin/worktree.sh +6 -0
  461. package/runtime/pi/docs/spirit-discovery.md +96 -0
  462. package/runtime/pi/egregore_runtime/__init__.py +18 -12
  463. package/runtime/pi/egregore_runtime/access.py +608 -0
  464. package/runtime/pi/egregore_runtime/access_cli.py +190 -0
  465. package/runtime/pi/egregore_runtime/adapters/invitation.py +5 -3
  466. package/runtime/pi/egregore_runtime/adapters/qmd.py +521 -47
  467. package/runtime/pi/egregore_runtime/admin_entry.py +56 -0
  468. package/runtime/pi/egregore_runtime/artifacts.py +222 -16
  469. package/runtime/pi/egregore_runtime/contracts.py +27 -0
  470. package/runtime/pi/egregore_runtime/eligibility.py +5 -12
  471. package/runtime/pi/egregore_runtime/episode_binding.py +16 -5
  472. package/runtime/pi/egregore_runtime/github_issues.py +2 -2
  473. package/runtime/pi/egregore_runtime/handoff_lookup.py +9 -20
  474. package/runtime/pi/egregore_runtime/harness_cli.py +674 -149
  475. package/runtime/pi/egregore_runtime/held.py +2436 -0
  476. package/runtime/pi/egregore_runtime/hosted_api.py +137 -0
  477. package/runtime/pi/egregore_runtime/identity.py +196 -65
  478. package/runtime/pi/egregore_runtime/identity_transfer.py +6 -2
  479. package/runtime/pi/egregore_runtime/ingest_cli.py +2 -2
  480. package/runtime/pi/egregore_runtime/ingest_index_cli.py +4 -16
  481. package/runtime/pi/egregore_runtime/installation.py +119 -0
  482. package/runtime/pi/egregore_runtime/investigation.py +64 -41
  483. package/runtime/pi/egregore_runtime/investigation_guidance.py +16 -11
  484. package/runtime/pi/egregore_runtime/issue_cli.py +51 -32
  485. package/runtime/pi/egregore_runtime/issue_reporting.py +4 -3
  486. package/runtime/pi/egregore_runtime/issue_triage_service.py +2 -2
  487. package/runtime/pi/egregore_runtime/issues.py +12 -6
  488. package/runtime/pi/egregore_runtime/knowledge.py +331 -5
  489. package/runtime/pi/egregore_runtime/knowledge_cli.py +57 -17
  490. package/runtime/pi/egregore_runtime/lifecycle.py +88 -22
  491. package/runtime/pi/egregore_runtime/lifecycle_cli.py +21 -13
  492. package/runtime/pi/egregore_runtime/line_ids.py +10 -0
  493. package/runtime/pi/egregore_runtime/memory_push.py +349 -0
  494. package/runtime/pi/egregore_runtime/observe.py +64 -25
  495. package/runtime/pi/egregore_runtime/observers.py +19 -0
  496. package/runtime/pi/egregore_runtime/optional_modules.py +13 -0
  497. package/runtime/pi/egregore_runtime/outputs_cli.py +321 -0
  498. package/runtime/pi/egregore_runtime/policy.py +651 -24
  499. package/runtime/pi/egregore_runtime/private_result.py +2 -1
  500. package/runtime/pi/egregore_runtime/projection.py +946 -0
  501. package/runtime/pi/egregore_runtime/quest_cli.py +20 -23
  502. package/runtime/pi/egregore_runtime/question_cli.py +15 -11
  503. package/runtime/pi/egregore_runtime/questions.py +10 -10
  504. package/runtime/pi/egregore_runtime/quests.py +10 -10
  505. package/runtime/pi/egregore_runtime/record_publish.py +548 -0
  506. package/runtime/pi/egregore_runtime/record_subject.py +564 -0
  507. package/runtime/pi/egregore_runtime/research_ingest.py +2 -4
  508. package/runtime/pi/egregore_runtime/runtime.py +169 -60
  509. package/runtime/pi/egregore_runtime/sandbox.py +194 -0
  510. package/runtime/pi/egregore_runtime/scroll_cli.py +22 -32
  511. package/runtime/pi/egregore_runtime/scrolls.py +23 -15
  512. package/runtime/pi/egregore_runtime/shadow.py +262 -0
  513. package/runtime/pi/egregore_runtime/spirit_discovery.py +487 -0
  514. package/runtime/pi/egregore_runtime/spirit_discovery_cli.py +25 -0
  515. package/runtime/pi/egregore_runtime/spirit_discovery_transport.py +18 -0
  516. package/runtime/pi/egregore_runtime/status.py +101 -44
  517. package/runtime/pi/egregore_runtime/status_cli.py +13 -5
  518. package/runtime/pi/egregore_runtime/sync.py +39 -21
  519. package/runtime/pi/egregore_runtime/taste.py +934 -0
  520. package/runtime/pi/egregore_runtime/taste_cli.py +242 -0
  521. package/runtime/pi/egregore_runtime/taste_rules.py +91 -0
  522. package/runtime/pi/egregore_runtime/telemetry.py +26 -3
  523. package/runtime/pi/egregore_runtime/todo_cli.py +15 -12
  524. package/runtime/pi/egregore_runtime/todos.py +3 -2
  525. package/runtime/pi/egregore_runtime/upgrade.py +85 -58
  526. package/runtime/pi/egregore_runtime/writeback.py +330 -24
  527. package/runtime/pi/manifest.json +66 -5
  528. package/runtime/prime/.claude/context/handoff-claiming.md +32 -0
  529. package/runtime/prime/.claude/context/notification-consent.md +12 -9
  530. package/runtime/prime/.claude/skills/view/SKILL.md +438 -0
  531. package/runtime/prime/.codex/hooks.json +11 -0
  532. package/runtime/prime/.codex/skills/dark-mode/SKILL.md +1 -1
  533. package/runtime/prime/.codex/skills/records/SKILL.md +29 -0
  534. package/runtime/prime/.codex/skills/summon/SKILL.md +1 -1
  535. package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +19 -13
  536. package/runtime/prime/.codex/spec-manifest.json +13 -19
  537. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +53 -99
  538. package/runtime/prime/.prime/agent/extensions/egregore.ts +14 -1
  539. package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +14 -6
  540. package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +66 -1
  541. package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
  542. package/runtime/prime/AGENTS.md +54 -105
  543. package/runtime/prime/bin/agent.sh +231 -7
  544. package/runtime/prime/bin/api-call.sh +44 -23
  545. package/runtime/prime/bin/artifact-register.sh +7 -1
  546. package/runtime/prime/bin/artifact-writeback.sh +1 -1
  547. package/runtime/prime/bin/artifacts.sh +66 -265
  548. package/runtime/prime/bin/attendant.sh +4 -1
  549. package/runtime/prime/bin/boundary-guard.cjs +1848 -0
  550. package/runtime/prime/bin/boundary-hook.sh +41 -0
  551. package/runtime/prime/bin/capture-run.sh +32 -2
  552. package/runtime/prime/bin/codex-render-spec.mjs +24 -46
  553. package/runtime/prime/bin/codex-session-start.sh +19 -0
  554. package/runtime/prime/bin/codex-skill-render.mjs +18 -0
  555. package/runtime/prime/bin/config-get.sh +10 -1
  556. package/runtime/prime/bin/connect-refresh.sh +16 -0
  557. package/runtime/prime/bin/corpus_map.py +5 -0
  558. package/runtime/prime/bin/data/capabilities/prime.json +1458 -0
  559. package/runtime/prime/bin/genui.sh +19 -0
  560. package/runtime/prime/bin/graph-batch.sh +1 -1
  561. package/runtime/prime/bin/graph-op.sh +14 -0
  562. package/runtime/prime/bin/graph.sh +1 -1
  563. package/runtime/prime/bin/handoff-pr-backfill.sh +1 -1
  564. package/runtime/prime/bin/handoff-run.sh +11 -6
  565. package/runtime/prime/bin/ingest-item.sh +1 -1
  566. package/runtime/prime/bin/ingest-surface.html +22 -4
  567. package/runtime/prime/bin/issue.sh +1 -1
  568. package/runtime/prime/bin/knowledge.sh +1 -1
  569. package/runtime/prime/bin/lib/card-delivery.sh +329 -0
  570. package/runtime/prime/bin/lib/config.sh +148 -3
  571. package/runtime/prime/bin/lib/context.sh +38 -67
  572. package/runtime/prime/bin/lib/genui-entry.py +36 -0
  573. package/runtime/prime/bin/lib/git-message.sh +25 -6
  574. package/runtime/prime/bin/lib/git-sync.sh +12 -4
  575. package/runtime/prime/bin/lib/greeting.sh +255 -28
  576. package/runtime/prime/bin/lib/handoff_meta.py +294 -0
  577. package/runtime/prime/bin/lib/health-footer.sh +83 -2
  578. package/runtime/prime/bin/lib/identity.sh +31 -10
  579. package/runtime/prime/bin/lib/instance-registry.sh +71 -0
  580. package/runtime/prime/bin/lib/local_ci_receipt.py +249 -0
  581. package/runtime/prime/bin/lib/metrics.sh +121 -8
  582. package/runtime/prime/bin/lib/notices.sh +36 -0
  583. package/runtime/prime/bin/lib/record_outcomes.py +729 -0
  584. package/runtime/prime/bin/lib/reflexes.py +267 -0
  585. package/runtime/prime/bin/lib/retrieval_scorecard.py +624 -0
  586. package/runtime/prime/bin/lib/session-record-prompt.txt +18 -0
  587. package/runtime/prime/bin/lib/session_record.py +3747 -0
  588. package/runtime/prime/bin/lib/session_trail.py +1983 -0
  589. package/runtime/prime/bin/lib/settings-drift.sh +8 -5
  590. package/runtime/prime/bin/lib/startup-restart.sh +41 -0
  591. package/runtime/prime/bin/lib/worktree-links.sh +92 -0
  592. package/runtime/prime/bin/memory-push.sh +6 -0
  593. package/runtime/prime/bin/merge-gate.sh +498 -0
  594. package/runtime/prime/bin/notify.sh +5 -2
  595. package/runtime/prime/bin/page-access.sh +103 -0
  596. package/runtime/prime/bin/person.sh +13 -2
  597. package/runtime/prime/bin/pi-branch-policy.mjs +37 -0
  598. package/runtime/prime/bin/pi-observe-context.mjs +97 -4
  599. package/runtime/prime/bin/pi-render-spec.mjs +0 -1
  600. package/runtime/prime/bin/pi-spirit-discovery.mjs +34 -0
  601. package/runtime/prime/bin/post-update.sh +181 -0
  602. package/runtime/prime/bin/prime-render-spec.mjs +0 -1
  603. package/runtime/prime/bin/prime-session-start.sh +3 -1
  604. package/runtime/prime/bin/publish-artifact.sh +89 -42
  605. package/runtime/prime/bin/qa-inventory.sh +2 -2
  606. package/runtime/prime/bin/quest.sh +1 -1
  607. package/runtime/prime/bin/records.sh +11 -0
  608. package/runtime/prime/bin/reflexes/05-vitals.sh +9 -0
  609. package/runtime/prime/bin/reflexes/10-open-items.sh +12 -0
  610. package/runtime/prime/bin/reflexes/15-retrieval.sh +8 -0
  611. package/runtime/prime/bin/reflexes/20-nightly.sh +100 -0
  612. package/runtime/prime/bin/reflexes/20-sandbox.sh +85 -0
  613. package/runtime/prime/bin/reflexes/30-cost.py +116 -0
  614. package/runtime/prime/bin/reflexes/40-shells.py +51 -0
  615. package/runtime/prime/bin/reflexes/README.md +61 -0
  616. package/runtime/prime/bin/reflexes.sh +7 -0
  617. package/runtime/prime/bin/runtime-identity.sh +140 -42
  618. package/runtime/prime/bin/screenshot.sh +77 -0
  619. package/runtime/prime/bin/scroll.sh +1 -1
  620. package/runtime/prime/bin/search.sh +31 -5
  621. package/runtime/prime/bin/session-autosave.sh +6 -3
  622. package/runtime/prime/bin/session-id.sh +147 -0
  623. package/runtime/prime/bin/session-log.sh +19 -0
  624. package/runtime/prime/bin/session-record.sh +26 -0
  625. package/runtime/prime/bin/settings.sh +13 -8
  626. package/runtime/prime/bin/spirit-discovery-claude.sh +4 -0
  627. package/runtime/prime/bin/spirit-discovery-codex.sh +4 -0
  628. package/runtime/prime/bin/spirit-discovery-pi.sh +4 -0
  629. package/runtime/prime/bin/spirit-discovery-prime.sh +4 -0
  630. package/runtime/prime/bin/spirit-discovery.sh +4 -0
  631. package/runtime/prime/bin/startup-check.sh +1 -1
  632. package/runtime/prime/bin/telemetry.sh +11 -4
  633. package/runtime/prime/bin/test-baseline.sh +33 -5
  634. package/runtime/prime/bin/todo.sh +1 -1
  635. package/runtime/prime/bin/worktree-create.sh +15 -45
  636. package/runtime/prime/bin/worktree.sh +6 -0
  637. package/runtime/prime/docs/spirit-discovery.md +96 -0
  638. package/runtime/prime/egregore_runtime/__init__.py +18 -12
  639. package/runtime/prime/egregore_runtime/access.py +608 -0
  640. package/runtime/prime/egregore_runtime/access_cli.py +190 -0
  641. package/runtime/prime/egregore_runtime/adapters/invitation.py +5 -3
  642. package/runtime/prime/egregore_runtime/adapters/qmd.py +521 -47
  643. package/runtime/prime/egregore_runtime/admin_entry.py +56 -0
  644. package/runtime/prime/egregore_runtime/artifacts.py +222 -16
  645. package/runtime/prime/egregore_runtime/contracts.py +27 -0
  646. package/runtime/prime/egregore_runtime/eligibility.py +5 -12
  647. package/runtime/prime/egregore_runtime/episode_binding.py +16 -5
  648. package/runtime/prime/egregore_runtime/github_issues.py +2 -2
  649. package/runtime/prime/egregore_runtime/handoff_lookup.py +9 -20
  650. package/runtime/prime/egregore_runtime/harness_cli.py +674 -149
  651. package/runtime/prime/egregore_runtime/held.py +2436 -0
  652. package/runtime/prime/egregore_runtime/hosted_api.py +137 -0
  653. package/runtime/prime/egregore_runtime/identity.py +196 -65
  654. package/runtime/prime/egregore_runtime/identity_transfer.py +6 -2
  655. package/runtime/prime/egregore_runtime/ingest_cli.py +2 -2
  656. package/runtime/prime/egregore_runtime/ingest_index_cli.py +4 -16
  657. package/runtime/prime/egregore_runtime/installation.py +119 -0
  658. package/runtime/prime/egregore_runtime/investigation.py +64 -41
  659. package/runtime/prime/egregore_runtime/investigation_guidance.py +16 -11
  660. package/runtime/prime/egregore_runtime/issue_cli.py +51 -32
  661. package/runtime/prime/egregore_runtime/issue_reporting.py +4 -3
  662. package/runtime/prime/egregore_runtime/issue_triage_service.py +2 -2
  663. package/runtime/prime/egregore_runtime/issues.py +12 -6
  664. package/runtime/prime/egregore_runtime/knowledge.py +331 -5
  665. package/runtime/prime/egregore_runtime/knowledge_cli.py +57 -17
  666. package/runtime/prime/egregore_runtime/lifecycle.py +88 -22
  667. package/runtime/prime/egregore_runtime/lifecycle_cli.py +21 -13
  668. package/runtime/prime/egregore_runtime/line_ids.py +10 -0
  669. package/runtime/prime/egregore_runtime/memory_push.py +349 -0
  670. package/runtime/prime/egregore_runtime/observe.py +64 -25
  671. package/runtime/prime/egregore_runtime/observers.py +19 -0
  672. package/runtime/prime/egregore_runtime/optional_modules.py +13 -0
  673. package/runtime/prime/egregore_runtime/outputs_cli.py +321 -0
  674. package/runtime/prime/egregore_runtime/policy.py +651 -24
  675. package/runtime/prime/egregore_runtime/private_result.py +2 -1
  676. package/runtime/prime/egregore_runtime/projection.py +946 -0
  677. package/runtime/prime/egregore_runtime/quest_cli.py +20 -23
  678. package/runtime/prime/egregore_runtime/question_cli.py +15 -11
  679. package/runtime/prime/egregore_runtime/questions.py +10 -10
  680. package/runtime/prime/egregore_runtime/quests.py +10 -10
  681. package/runtime/prime/egregore_runtime/record_publish.py +548 -0
  682. package/runtime/prime/egregore_runtime/record_subject.py +564 -0
  683. package/runtime/prime/egregore_runtime/research_ingest.py +2 -4
  684. package/runtime/prime/egregore_runtime/runtime.py +169 -60
  685. package/runtime/prime/egregore_runtime/sandbox.py +194 -0
  686. package/runtime/prime/egregore_runtime/scroll_cli.py +22 -32
  687. package/runtime/prime/egregore_runtime/scrolls.py +23 -15
  688. package/runtime/prime/egregore_runtime/shadow.py +262 -0
  689. package/runtime/prime/egregore_runtime/spirit_discovery.py +487 -0
  690. package/runtime/prime/egregore_runtime/spirit_discovery_cli.py +25 -0
  691. package/runtime/prime/egregore_runtime/spirit_discovery_transport.py +18 -0
  692. package/runtime/prime/egregore_runtime/status.py +101 -44
  693. package/runtime/prime/egregore_runtime/status_cli.py +13 -5
  694. package/runtime/prime/egregore_runtime/sync.py +39 -21
  695. package/runtime/prime/egregore_runtime/taste.py +934 -0
  696. package/runtime/prime/egregore_runtime/taste_cli.py +242 -0
  697. package/runtime/prime/egregore_runtime/taste_rules.py +91 -0
  698. package/runtime/prime/egregore_runtime/telemetry.py +26 -3
  699. package/runtime/prime/egregore_runtime/todo_cli.py +15 -12
  700. package/runtime/prime/egregore_runtime/todos.py +3 -2
  701. package/runtime/prime/egregore_runtime/upgrade.py +85 -58
  702. package/runtime/prime/egregore_runtime/writeback.py +330 -24
  703. package/runtime/prime/manifest.json +66 -5
  704. package/runtime/claude/bin/thread.sh +0 -9
  705. package/runtime/claude/egregore_runtime/admin_gate.py +0 -102
  706. package/runtime/claude/egregore_runtime/thread_cli.py +0 -173
  707. package/runtime/claude/egregore_runtime/threads.py +0 -521
  708. package/runtime/codex/bin/thread.sh +0 -9
  709. package/runtime/codex/egregore_runtime/admin_gate.py +0 -102
  710. package/runtime/codex/egregore_runtime/thread_cli.py +0 -173
  711. package/runtime/codex/egregore_runtime/threads.py +0 -521
  712. package/runtime/pi/bin/thread.sh +0 -9
  713. package/runtime/pi/egregore_runtime/admin_gate.py +0 -102
  714. package/runtime/pi/egregore_runtime/thread_cli.py +0 -173
  715. package/runtime/pi/egregore_runtime/threads.py +0 -521
  716. package/runtime/prime/bin/thread.sh +0 -9
  717. package/runtime/prime/egregore_runtime/admin_gate.py +0 -102
  718. package/runtime/prime/egregore_runtime/thread_cli.py +0 -173
  719. package/runtime/prime/egregore_runtime/threads.py +0 -521
@@ -0,0 +1,498 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ # Repository selection belongs to gh and this checkout, never to inherited Git overrides.
4
+ unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_COMMON_DIR
5
+
6
+ # The merge gate: the one door for merging a pull request into the
7
+ # integration branch, on every harness (`bash bin/agent.sh merge <pr>`).
8
+ # It reads the checks on the PR's head commit and merges only when they
9
+ # allow it. A red check merges only under a receipt: a Markdown file that
10
+ # names every red check and why it does not matter, posted on the PR as a
11
+ # comment headed "Merge receipt" before the merge runs.
12
+ #
13
+ # Each merge, and each refusal of red checks without a receipt, appends one
14
+ # line to the gate ledger, memory/sessions/gate.jsonl, through the records
15
+ # module (bin/lib/session_record.py gate-event). The line holds the checks,
16
+ # the head's CI attempts and the jobs that passed only on a re-run, flaked
17
+ # when an earlier attempt failed them and recovered when it cancelled them;
18
+ # writing it never changes the decision or the exit code.
19
+ #
20
+ # Usage:
21
+ # bash bin/merge-gate.sh <pr-number-or-url> # merge when every check is green
22
+ # bash bin/merge-gate.sh <pr-number-or-url> --receipt FILE # merge past the red checks it names
23
+ # bash bin/merge-gate.sh <pr-number-or-url> --local-receipt FILE # SHA-bound local CI
24
+ # bash bin/merge-gate.sh <pr-number-or-url> --dry-run # print the classification and decision
25
+ #
26
+ # A number names a PR in this instance's repository (github_org/repo_name);
27
+ # a URL may name a managed repo, which merges into its own integration
28
+ # branch (bin/base-branch.sh <name>). Checks: green = success; neutral =
29
+ # skipped, neutral; pending = queued, in progress, waiting; red = failure,
30
+ # cancelled, timed out, action required and any other result. A workflow
31
+ # run on the commit that is still going holds the merge too. A receipt
32
+ # names each red check by its exact name, on a line that says why it does
33
+ # not matter. --dry-run posts and merges nothing and exits with the code
34
+ # the real run would. gh runs through bin/gh-run.sh when it is present.
35
+ #
36
+ # Exit 0 = merged (with --dry-run: would merge)
37
+ # Exit 1 = usage, gh unavailable, or a PR that cannot merge as it stands
38
+ # Exit 2 = red checks without a receipt, or a receipt that misses one
39
+ # Exit 3 = checks or workflow runs still going, or no checks reported yet
40
+ # Exit 4 = the base is not the integration branch of the PR's repository,
41
+ # or the repository is neither this one nor a managed repo
42
+
43
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
44
+
45
+ die() {
46
+ printf 'merge-gate: %s\n' "$*" >&2
47
+ exit 1
48
+ }
49
+
50
+ pr="" receipt="" local_receipt="" local_base_sha="" dry_run=false number=""
51
+ while [ "$#" -gt 0 ]; do
52
+ case "$1" in
53
+ --receipt)
54
+ [ "$#" -ge 2 ] && [ -n "$2" ] || die "--receipt needs a file"
55
+ receipt="$2"; shift 2 ;;
56
+ --receipt=*)
57
+ receipt="${1#--receipt=}"
58
+ [ -n "$receipt" ] || die "--receipt needs a file"
59
+ shift ;;
60
+ --local-receipt)
61
+ [ "$#" -ge 2 ] && [ -n "$2" ] || die "--local-receipt needs a file"
62
+ local_receipt="$2"; shift 2 ;;
63
+ --dry-run) dry_run=true; shift ;;
64
+ -h|--help) awk 'NR >= 6 { if ($0 == "") exit; sub(/^# ?/, ""); print }' "$0"; exit 0 ;;
65
+ -*) die "unknown option: $1 (see --help)" ;;
66
+ *)
67
+ [ -z "$pr" ] || die "one pull request at a time"
68
+ pr="${1#\#}"; shift ;;
69
+ esac
70
+ done
71
+ [ -n "$pr" ] || die "usage: bash bin/merge-gate.sh <pr-number-or-url> [--receipt FILE] [--dry-run]"
72
+ case "$pr" in
73
+ *[!0-9]*)
74
+ [[ "$pr" =~ ^https://[^/[:space:]]+/[^/[:space:]]+/[^/[:space:]]+/pull/[0-9]+(/[^[:space:]]*)?$ ]] \
75
+ || die "not a pull request number or URL: $pr" ;;
76
+ esac
77
+ if [ -n "$local_receipt" ]; then
78
+ [ -z "$receipt" ] || die "choose --receipt or --local-receipt, not both"
79
+ case "$local_receipt" in /*) ;; *) local_receipt="$PWD/$local_receipt" ;; esac
80
+ [ -f "$local_receipt" ] && [ -r "$local_receipt" ] || die "local receipt not found: $local_receipt"
81
+ fi
82
+ if [ -n "$receipt" ]; then
83
+ case "$receipt" in /*) ;; *) receipt="$PWD/$receipt" ;; esac
84
+ [ -f "$receipt" ] && [ -r "$receipt" ] || die "receipt not found: $receipt"
85
+ command -v python3 >/dev/null 2>&1 || die "python3 is required to read a receipt"
86
+ fi
87
+ command -v gh >/dev/null 2>&1 || die "the GitHub CLI is required: install gh, then run gh auth login"
88
+ command -v jq >/dev/null 2>&1 || die "jq is required"
89
+
90
+ # This instance's repository, as the skills name it; base-branch.sh and
91
+ # config-get.sh read the same egregore.json.
92
+ config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
93
+ org="$(jq -r '.github_org | select(type == "string" and length > 0)' "$config" 2>/dev/null || true)"
94
+ core_repo=""
95
+ [ -z "$org" ] || core_repo="$(bash "$SCRIPT_DIR/bin/config-get.sh" repo 2>/dev/null || true)"
96
+ # The gate ledger lives in this checkout's memory, as bin/agent.sh finds it.
97
+ memory="${EGREGORE_MEMORY_DIR:-$SCRIPT_DIR/memory}"
98
+
99
+ scratch="$(mktemp -d "${TMPDIR:-/tmp}/merge-gate.XXXXXX")" || die "could not create a scratch directory"
100
+ trap 'rm -rf "$scratch"' EXIT
101
+ cd "$SCRIPT_DIR"
102
+
103
+ # gh with the checkout's token from .env when bin/gh-run.sh is there.
104
+ gh_cli() {
105
+ if [ -f "$SCRIPT_DIR/bin/gh-run.sh" ]; then
106
+ bash "$SCRIPT_DIR/bin/gh-run.sh" "$@"
107
+ else
108
+ gh "$@"
109
+ fi
110
+ }
111
+
112
+ lower() {
113
+ printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
114
+ }
115
+
116
+ first_line() {
117
+ head -n 1 "$1" 2>/dev/null | cut -c1-200
118
+ }
119
+
120
+ plural() {
121
+ if [ "$1" -eq 1 ]; then printf '1 %s' "$2"; else printf '%s %ss' "$1" "$2"; fi
122
+ }
123
+
124
+ # refuse CODE REASON [DETAIL...]: stop without posting or merging. A real run
125
+ # writes to stderr; --dry-run prints the decision and its detail on stdout.
126
+ # The one refusal the ledger records sets ledger_kind first; its ledger line
127
+ # follows the decision on the same stream.
128
+ ledger_kind=""
129
+ refuse() {
130
+ local code="$1" reason="$2" line
131
+ shift 2
132
+ if $dry_run; then
133
+ printf 'decision: refuse, %s (exit %s)\n' "$reason" "$code"
134
+ else
135
+ printf 'merge-gate: refused #%s: %s\n' "$number" "$reason" >&2
136
+ fi
137
+ for line in "$@"; do
138
+ [ -n "$line" ] || continue
139
+ if $dry_run; then printf '%s\n' "$line"; else printf '%s\n' "$line" >&2; fi
140
+ done
141
+ if [ -n "$ledger_kind" ]; then
142
+ if $dry_run; then ledger "$ledger_kind" false || true; else ledger "$ledger_kind" false >&2 || true; fi
143
+ fi
144
+ exit "$code"
145
+ }
146
+
147
+ # --- The pull request ------------------------------------------------------
148
+ # A number is looked up in this repository, never in whichever remote gh
149
+ # would pick by default.
150
+ view_args=(pr view "$pr")
151
+ case "$pr" in
152
+ *[!0-9]*) ;;
153
+ *) [ -z "$core_repo" ] || view_args+=(--repo "$core_repo") ;;
154
+ esac
155
+ gh_cli "${view_args[@]}" --json number,url,state,isDraft,mergeable,baseRefName,headRefName,headRefOid \
156
+ >"$scratch/pr.json" 2>"$scratch/err" \
157
+ || die "could not read pull request $pr through gh: $(first_line "$scratch/err")"
158
+ IFS=$'\t' read -r number url state draft mergeable pr_base head_ref sha < <(
159
+ jq -r '[.number, .url, .state, .isDraft, (.mergeable // "UNKNOWN"), .baseRefName, .headRefName, .headRefOid]
160
+ | map(tostring) | @tsv' "$scratch/pr.json" 2>/dev/null
161
+ ) || die "gh returned an unreadable pull request for $pr"
162
+ [[ "$number" =~ ^[0-9]+$ && "$sha" =~ ^[0-9a-f]{40}$ && "$url" == https://*/*/*/pull/"$number" ]] \
163
+ || die "gh returned an unreadable pull request for $pr"
164
+ short="${sha:0:8}"
165
+
166
+ [ "$state" = "OPEN" ] || die "#$number is not open (state $state)"
167
+
168
+ # --- The integration branch of the PR's repository --------------------------
169
+ # This repository merges into its configured base, a managed repo into its
170
+ # own. Without github_org there is nothing to tell apart: the PR is this
171
+ # repository's.
172
+ pr_repo="${url#https://}"
173
+ pr_repo="${pr_repo#*/}"
174
+ pr_repo="${pr_repo%/pull/*}"
175
+ managed=""
176
+ if [ -z "$core_repo" ] || [ "$(lower "$pr_repo")" = "$(lower "$core_repo")" ]; then
177
+ base="$(bash "$SCRIPT_DIR/bin/base-branch.sh")" || die "could not read the integration branch from egregore.json"
178
+ else
179
+ if [ "$(lower "${pr_repo%%/*}")" = "$(lower "$org")" ]; then
180
+ managed="$(jq -r --arg repo "$(lower "${pr_repo#*/}")" '
181
+ first(.repos[]? | (if type == "object" then .name else . end)
182
+ | select(type == "string" and ascii_downcase == $repo)) // empty' "$config" 2>/dev/null || true)"
183
+ fi
184
+ [ -n "$managed" ] \
185
+ || refuse 4 "it belongs to $pr_repo, which is neither this instance's repository ($core_repo) nor a managed repo, so it has no integration branch here"
186
+ base="$(bash "$SCRIPT_DIR/bin/base-branch.sh" "$managed")" \
187
+ || die "could not read the integration branch of the managed repo $managed from egregore.json"
188
+ fi
189
+ if [ "$pr_base" != "$base" ]; then
190
+ if [ -n "$managed" ]; then
191
+ refuse 4 "its base is $pr_base, not $base, the integration branch of $pr_repo; the gate merges only into $base"
192
+ fi
193
+ refuse 4 "its base is $pr_base, not the integration branch $base; the gate merges only into $base, and the release flow owns main"
194
+ fi
195
+ [ "$draft" = "false" ] || die "#$number is a draft; mark it ready for review first"
196
+ [ "$mergeable" != "CONFLICTING" ] || die "#$number conflicts with $base; rebase it, then run the gate again"
197
+
198
+ # --- The checks on the head commit -----------------------------------------
199
+ # gh keeps the latest run of each check, so a superseded run never counts.
200
+ # Its exit status says whether checks failed; the JSON is what is read.
201
+ gh_cli pr checks "$url" --json name,state,link >"$scratch/checks.json" 2>"$scratch/err" || true
202
+ if ! jq -e 'type == "array"' "$scratch/checks.json" >/dev/null 2>&1; then
203
+ grep -q 'no checks reported' "$scratch/err" 2>/dev/null \
204
+ || die "could not read the checks on $short: $(first_line "$scratch/err")"
205
+ printf '[]\n' >"$scratch/checks.json"
206
+ fi
207
+ jq '
208
+ def class:
209
+ if .state == "SUCCESS" then "green"
210
+ elif .state == "SKIPPED" or .state == "NEUTRAL" then "neutral"
211
+ elif .state == "QUEUED" or .state == "IN_PROGRESS" or .state == "PENDING" or .state == "WAITING"
212
+ or .state == "REQUESTED" or .state == "EXPECTED" then "pending"
213
+ else "red" end;
214
+ map({class: class, name: (.name // ""), state: ((.state // "unknown") | ascii_downcase), link: (.link // "")})
215
+ | sort_by({"red": 0, "pending": 1, "neutral": 2, "green": 3}[.class])
216
+ ' "$scratch/checks.json" >"$scratch/classified.json" 2>/dev/null || die "could not read the checks on $short"
217
+
218
+ count() {
219
+ jq --arg class "$1" '[.[] | select(.class == $class)] | length' "$scratch/classified.json"
220
+ }
221
+ # One line per check: class, name, result, and the link of a red or pending one.
222
+ lines() {
223
+ jq -r --arg class "${1:-}" '.[] | select($class == "" or .class == $class)
224
+ | " " + ((.class + " ")[:8]) + " " + .name + " (" + .state + ")"
225
+ + (if (.class == "red" or .class == "pending") and .link != "" then " " + .link else "" end)' \
226
+ "$scratch/classified.json"
227
+ }
228
+ # The same lines for a refusal, unless --dry-run has already listed every check.
229
+ detail() {
230
+ $dry_run || lines "$1"
231
+ }
232
+ green="$(count green)"; neutral="$(count neutral)"; pending="$(count pending)"; red="$(count red)"
233
+ total=$((green + neutral + pending + red))
234
+
235
+ if $dry_run; then
236
+ printf 'merge-gate: #%s %s into %s at %s\n' "$number" "$head_ref" "$base" "$short"
237
+ [ "$total" -eq 0 ] || lines
238
+ printf 'checks: %s green, %s neutral, %s pending, %s red\n' "$green" "$neutral" "$pending" "$red"
239
+ fi
240
+
241
+ [ "$total" -gt 0 ] || [ -n "$local_receipt" ] || refuse 3 "no checks have reported on $short yet; run the gate again once CI starts"
242
+ if [ "$pending" -gt 0 ]; then
243
+ reason="$(plural "$pending" check) of $total still pending on $short"
244
+ [ "$red" -eq 0 ] || reason="$reason, $(plural "$red" "red check") so far"
245
+ refuse 3 "$reason" "$(detail pending)" "Run the gate again when they finish."
246
+ fi
247
+
248
+ # A workflow run still going may not have created every check yet: jobs that
249
+ # wait on a planner appear only once it finishes. Such a run holds the merge.
250
+ repo_ref="${url#https://}"
251
+ repo_ref="${repo_ref%/pull/*}"
252
+ gh_cli run list --repo "$repo_ref" --commit "$sha" --json workflowName,status,url --limit 100 \
253
+ >"$scratch/runs.json" 2>"$scratch/err" \
254
+ || die "could not read the workflow runs on $short: $(first_line "$scratch/err")"
255
+ unfinished="$(jq -r '.[]? | select(.status != "completed")
256
+ | " pending " + (.workflowName // "workflow") + " run (" + ((.status // "unknown") | ascii_downcase) + ")"
257
+ + (if (.url // "") != "" then " " + .url else "" end)' "$scratch/runs.json" 2>/dev/null)" \
258
+ || die "could not read the workflow runs on $short"
259
+ if [ -n "$unfinished" ]; then
260
+ refuse 3 "a workflow run on $short is still going, so not every check has reported" "$unfinished" \
261
+ "Run the gate again when it finishes."
262
+ fi
263
+
264
+ # Explicit local evidence can replace unavailable hosted execution, never pending
265
+ # execution. Bind it to the current base as well as the classified PR head.
266
+ read_base_sha() {
267
+ gh_cli pr view "$url" --json baseRefOid >"$scratch/base.json" 2>"$scratch/err" || return 1
268
+ jq -er '.baseRefOid | select(test("^[0-9a-f]{40}$"))' "$scratch/base.json"
269
+ }
270
+ if [ -n "$local_receipt" ]; then
271
+ local_base_sha="$(read_base_sha)" || die "could not resolve the current base for local evidence"
272
+ receipt="$scratch/local-receipt.md"
273
+ python3 "$SCRIPT_DIR/bin/lib/local_ci_receipt.py" validate --receipt "$local_receipt" \
274
+ --repo "$pr_repo" --pr "$number" --head "$sha" --base "$base" --base-sha "$local_base_sha" \
275
+ >"$receipt" 2>"$scratch/err" || die "local evidence rejected: $(first_line "$scratch/err")"
276
+ fi
277
+
278
+ # The merge names the classified commit: a push after the read is refused by GitHub.
279
+ merge_head() {
280
+ if [ -n "$local_receipt" ]; then
281
+ local latest_base
282
+ latest_base="$(read_base_sha)" || { echo "cannot recheck integration base" >"$scratch/merge.out"; return 1; }
283
+ [ "$latest_base" = "$local_base_sha" ] || {
284
+ echo "integration base changed; refresh local checks and receipt" >"$scratch/merge.out"; return 1;
285
+ }
286
+ fi
287
+ gh_cli pr merge "$url" --merge --match-head-commit "$sha" >"$scratch/merge.out" 2>&1
288
+ }
289
+
290
+ # --- The ledger ----------------------------------------------------------------
291
+ # CI's attempts on the head: the highest run_attempt among its ci.yml runs and,
292
+ # past the first, the jobs of every attempt in one listing. A job that failed
293
+ # or timed out in an earlier attempt and passes in the last flaked; one that
294
+ # was cancelled in an earlier attempt and passes in the last recovered, for a
295
+ # cause the listing does not hold (a job killed by its own time limit is
296
+ # cancelled like one a person stopped); the aggregate Full CI result job is
297
+ # neither. A gh failure leaves attempts unknown and nothing flaked or
298
+ # recovered.
299
+ attempts=""
300
+ ci_attempts() {
301
+ local host="${repo_ref%%/*}" run="" id=""
302
+ attempts=""
303
+ : >"$scratch/reruns"
304
+ gh_cli api "repos/$pr_repo/actions/runs?head_sha=$sha&per_page=20" --hostname "$host" \
305
+ >"$scratch/ci-runs.json" 2>/dev/null || return 0
306
+ run="$(jq -r '[.workflow_runs[]? | select((.path // "" | split("@")[0]) == ".github/workflows/ci.yml")
307
+ | select((.id | type) == "number" and (.run_attempt | type) == "number")]
308
+ | max_by([.run_attempt, .id]) // empty | "\(.id) \(.run_attempt)"' "$scratch/ci-runs.json" 2>/dev/null)" \
309
+ || return 0
310
+ id="${run% *}"
311
+ run="${run#* }"
312
+ [[ "$id" =~ ^[0-9]+$ && "$run" =~ ^[1-9][0-9]*$ ]] || return 0
313
+ if [ "$run" -gt 1 ]; then
314
+ gh_cli api "repos/$pr_repo/actions/runs/$id/jobs?filter=all&per_page=100" --paginate --hostname "$host" \
315
+ >"$scratch/jobs.json" 2>/dev/null || return 0
316
+ jq -rs --argjson last "$run" '
317
+ [.[] | .jobs[]? | select((.name | type) == "string" and (.run_attempt | type) == "number")
318
+ | select(.name | test("^Full CI result( — |$)") | not)]
319
+ | ([.[] | select(.run_attempt == $last and .conclusion == "success") | .name] | unique) as $passed
320
+ | ([.[] | select(.run_attempt < $last and (.conclusion == "failure" or .conclusion == "timed_out")) | .name]
321
+ | unique | map(select(. as $job | any($passed[]; . == $job)))) as $flaked
322
+ | ([.[] | select(.run_attempt < $last and .conclusion == "cancelled") | .name]
323
+ | unique | map(select(. as $job | any($passed[]; . == $job) and (any($flaked[]; . == $job) | not)))) as $recovered
324
+ | ($flaked[] | "flaked\t\(.)"), ($recovered[] | "recovered\t\(.)")' "$scratch/jobs.json" \
325
+ >"$scratch/reruns.read" 2>/dev/null || return 0
326
+ mv "$scratch/reruns.read" "$scratch/reruns" || return 0
327
+ fi
328
+ attempts="$run"
329
+ }
330
+
331
+ # ledger KIND POSTED: append the decision to the gate ledger, or with --dry-run
332
+ # print the line it would append. Its callers add `|| true`: a line that cannot
333
+ # be written is reported and never changes the decision or the exit code.
334
+ ledger() {
335
+ local kind="$1" posted="$2" fate="" job="" flaked="" recovered="" note="" status=0 reason="" by="" session="" line=""
336
+ local args=(gate-event --root "$SCRIPT_DIR" --memory "$memory" --kind "$kind" --pr "$number"
337
+ --head "$sha" --base "$base" --repo "$pr_repo" --green "$green" --neutral "$neutral" --red "$red")
338
+ ci_attempts || true
339
+ [ -z "$attempts" ] || args+=(--attempts "$attempts")
340
+ while IFS=$'\t' read -r fate job; do
341
+ case "$fate" in
342
+ flaked) args+=(--flaked "$job"); flaked="${flaked:+$flaked, }$job" ;;
343
+ recovered) args+=(--recovered "$job"); recovered="${recovered:+$recovered, }$job" ;;
344
+ esac
345
+ done <"$scratch/reruns"
346
+ if $posted; then args+=(--receipt); fi
347
+ if [ -n "$attempts" ] && [ "$attempts" -gt 1 ]; then
348
+ note=" · attempts $attempts, flaked: ${flaked:-none}${recovered:+; recovered: $recovered}"
349
+ fi
350
+ if $dry_run; then
351
+ python3 "$SCRIPT_DIR/bin/lib/session_record.py" "${args[@]}" --dry-run \
352
+ >"$scratch/ledger-preview.json" 2>"$scratch/ledger.err" || status=$?
353
+ if [ "$status" -eq 0 ]; then
354
+ printf 'ledger: would append %s\n' "$(cat "$scratch/ledger-preview.json")"
355
+ else
356
+ printf 'ledger: would withhold (%s)\n' "$(first_line "$scratch/ledger.err")"
357
+ fi
358
+ return 0
359
+ fi
360
+ python3 "$SCRIPT_DIR/bin/lib/session_record.py" "${args[@]}" >/dev/null 2>"$scratch/ledger.err" || status=$?
361
+ if [ "$status" -eq 0 ]; then
362
+ printf 'ledger: gate.jsonl +1%s\n' "$note"
363
+ else
364
+ reason="$(first_line "$scratch/ledger.err")" || reason=""
365
+ printf 'ledger: not written (%s)\n' "${reason:-exit $status}"
366
+ fi
367
+ return 0
368
+ }
369
+
370
+ if [ "$red" -eq 0 ] && [ -z "$local_receipt" ]; then
371
+ result="all $green checks green"
372
+ [ "$neutral" -eq 0 ] || result="$result, $neutral neutral"
373
+ if $dry_run; then
374
+ printf 'decision: merge, %s (exit 0)\n' "$result"
375
+ ledger merge false || true
376
+ exit 0
377
+ fi
378
+ [ -z "$receipt" ] || echo "receipt: not posted, every check is green"
379
+ merge_head || die "gh could not merge #$number: $(first_line "$scratch/merge.out")"
380
+ printf 'merged %s into %s: %s\n' "$sha" "$base" "$result"
381
+ ledger merge false || true
382
+ exit 0
383
+ fi
384
+
385
+ if [ -z "$receipt" ]; then
386
+ ledger_kind=refuse
387
+ refuse 2 "$(plural "$red" "red check") on $short and no receipt" "$(detail red)" \
388
+ "Fix them, or write a receipt that names each red check and why it does not matter, then run: bash bin/agent.sh merge $number --receipt FILE"
389
+ fi
390
+
391
+ # --- The receipt -------------------------------------------------------------
392
+ # A red check is named when one line of the receipt holds its exact name
393
+ # (whitespace aside, not inside a longer word or a longer check name) and at
394
+ # least three words besides the check names on that line: the reason.
395
+ if ! python3 - "$receipt" "$scratch/classified.json" "$local_receipt" >"$scratch/missing" 2>"$scratch/err" <<'PY'
396
+ import json
397
+ import re
398
+ import sys
399
+
400
+
401
+ def norm(text):
402
+ return " ".join(text.split())
403
+
404
+
405
+ def pattern(name):
406
+ before = r"(?<![^\W_])" if name[:1].isalnum() else ""
407
+ after = r"(?![^\W_])" if name[-1:].isalnum() else ""
408
+ return re.compile(before + re.escape(name) + after)
409
+
410
+
411
+ # Structured local evidence must explicitly explain each failed job. A job
412
+ # named "test" appearing in a recorded command is not its waiver reason.
413
+ if sys.argv[3]:
414
+ with open(sys.argv[3], encoding="utf-8") as handle:
415
+ local = json.load(handle)
416
+ with open(sys.argv[2], encoding="utf-8") as handle:
417
+ checks = json.load(handle)
418
+ reasons = {norm(row["name"]): row["reason"] for row in local["failed_checks"]}
419
+ for check in checks:
420
+ if check["class"] == "red" and len(re.findall(r"[^\W_]+", reasons.get(norm(check["name"]), ""))) < 3:
421
+ print(f" missing {check['name']} ({check['state']})")
422
+ sys.exit(0)
423
+
424
+ with open(sys.argv[1], encoding="utf-8", errors="replace") as handle:
425
+ receipt = [norm(line) for line in handle.read().splitlines()]
426
+ with open(sys.argv[2], encoding="utf-8") as handle:
427
+ checks = json.load(handle)
428
+ # Longest names first, so a check whose name contains another's is taken whole.
429
+ names = sorted({norm(check["name"]) for check in checks if norm(check["name"])}, key=len, reverse=True)
430
+ patterns = [(name, pattern(name)) for name in names]
431
+ named = set()
432
+ for line in receipt:
433
+ present = set()
434
+ for name, exact in patterns:
435
+ if exact.search(line):
436
+ present.add(name)
437
+ line = exact.sub(" ", line)
438
+ if len(re.findall(r"[^\W_]+", line)) >= 3:
439
+ named |= present
440
+ reported = set()
441
+ for check in checks:
442
+ name = norm(check["name"])
443
+ if check["class"] != "red" or name in named or name in reported:
444
+ continue
445
+ reported.add(name)
446
+ link = f" {check['link']}" if check["link"] else ""
447
+ print(f" missing {check['name']} ({check['state']}){link}")
448
+ PY
449
+ then
450
+ die "could not read the receipt $receipt: $(first_line "$scratch/err")"
451
+ fi
452
+ if [ -s "$scratch/missing" ]; then
453
+ refuse 2 "the receipt does not name every red check with a reason" "$(cat "$scratch/missing")" \
454
+ "Name each red check exactly as listed, on a line that says why it does not matter."
455
+ fi
456
+
457
+ result="$green green"
458
+ [ "$neutral" -eq 0 ] || result="$result, $neutral neutral"
459
+ result="$result, $red red under receipt"
460
+ [ -z "$local_receipt" ] || result="$result, verified local evidence"
461
+ if $dry_run; then
462
+ printf 'decision: merge, %s; the receipt is posted on #%s first (exit 0)\n' "$result" "$number"
463
+ ledger merge true || true
464
+ exit 0
465
+ fi
466
+
467
+ # The comment: the receipt as written under one "Merge receipt" heading, then
468
+ # the commit, each red check and the runs, linked. Backticks are Markdown.
469
+ # shellcheck disable=SC2016
470
+ {
471
+ printf '## Merge receipt\n\n'
472
+ started=false
473
+ while IFS= read -r line || [ -n "$line" ]; do
474
+ if ! $started; then
475
+ [ -n "${line//[[:space:]]/}" ] || continue
476
+ started=true
477
+ if [[ "$line" =~ ^#+[[:space:]]*[Mm]erge[[:space:]]+[Rr]eceipt[[:space:]]*$ ]]; then continue; fi
478
+ fi
479
+ printf '%s\n' "$line"
480
+ done <"$receipt"
481
+ printf '\n---\n\n'
482
+ printf 'Checks on [`%s`](%s/commit/%s) into `%s`: %s green, %s neutral, %s red.\n\n' \
483
+ "$short" "${url%/pull/*}" "$sha" "$base" "$green" "$neutral" "$red"
484
+ jq -r '.[] | select(.class == "red")
485
+ | "- red · " + .name + " (" + .state + ")" + (if .link != "" then " · " + .link else "" end)' \
486
+ "$scratch/classified.json"
487
+ jq -r '[.[] | .link | select(test("/actions/runs/[0-9]+/job/[0-9]+")) | sub("/job/[0-9]+.*$"; "")]
488
+ | unique | .[] | "- run · " + .' "$scratch/classified.json"
489
+ printf '\nMerging on this receipt through `bin/agent.sh merge`.\n'
490
+ } >"$scratch/comment.md"
491
+
492
+ comment_url="$(gh_cli pr comment "$url" --body-file "$scratch/comment.md" 2>"$scratch/err")" \
493
+ || die "could not post the receipt on #$number, so nothing merged: $(first_line "$scratch/err")"
494
+ printf 'receipt: %s\n' "${comment_url:-posted on #$number}"
495
+ merge_head || die "the receipt is posted, but gh could not merge #$number: $(first_line "$scratch/merge.out")"
496
+ printf 'merged %s into %s: %s\n' "$sha" "$base" "$result"
497
+ ledger merge true || true
498
+ exit 0
@@ -15,6 +15,8 @@ set +x
15
15
  NOTIFY_CHECKOUT="$(cd "$(dirname "$0")/.." && pwd)"
16
16
  # shellcheck source=/dev/null
17
17
  source "$NOTIFY_CHECKOUT/bin/lib/scratch.sh"
18
+ # shellcheck source=bin/lib/config.sh
19
+ source "$NOTIFY_CHECKOUT/bin/lib/config.sh"
18
20
  SCRIPT_DIR="${EGREGORE_NOTIFY_PROJECT_DIR:-$NOTIFY_CHECKOUT}"
19
21
  CONFIG="$SCRIPT_DIR/egregore.json"
20
22
 
@@ -32,7 +34,7 @@ SESSION_ID="$(sed -n '1p' "$SCRIPT_DIR/.egregore-session-id" 2>/dev/null || true
32
34
  SESSION_ID="${SESSION_ID:-no-session}"
33
35
  PROJECT_HASH="$(printf '%s' "$SCRIPT_DIR" | cksum | awk '{print $1}')"
34
36
  STATE_DIR="${EGREGORE_NOTIFY_STATE_DIR:-$HOME/.egregore/notifications/$PROJECT_HASH}"
35
- RELAY_URL="${EGREGORE_NOTIFY_RELAY_URL:-https://api.egregore.xyz}"
37
+ RELAY_URL="$(_hosted_api_url "${EGREGORE_NOTIFY_RELAY_URL:-$EGREGORE_HOSTED_API_ORIGIN}")" || RELAY_URL=""
36
38
  mkdir -p "$STATE_DIR"
37
39
  chmod 700 "$STATE_DIR" 2>/dev/null || true
38
40
 
@@ -41,7 +43,7 @@ if [ -f "$SCRIPT_DIR/.env" ]; then
41
43
  EGREGORE_API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
42
44
  SLACK_BOT_TOKEN="${SLACK_BOT_TOKEN:-$(grep '^SLACK_BOT_TOKEN=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)}"
43
45
  fi
44
- API_URL="${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)}"
46
+ API_URL="$(_hosted_api_url "${EGREGORE_API_URL:-$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null)}")" || API_URL=""
45
47
  API_KEY="${EGREGORE_API_KEY:-}"
46
48
 
47
49
  die() {
@@ -438,6 +440,7 @@ dispatch_local_telegram() {
438
440
  group_link=""
439
441
  ;;
440
442
  esac
443
+ [ -n "$RELAY_URL" ] || die "The notification relay address isn't a plain web address, so nothing was sent."
441
444
  curl -q -sS -X POST "${RELAY_URL}/api/notify/relay" \
442
445
  -H "Content-Type: application/json" \
443
446
  -d "$(jq -nc \
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ # Decide "Request access" asks for this organisation's hosted pages.
3
+ #
4
+ # bash bin/page-access.sh requests [--page <id>] [--status pending|granted|declined|all]
5
+ # bash bin/page-access.sh grant <request-id>
6
+ # bash bin/page-access.sh decline <request-id>
7
+ #
8
+ # Someone signed in who opens a page outside its audience (publish-artifact.sh
9
+ # --visible-to) can ask for it. A grant adds their email to the page's
10
+ # audience; a decline shows them "Request declined" and stops new asks for
11
+ # seven days. Nothing is sent to anyone: the requester sees the outcome on
12
+ # the page. Connected mode only. The org key names the organisation and your
13
+ # own GitHub token proves who decides; both stay inside bin/api-call.sh.
14
+ #
15
+ # Exit: 0 done, 1 the request failed, 2 usage, 4 Local mode.
16
+ set -euo pipefail
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
19
+ # shellcheck source=/dev/null
20
+ source "$SCRIPT_DIR/bin/lib/config.sh"
21
+
22
+ usage() {
23
+ echo 'Usage: bash bin/page-access.sh requests [--page <id>] [--status pending|granted|declined|all]' >&2
24
+ echo ' bash bin/page-access.sh grant <request-id>' >&2
25
+ echo ' bash bin/page-access.sh decline <request-id>' >&2
26
+ exit 2
27
+ }
28
+
29
+ [ "$#" -ge 1 ] || usage
30
+ command="$1"
31
+ shift
32
+
33
+ page=''
34
+ status=pending
35
+ request_id=''
36
+ case "$command" in
37
+ requests)
38
+ while [ "$#" -gt 0 ]; do
39
+ [ "$#" -ge 2 ] || usage
40
+ case "$1" in
41
+ --page) page="$2" ;;
42
+ --status) status="$2" ;;
43
+ *) usage ;;
44
+ esac
45
+ shift 2
46
+ done
47
+ [[ "$status" =~ ^(pending|granted|declined|all)$ ]] || usage
48
+ [ -z "$page" ] || [[ "$page" =~ ^[A-Za-z0-9_-]{1,50}$ ]] || usage
49
+ ;;
50
+ grant|decline)
51
+ [ "$#" -eq 1 ] || usage
52
+ request_id="$1"
53
+ [[ "$request_id" =~ ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$ ]] || usage
54
+ ;;
55
+ *) usage ;;
56
+ esac
57
+
58
+ if [ "$(_detect_mode)" != connected ]; then
59
+ echo 'page-access: asking for a page and deciding requests needs Connected mode, where pages open by identity. In Local mode, hand over the password-protected file instead.' >&2
60
+ exit 4
61
+ fi
62
+
63
+ mkdir -p "$SCRIPT_DIR/tmp"
64
+ out=$(mktemp "$SCRIPT_DIR/tmp/page-access.XXXXXX")
65
+ err=$(mktemp "$SCRIPT_DIR/tmp/page-access-error.XXXXXX")
66
+ trap 'rm -f -- "$out" "$err"' EXIT
67
+
68
+ call() {
69
+ if ! bash "$SCRIPT_DIR/bin/api-call.sh" "$1" "$2" --auth egregore+github \
70
+ --out "$out" --error-out "$err"; then
71
+ detail=$(jq -r '.detail // empty' "$err" 2>/dev/null || true)
72
+ [ -z "$detail" ] || printf 'page-access: %s\n' "$detail" >&2
73
+ exit 1
74
+ fi
75
+ }
76
+
77
+ case "$command" in
78
+ requests)
79
+ path="/api/page-access/requests?status=$status"
80
+ [ -z "$page" ] || path="$path&page=$page"
81
+ call GET "$path"
82
+ if [ "$(jq '.requests | length' "$out")" -eq 0 ]; then
83
+ echo "No $([ "$status" = all ] || printf '%s ' "$status")requests."
84
+ exit 0
85
+ fi
86
+ jq -r '.requests[] | [
87
+ .id,
88
+ .page,
89
+ (.requester.email + (if .requester.handle then " (@" + .requester.handle + ")" else "" end)),
90
+ .status,
91
+ .requested_at,
92
+ (if .decided_by then "by " + .decided_by + " " + .decided_at else "" end)
93
+ ] | @tsv' "$out"
94
+ ;;
95
+ grant)
96
+ call POST "/api/page-access/requests/$request_id/grant"
97
+ jq -r '"Granted: " + .request.requester.email + " can open " + .request.page + " when signed in."' "$out"
98
+ ;;
99
+ decline)
100
+ call POST "/api/page-access/requests/$request_id/decline"
101
+ jq -r '"Declined: " + .request.requester.email + " sees \"Request declined\" on " + .request.page + " and cannot ask again for seven days."' "$out"
102
+ ;;
103
+ esac
@@ -29,7 +29,7 @@ authorize_identity() {
29
29
  }
30
30
 
31
31
  remote_config() {
32
- api_url="${EGREGORE_API_URL:-$(_config_val api_url)}"
32
+ api_url="$(_hosted_api_url "${EGREGORE_API_URL:-$(_config_val api_url)}")" || api_url=""
33
33
  api_key="${EGREGORE_API_KEY:-$(_load_env_var EGREGORE_API_KEY)}"
34
34
  }
35
35
 
@@ -49,11 +49,22 @@ sync_remote_identity() {
49
49
  email,
50
50
  emails
51
51
  }')"
52
- api_result="$(curl -q -sf "${api_url}/api/user/ensure" \
52
+ api_response="$(curl -q -s "${api_url}/api/user/ensure" \
53
53
  -H "Authorization: Bearer ${api_key}" \
54
54
  -H "Content-Type: application/json" \
55
55
  -d "$api_payload" \
56
+ -w '\n%{http_code}' \
56
57
  --max-time 10 2>/dev/null || true)"
58
+ api_code="${api_response##*$'\n'}"
59
+ api_result=""
60
+ case "$api_code" in 2??) api_result="${api_response%$'\n'*}" ;; esac
61
+ # This is each session's one authenticated call, so it doubles as the
62
+ # probe: a rejected organization key is renewed through verified GitHub
63
+ # membership (bin/runtime-identity.sh), never from this response.
64
+ if [ "$api_code" = 401 ] && [ -f "$ROOT/bin/runtime-identity.sh" ]; then
65
+ rejected_code="$(printf '%s' "${api_response%$'\n'*}" | jq -r '.code // empty' 2>/dev/null || true)"
66
+ bash "$ROOT/bin/runtime-identity.sh" recover "$rejected_code" >/dev/null 2>&1 || true
67
+ fi
57
68
  api_status="$(printf '%s' "$api_result" | jq -r '.status // "failed"' 2>/dev/null || echo failed)"
58
69
  platform_user_id="$(printf '%s' "$api_result" | jq -r '.user_id // empty' 2>/dev/null || true)"
59
70
  if [ "$api_status" = "ok" ]; then