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
@@ -22,6 +22,76 @@
22
22
  # Output: the footer lines on stdout, each already indented two spaces.
23
23
  # Returns 0 when nothing failed, 1 when at least one dimension failed.
24
24
 
25
+ # Dated, release-delivered incident copy, not a live availability check. Keep
26
+ # the launcher counterpart aligned; launcher-connected-status.test.js checks
27
+ # the actual output of both implementations. No network or credential reads.
28
+ _hosted_recovery_notice() {
29
+ [ "${LOCAL_MODE:-true}" != "true" ] || return 0
30
+ local origin now
31
+ [ -f "${CONFIG:-}" ] || return 0
32
+ origin=$(jq -r '.api_url // empty' "$CONFIG" 2>/dev/null) || return 0
33
+ case "$origin" in
34
+ https://api.egregore.xyz|https://api.egregore.xyz/|https://egregore-production-55f2.up.railway.app|https://egregore-production-55f2.up.railway.app/) ;;
35
+ *) return 0 ;;
36
+ esac
37
+ now=$(date -u +%s 2>/dev/null) || now=0
38
+ # A stale bundled notice never presents itself as current service status.
39
+ if [ "$now" -ge 1792627200 ] 2>/dev/null && [ "$now" -lt 1792800000 ] 2>/dev/null; then
40
+ printf '%s\n' ' ◐ Service recovery · 22 Oct 2026' \
41
+ ' Some connected services are temporarily unavailable' \
42
+ ' during security recovery.'
43
+ else
44
+ printf '%s\n' ' ◐ Service recovery · last update 22 Oct 2026' \
45
+ ' Some connected services were unavailable at this update.' \
46
+ ' Current hosted availability has not been checked.'
47
+ fi
48
+ printf '%s\n' ' Local memory and search remain available.' \
49
+ ' Keep your existing Egregore; you do not need to set it up again.'
50
+ }
51
+
52
+ # Recorded in .egregore-state.json by bin/runtime-identity.sh when it renews,
53
+ # or cannot renew, an organization key retired in the October 2026 security
54
+ # reset. Local state only; no network. The renewal shows once; the sign-in
55
+ # line shows each session until a renewal works.
56
+ _hosted_key_notice() {
57
+ [ "${LOCAL_MODE:-true}" != "true" ] || return 0
58
+ local state_file="${STATE_FILE:-}" file tmp
59
+ [ -n "$state_file" ] && [ -f "$state_file" ] || return 0
60
+ case "$(jq -r '.hosted_key_notice.state // empty' "$state_file" 2>/dev/null)" in
61
+ renewed)
62
+ printf '%s\n' " ↻ Hosted services renewed this organization's key after" \
63
+ ' the October security reset. Your memory, files and' \
64
+ ' history are untouched.'
65
+ file=$(realpath "$state_file" 2>/dev/null) || file="$state_file"
66
+ tmp=$(mktemp "$file.XXXXXX" 2>/dev/null) || return 0
67
+ jq 'if .hosted_key_notice.state == "renewed" then del(.hosted_key_notice) else . end' \
68
+ "$file" > "$tmp" 2>/dev/null && [ -s "$tmp" ] && mv "$tmp" "$file" || rm -f "$tmp"
69
+ ;;
70
+ needs_sign_in)
71
+ printf '%s\n' ' ◐ Hosted services need a fresh key after the October' \
72
+ ' security reset. Your memory, files and history are' \
73
+ ' untouched. Run bash bin/github-auth.sh and start a new' \
74
+ ' session — it renews on its own.'
75
+ ;;
76
+ esac
77
+ return 0
78
+ }
79
+
80
+ # A Connected org whose saved api_url is not a plain web address sends nothing
81
+ # to hosted services (bin/lib/config.sh refuses it), and /update rewrites only
82
+ # a plain retired address. Say so each session until egregore.json is fixed.
83
+ # Needs config.sh's resolver; reads local config only.
84
+ _hosted_address_notice() {
85
+ [ "${LOCAL_MODE:-true}" != "true" ] || return 0
86
+ [ -f "${CONFIG:-}" ] || return 0
87
+ command -v _config_api_url >/dev/null 2>&1 || return 0
88
+ EGREGORE_ADDRESS_NOTICE=shown _config_api_url >/dev/null 2>&1 && return 0
89
+ printf '%s\n' " ◐ Hosted services are off: api_url in egregore.json isn't" \
90
+ ' a plain web address, so nothing is sent to it. Correct it' \
91
+ " (the hosted address is ${EGREGORE_HOSTED_API_ORIGIN:-https://api.egregore.xyz})" \
92
+ ' and start a new session. Local memory and search work.'
93
+ }
94
+
25
95
  _health_footer_label() {
26
96
  case "$1" in
27
97
  github) printf 'github' ;;
@@ -65,7 +135,16 @@ _health_footer_check() {
65
135
  }
66
136
 
67
137
  _render_health_footer() {
68
- local width="${LINE_WIDTH:-65}" failed=0
138
+ local width="${LINE_WIDTH:-65}" failed=0 recovery_notice key_notice address_notice local_only=""
139
+ address_notice=$(_hosted_address_notice)
140
+ [ -z "$address_notice" ] || { printf '%s\n' "$address_notice"; local_only=1; }
141
+ recovery_notice=$(_hosted_recovery_notice)
142
+ [ -z "$recovery_notice" ] || printf '%s\n' "$recovery_notice"
143
+ key_notice=$(_hosted_key_notice)
144
+ [ -z "$key_notice" ] || printf '%s\n' "$key_notice"
145
+ # Hosted services stay out of reach for this person until the key renews.
146
+ [ -z "$recovery_notice" ] || local_only=1
147
+ case "$key_notice" in *"need a fresh key"*) local_only=1 ;; esac
69
148
  _health_footer_check github "${HEALTH_GITHUB:-skip}" || failed=1
70
149
  _health_footer_check git "${HEALTH_GIT:-skip}" || failed=1
71
150
  _health_footer_check memory "${HEALTH_MEMORY:-skip}" || failed=1
@@ -87,7 +166,9 @@ _render_health_footer() {
87
166
  fi
88
167
 
89
168
  local left right pad
90
- if [ "${FRAMEWORK_UPDATED:-false}" = "true" ]; then
169
+ if [ -n "$local_only" ]; then
170
+ left=" ✓ local checks ready"
171
+ elif [ "${FRAMEWORK_UPDATED:-false}" = "true" ]; then
91
172
  left=" ◆ updated"
92
173
  else
93
174
  left=" ✓ ready"
@@ -7,10 +7,12 @@
7
7
  # 3. Graph person lookup (existing team member on new machine)
8
8
  # 4. Global git config user.name (last resort)
9
9
  #
10
- # Inputs: STATE_FILE, ENV_FILE, SCRIPT_DIR, HEALTH_GITHUB
10
+ # Inputs: STATE_FILE, ENV_FILE, SCRIPT_DIR, HEALTH_GITHUB; optionally
11
+ # EGREGORE_START_HARNESS with EGREGORE_NATIVE_SESSION_ID/_HARNESS
11
12
  # Outputs: AUTHOR, DISPLAY_NAME_STATE, FIRST_SESSION, HEALTH_GITHUB,
12
13
  # EGREGORE_USER, EGREGORE_ORG, EGREGORE_SESSION_ID
13
- # Writes .egregore-session-id and ~/.egregore/session-*.id
14
+ # Writes .egregore-session-id and ~/.egregore/session-*.id, and
15
+ # maps the native session id via bin/session-id.sh
14
16
 
15
17
  STORED_USERNAME=""
16
18
  FIRST_SESSION=""
@@ -192,16 +194,35 @@ fi
192
194
  # --- Export telemetry identity env vars ---
193
195
  export EGREGORE_USER="$AUTHOR"
194
196
  export EGREGORE_ORG="$(jq -r '.slug // .github_org // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null || true)"
195
- export EGREGORE_SESSION_ID="$(date -u +%Y%m%dT%H%M%S)-${AUTHOR}-$$"
196
-
197
- # Persist session ID to file so telemetry.sh can read it
198
- # (env vars from hooks don't propagate into the Claude Code agent)
199
197
  SESSION_ID_DIR="$HOME/.egregore"
200
198
  mkdir -p "$SESSION_ID_DIR"
201
- PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
202
- echo "$EGREGORE_SESSION_ID" > "$SESSION_ID_DIR/session-${PROJ_HASH}.id"
203
- # Also write project-local file (CLAUDE.md reads this — no glob needed)
204
- echo "$EGREGORE_SESSION_ID" > "$SCRIPT_DIR/.egregore-session-id"
199
+ if [ -n "${_EGREGORE_REUSE_SESSION_ID:-}" ]; then
200
+ # Background refresh of a session the quick startup pass already identified
201
+ # (bin/lib/card-delivery.sh): keep its id, and leave the id files and the
202
+ # native mapping as that pass wrote them; a newer session may own them now.
203
+ export EGREGORE_SESSION_ID="$_EGREGORE_REUSE_SESSION_ID"
204
+ unset _EGREGORE_REUSE_SESSION_ID
205
+ else
206
+ export EGREGORE_SESSION_ID="$(date -u +%Y%m%dT%H%M%S)-${AUTHOR}-$$"
207
+
208
+ # Persist session ID to file so telemetry.sh can read it
209
+ # (env vars from hooks don't propagate into the Claude Code agent)
210
+ PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
211
+ echo "$EGREGORE_SESSION_ID" > "$SESSION_ID_DIR/session-${PROJ_HASH}.id"
212
+ # Also write project-local file (CLAUDE.md reads this — no glob needed)
213
+ echo "$EGREGORE_SESSION_ID" > "$SCRIPT_DIR/.egregore-session-id"
214
+ fi
215
+ # Every worktree links that file, so a parallel session start rewrites it
216
+ # under this session. Map the starting harness session's own id to this id
217
+ # (bin/session-id.sh) so its commits keep their own Egregore-Session trailer.
218
+ # Each entry point names the harness it starts; a native id inherited from
219
+ # another session's shell never matches that name.
220
+ if [ -n "${EGREGORE_START_HARNESS:-}" ] && [ -n "${EGREGORE_NATIVE_SESSION_ID:-}" ] \
221
+ && [ "${EGREGORE_NATIVE_HARNESS:-}" = "$EGREGORE_START_HARNESS" ] \
222
+ && [ -f "$SCRIPT_DIR/bin/session-id.sh" ]; then
223
+ bash "$SCRIPT_DIR/bin/session-id.sh" record "$EGREGORE_START_HARNESS" \
224
+ "$EGREGORE_NATIVE_SESSION_ID" "$EGREGORE_SESSION_ID" --root "$SCRIPT_DIR" >/dev/null 2>&1 || true
225
+ fi
205
226
 
206
227
  # Model stamp hashes the git common-dir root because telemetry.sh resolves it that way.
207
228
  MODEL_REPO_ROOT=$(git -C "$SCRIPT_DIR" rev-parse --path-format=absolute --git-common-dir 2>/dev/null | sed 's|/\.git$||') || MODEL_REPO_ROOT="$SCRIPT_DIR"
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env bash
2
+ # instance-registry.sh — session start's maintenance of the instance registry
3
+ # (~/.egregore/instances.json). Sourced by bin/session-start.sh (register +
4
+ # prune) and bin/codex-session-start.sh (prune). Requires jq.
5
+ #
6
+ # egregore_registry_prune <registry>
7
+ # Drop entries that can never be an instance: a checkout inside another
8
+ # registered instance's .egregore/ runtime directory (an evaluation
9
+ # sandbox) is runtime state of that instance.
10
+ # egregore_registry_register <registry> <dir> <slug> <name> [org_id]
11
+ # Register <dir> unless it is already registered or is such runtime
12
+ # state; patch a legacy entry with its org_id.
13
+ #
14
+ # Every write goes through a temporary file and a rename: the boundary check
15
+ # reads an empty or half-written registry as damage and fails closed.
16
+
17
+ # True when $p sits inside another listed path's .egregore/ directory.
18
+ _EGREGORE_REGISTRY_JQ_DEFS='
19
+ def runtime_state($all): . as $p | any($all[]; . as $root | $root != $p and ($p | startswith($root + "/.egregore/")));
20
+ def registered_paths: [.[] | objects | .path | strings];
21
+ def runtime_entry($all): type == "object" and (.path | type) == "string" and (.path | runtime_state($all));
22
+ '
23
+
24
+ _egregore_registry_write() {
25
+ local registry="$1" tmp="$1.tmp.$$"
26
+ shift
27
+ if jq "$@" "$registry" > "$tmp" 2>/dev/null && [ -s "$tmp" ]; then
28
+ mv "$tmp" "$registry"
29
+ else
30
+ rm -f "$tmp"
31
+ return 1
32
+ fi
33
+ }
34
+
35
+ egregore_registry_prune() {
36
+ local registry="$1" pruned
37
+ [ -f "$registry" ] || return 0
38
+ pruned=$(jq "$_EGREGORE_REGISTRY_JQ_DEFS"'
39
+ if type == "array" then registered_paths as $all | [.[] | select(runtime_entry($all))] | length else 0 end' \
40
+ "$registry" 2>/dev/null) || return 0
41
+ [ "${pruned:-0}" -gt 0 ] 2>/dev/null || return 0
42
+ _egregore_registry_write "$registry" "$_EGREGORE_REGISTRY_JQ_DEFS"'
43
+ registered_paths as $all | map(select(runtime_entry($all) | not))'
44
+ }
45
+
46
+ egregore_registry_register() {
47
+ local registry="$1" dir="$2" slug="$3" name="$4" org="${5:-}" entry
48
+ [ -n "$dir" ] && [ -n "$slug" ] && [ -n "$name" ] || return 0
49
+ mkdir -p "$(dirname "$registry")" || return 0
50
+ if [ ! -f "$registry" ]; then
51
+ echo "[]" > "$registry.tmp.$$" && mv "$registry.tmp.$$" "$registry"
52
+ fi
53
+ # Runtime state of another registered instance never registers itself.
54
+ if jq -e --arg p "$dir" "$_EGREGORE_REGISTRY_JQ_DEFS"'registered_paths as $all | $p | runtime_state($all)' \
55
+ "$registry" >/dev/null 2>&1; then
56
+ return 0
57
+ fi
58
+ if [ "$(jq --arg p "$dir" '[.[] | objects | select(.path == $p)] | length' "$registry" 2>/dev/null)" = "0" ]; then
59
+ # org_id is the stable identity other sessions use to prove this
60
+ # checkout is same-organization rather than foreign
61
+ entry=$(jq -n --arg s "$slug" --arg n "$name" --arg p "$dir" --arg o "$org" \
62
+ '{slug: $s, name: $n, path: $p} + (if $o == "" then {} else {org_id: $o} end)')
63
+ _egregore_registry_write "$registry" --argjson e "$entry" '. + [$e]'
64
+ elif [ -n "$org" ]; then
65
+ # Backward compatibility: patch this checkout's own legacy entry with its
66
+ # org_id so other sessions can prove identity instead of failing closed
67
+ # against it
68
+ _egregore_registry_write "$registry" --arg p "$dir" --arg o "$org" \
69
+ 'map(if type == "object" and .path == $p and ((.org_id // "") == "") then . + {org_id: $o} else . end)'
70
+ fi
71
+ }
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env python3
2
+ """Bind caller-recorded local checks to a PR commit while hosted CI is unavailable.
3
+
4
+ generate --records FILE accepts a JSON array of {suite, command, exit_code, log}.
5
+ It copies logs beside the receipt and hashes them. validate checks identity,
6
+ Git trees and retained log integrity; it does not execute commands or certify
7
+ that the caller actually ran them. Authorization belongs to the merge caller.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import hashlib
13
+ import json
14
+ import os
15
+ from pathlib import Path
16
+ import re
17
+ import stat
18
+ import subprocess
19
+ import sys
20
+
21
+ SCHEMA = "local-ci/v1"
22
+ REASON = "GitHub Actions credits exhausted"
23
+ MAX_JSON = 1024 * 1024
24
+ MAX_LOG = 64 * 1024 * 1024
25
+ SHA = re.compile(r"[0-9a-f]{40}")
26
+ DIGEST = re.compile(r"[0-9a-f]{64}")
27
+
28
+
29
+ class Invalid(ValueError):
30
+ pass
31
+
32
+
33
+ def require(condition, message):
34
+ if not condition:
35
+ raise Invalid(message)
36
+
37
+
38
+ def read_file(path, limit):
39
+ """Read a bounded regular file without following its final symlink."""
40
+ fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK | getattr(os, "O_NOFOLLOW", 0))
41
+ with os.fdopen(fd, "rb") as stream:
42
+ info = os.fstat(stream.fileno())
43
+ require(stat.S_ISREG(info.st_mode) and info.st_size <= limit, "invalid evidence file")
44
+ value = stream.read(limit + 1)
45
+ require(len(value) <= limit, "evidence file exceeds limit")
46
+ return value
47
+
48
+
49
+ def read_log(root, relative):
50
+ """A receipt can read regular log descendants only, without symlink hops."""
51
+ require(isinstance(relative, str) and relative, "missing retained log path")
52
+ path = Path(relative)
53
+ require(path.parts and not path.is_absolute() and all(p not in (".", "..") for p in path.parts),
54
+ "retained log must stay beside the receipt")
55
+ fd = os.open(root, os.O_RDONLY | os.O_DIRECTORY)
56
+ try:
57
+ for part in path.parts[:-1]:
58
+ child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | getattr(os, "O_NOFOLLOW", 0), dir_fd=fd)
59
+ os.close(fd)
60
+ fd = child
61
+ leaf = os.open(path.parts[-1], os.O_RDONLY | os.O_NONBLOCK | getattr(os, "O_NOFOLLOW", 0), dir_fd=fd)
62
+ with os.fdopen(leaf, "rb") as stream:
63
+ info = os.fstat(stream.fileno())
64
+ require(stat.S_ISREG(info.st_mode) and info.st_size <= MAX_LOG, "invalid retained log")
65
+ content = stream.read(MAX_LOG + 1)
66
+ require(len(content) <= MAX_LOG, "retained log exceeds limit")
67
+ return content
68
+ finally:
69
+ os.close(fd)
70
+
71
+
72
+ def document(path):
73
+ value = json.loads(read_file(path, MAX_JSON))
74
+ require(isinstance(value, dict), "receipt must be an object")
75
+ return value
76
+
77
+
78
+ def git(checkout, *args, failure="Git identity is unavailable"):
79
+ env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
80
+ env.update(GIT_OPTIONAL_LOCKS="0", GIT_CONFIG_NOSYSTEM="1", GIT_NO_REPLACE_OBJECTS="1")
81
+ result = subprocess.run(["git", "-c", "core.fsmonitor=false", "-C", str(checkout), *args], env=env,
82
+ capture_output=True, text=True, timeout=15)
83
+ require(result.returncode == 0, failure)
84
+ return result.stdout.strip()
85
+
86
+
87
+ def text(value, label, limit=4096):
88
+ require(isinstance(value, str) and 0 < len(value) <= limit
89
+ and all(ord(c) >= 32 or c in "\n\t" for c in value), f"invalid {label}")
90
+ return value
91
+
92
+
93
+ def identity(row):
94
+ require(row.get("schema") == SCHEMA, "unsupported local CI receipt schema")
95
+ require(isinstance(row.get("repo"), str) and
96
+ re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", row["repo"]), "invalid repository")
97
+ require(type(row.get("pr")) is int and row["pr"] > 0, "invalid PR number")
98
+ for key in ("head", "base_sha", "tree_sha"):
99
+ require(isinstance(row.get(key), str) and SHA.fullmatch(row[key]), f"invalid {key}")
100
+ text(row.get("base"), "base ref", 200)
101
+ require("\n" not in row["base"] and "\t" not in row["base"] and not row["base"].startswith("-"),
102
+ "invalid base ref")
103
+ text(row.get("checkout"), "checkout")
104
+ require(Path(row["checkout"]).is_absolute(), "checkout must be absolute")
105
+ require(row.get("unavailable_reason") == REASON, "unsupported hosted CI exception")
106
+
107
+
108
+ def validate(path, *, repo, pr, head, base, base_sha):
109
+ row = document(path)
110
+ identity(row)
111
+ for key, wanted in (("repo", repo), ("pr", pr), ("head", head), ("base", base), ("base_sha", base_sha)):
112
+ require(row[key] == wanted, f"receipt {key} does not match current PR")
113
+ require(git(row["checkout"], "rev-parse", "--verify", row["head"] + "^{tree}") == row["tree_sha"],
114
+ "tested tree does not match PR head")
115
+ require(git(row["checkout"], "rev-parse", "--verify", row["base_sha"] + "^{commit}") == row["base_sha"],
116
+ "base commit is unavailable")
117
+ git(row["checkout"], "merge-base", "--is-ancestor", row["base_sha"], row["head"],
118
+ failure="tested head does not contain the current base")
119
+ suites = row.get("required_suites")
120
+ require(isinstance(suites, list) and 0 < len(suites) <= 100, "required suites are missing")
121
+ for suite in suites:
122
+ text(suite, "suite name", 200)
123
+ require(len(set(suites)) == len(suites), "required suites are duplicated")
124
+ results = row.get("results")
125
+ require(isinstance(results, list) and len(results) == len(suites), "required suite results are incomplete")
126
+ seen = set()
127
+ for result in results:
128
+ require(isinstance(result, dict), "invalid suite result")
129
+ suite = result.get("suite")
130
+ require(isinstance(suite, str) and suite in suites and suite not in seen,
131
+ "unexpected or duplicate suite result")
132
+ seen.add(suite)
133
+ text(result.get("command"), "recorded command", 16384)
134
+ require(type(result.get("exit_code")) is int and result["exit_code"] == 0,
135
+ f"required suite did not pass: {suite}")
136
+ require(isinstance(result.get("sha256"), str) and DIGEST.fullmatch(result["sha256"]),
137
+ "invalid log digest")
138
+ log = read_log(Path(path).resolve().parent, result.get("log"))
139
+ require(hashlib.sha256(log).hexdigest() == result["sha256"], f"log changed: {suite}")
140
+ failed = row.get("failed_checks")
141
+ require(isinstance(failed, list) and len(failed) <= 100, "failed_checks must be a list")
142
+ named = set()
143
+ for check in failed:
144
+ require(isinstance(check, dict), "invalid hosted check explanation")
145
+ name = text(check.get("name"), "hosted check name", 300)
146
+ text(check.get("reason"), "hosted check reason")
147
+ require(name not in named, "duplicate hosted check explanation")
148
+ named.add(name)
149
+ return row
150
+
151
+
152
+ def quoted(value):
153
+ """Single-line Markdown code, including hostile backticks, without HTML."""
154
+ value = " ".join(str(value).split())
155
+ runs = re.findall(r"`+", value)
156
+ fence = "`" * (1 + max(map(len, runs), default=0))
157
+ return f"{fence} {value} {fence}"
158
+
159
+
160
+ def markdown(row):
161
+ lines = ["## Local CI receipt", "",
162
+ f"Repository {quoted(row['repo'])}, PR #{row['pr']}.",
163
+ f"Head {quoted(row['head'])}; base {quoted(row['base'])} at {quoted(row['base_sha'])}.",
164
+ f"Tested tree {quoted(row['tree_sha'])}.",
165
+ f"Caller-reported hosted CI exception: {REASON}.", "",
166
+ "The caller recorded these checks. Validation verified commit identity and retained log hashes; "
167
+ "it did not independently execute or attest the commands. The receipt does not grant merge authorization.",
168
+ ""]
169
+ for result in row["results"]:
170
+ lines.append(f"- {quoted(result['suite'])}: recorded exit 0; {quoted(result['command'])}; "
171
+ f"log SHA256 {quoted(result['sha256'])}.")
172
+ if row["failed_checks"]:
173
+ lines.extend(["", "Hosted checks covered by this local exception:", ""])
174
+ for check in row["failed_checks"]:
175
+ lines.append(f"- {quoted(check['name'])}: {quoted(check['reason'])}.")
176
+ return "\n".join(lines) + "\n"
177
+
178
+
179
+ def generate(args):
180
+ checkout = Path(args.checkout).resolve()
181
+ tree = args.tree
182
+ if tree is None:
183
+ require(git(checkout, "rev-parse", "HEAD") == args.head, "checkout is not at the tested head")
184
+ require(not git(checkout, "status", "--porcelain", "--untracked-files=no"), "tracked checkout is dirty")
185
+ tree = git(checkout, "rev-parse", "HEAD^{tree}")
186
+ row = {"schema": SCHEMA, "repo": args.repo, "pr": args.pr, "head": args.head,
187
+ "base": args.base, "base_sha": args.base_sha, "tree_sha": tree,
188
+ "checkout": str(checkout), "unavailable_reason": args.unavailable_reason,
189
+ "required_suites": args.required_suite, "results": [],
190
+ "failed_checks": json.loads(read_file(args.failed_checks, MAX_JSON)) if args.failed_checks else []}
191
+ identity(row)
192
+ records = json.loads(read_file(args.records, MAX_JSON))
193
+ require(isinstance(records, list), "records must be a JSON array")
194
+ output = Path(args.output).absolute()
195
+ require(not output.exists(), "receipt output already exists")
196
+ output.parent.mkdir(parents=True, exist_ok=True)
197
+ log_root = output.parent / (output.name + ".logs")
198
+ log_root.mkdir(mode=0o700)
199
+ for index, result in enumerate(records):
200
+ require(isinstance(result, dict), "invalid caller record")
201
+ content = read_file(Path(args.records).resolve().parent / result["log"], MAX_LOG)
202
+ target = log_root / f"{index + 1:03d}.log"
203
+ with target.open("xb") as stream:
204
+ stream.write(content)
205
+ target.chmod(0o600)
206
+ row["results"].append({key: result.get(key) for key in ("suite", "command", "exit_code")} |
207
+ {"log": str(target.relative_to(output.parent)),
208
+ "sha256": hashlib.sha256(content).hexdigest()})
209
+ with output.open("x", encoding="utf-8") as stream:
210
+ json.dump(row, stream, indent=2)
211
+ stream.write("\n")
212
+ output.chmod(0o600)
213
+ validate(output, repo=args.repo, pr=args.pr, head=args.head, base=args.base, base_sha=args.base_sha)
214
+ return output
215
+
216
+
217
+ def main(argv=None):
218
+ parser = argparse.ArgumentParser(description=__doc__)
219
+ sub = parser.add_subparsers(dest="action", required=True)
220
+ for action in ("generate", "validate"):
221
+ command = sub.add_parser(action)
222
+ for flag in ("repo", "head", "base", "base-sha"):
223
+ command.add_argument("--" + flag, required=True)
224
+ command.add_argument("--pr", type=int, required=True)
225
+ if action == "validate":
226
+ command.add_argument("--receipt", type=Path, required=True)
227
+ else:
228
+ command.add_argument("--output", type=Path, required=True)
229
+ command.add_argument("--checkout", type=Path, required=True)
230
+ command.add_argument("--records", type=Path, required=True)
231
+ command.add_argument("--required-suite", action="append", required=True)
232
+ command.add_argument("--unavailable-reason", required=True)
233
+ command.add_argument("--failed-checks", type=Path, help="JSON array of hosted {name, reason} explanations")
234
+ command.add_argument("--tree", help="explicit tested Git tree, for checks run before committing that tree")
235
+ args = parser.parse_args(argv)
236
+ try:
237
+ if args.action == "generate":
238
+ print(generate(args))
239
+ else:
240
+ print(markdown(validate(args.receipt, repo=args.repo, pr=args.pr, head=args.head,
241
+ base=args.base, base_sha=args.base_sha)), end="")
242
+ except (Invalid, OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError) as error:
243
+ print(f"local-ci: {error}", file=sys.stderr)
244
+ return 1
245
+ return 0
246
+
247
+
248
+ if __name__ == "__main__":
249
+ raise SystemExit(main())