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,56 @@
1
+ """Admin-only entries refuse a non-admin grant before they read anything.
2
+
3
+ Corpus and data-room builders and hosted-index maintenance read or hand over
4
+ memory wholesale, outside the per-path predicate, so they are admin-only
5
+ channels: the entry asks the one policy whether this session's read grant is
6
+ an admin's and stops otherwise. There is no operator-only class; running in
7
+ your own checkout is not a grant. Shell and Node entries run the
8
+ ``egregore_runtime.admin_entry`` module with the surface name (exit 5 on refusal).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ REFUSED = 5
18
+
19
+
20
+ def refusal(surface: str) -> str:
21
+ return (f"{surface} is for organization admins, and this session's grant is not an admin's; "
22
+ "nothing was read. Admins are the GitHub handles listed under `admins` in egregore.json.")
23
+
24
+
25
+ def admin_session(root: Path | str | None = None, harness: str | None = None) -> bool:
26
+ """True only when the policy names this session's actor an admin; fails closed."""
27
+ from .contracts import Permission
28
+ from .policy import is_admin
29
+ from .runtime import local_runtime, runtime_root
30
+
31
+ try:
32
+ runtime = local_runtime(runtime_root(root), push_remote=False)
33
+ actor = runtime.resolve_actor(session_id=os.environ.get("EGREGORE_SESSION_ID") or "admin-entry",
34
+ harness=harness or os.environ.get("EGREGORE_RUNTIME") or "shell")
35
+ return is_admin(runtime.authorize(actor, Permission.READ))
36
+ except Exception:
37
+ return False
38
+
39
+
40
+ def require_admin(surface: str, root: Path | str | None = None) -> None:
41
+ if not admin_session(root):
42
+ print(refusal(surface), file=sys.stderr)
43
+ raise SystemExit(REFUSED)
44
+
45
+
46
+ def main(argv: list[str] | None = None) -> int:
47
+ args = sys.argv[1:] if argv is None else argv
48
+ surface = args[0] if args else "This command"
49
+ if admin_session():
50
+ return 0
51
+ print(refusal(surface), file=sys.stderr)
52
+ return REFUSED
53
+
54
+
55
+ if __name__ == "__main__":
56
+ raise SystemExit(main())
@@ -18,12 +18,14 @@ import unicodedata
18
18
  from dataclasses import dataclass, field, replace
19
19
  from datetime import UTC, date, datetime
20
20
  from pathlib import Path, PurePosixPath
21
- from typing import Any, Mapping, Protocol, runtime_checkable
21
+ from typing import Any, Callable, Mapping, Protocol, runtime_checkable
22
22
 
23
- from .contracts import ArtifactRelationship, CanonicalArtifact, SourceProvenance
23
+ from .contracts import ActorKind, ArtifactRelationship, CanonicalArtifact, ExecutionAuthority, SourceProvenance
24
24
 
25
25
 
26
26
  ARTIFACT_SCHEMA_VERSION = "egregore-artifact/v1"
27
+ # Names frontmatter keys whose block value the reader could not represent.
28
+ UNPARSED_FRONTMATTER = "unparsed frontmatter"
27
29
  _FRONTMATTER_KEY = re.compile(r"^([A-Za-z][A-Za-z0-9_-]*):(?:[ \t]*(.*))?$")
28
30
  # Existing hosted artifact ids are URL-safe opaque tokens and may begin with
29
31
  # ``-`` or ``_``. Keep those stable during explicit v1 migration.
@@ -54,6 +56,9 @@ class CanonicalDocument:
54
56
  replaces_existing: bool = False
55
57
  # Optimistic concurrency for a mutable lifecycle update; never serialized.
56
58
  expected_revision: str | None = None
59
+ # Stricter optimistic concurrency: the SHA-256 of the stored file's full
60
+ # bytes (header included), checked under the write lock; never serialized.
61
+ expected_sha256: str | None = None
57
62
 
58
63
 
59
64
  @runtime_checkable
@@ -156,33 +161,136 @@ def _parse_scalar(raw: str) -> Any:
156
161
  return value
157
162
 
158
163
 
159
- def split_frontmatter(markdown: str) -> tuple[dict[str, Any], str]:
160
- """Parse the deterministic JSON-in-YAML subset used by v1 and legacy scalars.
164
+ class _Unsupported(Exception):
165
+ """A frontmatter block outside the supported YAML subset."""
166
+
167
+
168
+ def _indent(line: str) -> int:
169
+ return len(line) - len(line.lstrip(" "))
170
+
171
+
172
+ def _dash(text: str) -> bool:
173
+ return text == "-" or text.startswith("- ")
174
+
175
+
176
+ def _block_scalar(raw: str) -> Any:
177
+ """A scalar inside a block. A plain value carrying a YAML comment is refused
178
+ rather than read with the comment as data (``- oz # owner``)."""
179
+
180
+ if raw.startswith("#") or " #" in raw or "\t#" in raw: # YAML: whitespace, then "#"
181
+ try:
182
+ return json.loads(raw)
183
+ except json.JSONDecodeError:
184
+ raise _Unsupported from None
185
+ return _parse_scalar(raw)
186
+
187
+
188
+ def _block(lines: list[str], index: int, indent: int) -> tuple[Any, int]:
189
+ """One block-style YAML value at ``indent``: a list of scalars or a mapping."""
190
+
191
+ if _dash(lines[index][indent:]):
192
+ items = []
193
+ while index < len(lines) and _indent(lines[index]) == indent and _dash(lines[index][indent:]):
194
+ item = lines[index][indent + 1:].strip()
195
+ index += 1
196
+ if not item or (index < len(lines) and _indent(lines[index]) > indent):
197
+ raise _Unsupported # an empty or nested list item
198
+ items.append(_block_scalar(item))
199
+ return items, index
200
+ mapping: dict[str, Any] = {}
201
+ while index < len(lines) and _indent(lines[index]) == indent and not _dash(lines[index][indent:]):
202
+ match = _FRONTMATTER_KEY.match(lines[index][indent:])
203
+ if match is None or match.group(1) in mapping:
204
+ raise _Unsupported
205
+ key, raw = match.group(1), (match.group(2) or "").strip()
206
+ index += 1
207
+ child = _indent(lines[index]) if index < len(lines) else -1
208
+ nested = child > indent or (child == indent and _dash(lines[index][indent:]))
209
+ if raw:
210
+ if nested or raw[:1] in {"|", ">"}:
211
+ raise _Unsupported # a multi-line scalar
212
+ mapping[key] = _block_scalar(raw)
213
+ elif nested:
214
+ mapping[key], index = _block(lines, index, child)
215
+ else:
216
+ mapping[key] = None
217
+ if index < len(lines) and _indent(lines[index]) > indent:
218
+ raise _Unsupported
219
+ return mapping, index
161
220
 
162
- JSON values are valid YAML and let the runtime round-trip nested provenance
163
- without a YAML dependency. Unknown indented legacy fields are ignored and
164
- preserved in the original file until an explicit migration/write occurs.
165
- """
166
221
 
222
+ def _parse_frontmatter(markdown: str) -> tuple[dict[str, Any], str, tuple[str, ...]]:
167
223
  normalized = markdown.replace("\r\n", "\n").replace("\r", "\n")
168
224
  if not normalized.startswith("---\n"):
169
- return {}, normalized
225
+ return {}, normalized, ()
170
226
  closing = normalized.find("\n---\n", 4)
171
227
  if closing < 0:
172
228
  raise ArtifactSchemaError("incomplete frontmatter block")
173
- raw_header = normalized[4:closing]
229
+ # Only a comment at column 0 is dropped: inside a block, a "#" line may be
230
+ # content (a literal block), so it stays and makes that block unsupported.
231
+ numbered = [(number, line) for number, line in enumerate(normalized[4:closing].splitlines(), 2)
232
+ if line.strip() and not line.startswith("#")]
233
+ lines = [line for _, line in numbered]
174
234
  result: dict[str, Any] = {}
175
- for line_number, line in enumerate(raw_header.splitlines(), 2):
176
- if not line.strip() or line.lstrip().startswith("#") or line[:1].isspace():
235
+ unparsed: list[str] = []
236
+ index = 0
237
+ while index < len(lines):
238
+ line = lines[index]
239
+ if line[:1].isspace():
240
+ index += 1 # an indented line no key above it could own
177
241
  continue
178
242
  match = _FRONTMATTER_KEY.match(line)
179
243
  if match is None:
180
- raise ArtifactSchemaError(f"invalid frontmatter line {line_number}")
244
+ raise ArtifactSchemaError(f"invalid frontmatter line {numbered[index][0]}")
181
245
  key, raw = match.groups()
182
246
  if key in result:
183
247
  raise ArtifactSchemaError(f"duplicate frontmatter key: {key}")
184
248
  result[key] = _parse_scalar(raw or "")
185
- return result, normalized[closing + 5 :]
249
+ index += 1
250
+ if index < len(lines) and lines[index][:1].isspace():
251
+ # A block value under this key. Keep the one-line reading when the
252
+ # block is outside the subset, and name the key so no write drops it.
253
+ following = index
254
+ while following < len(lines) and lines[following][:1].isspace():
255
+ following += 1
256
+ if (raw or "").strip():
257
+ unparsed.append(key)
258
+ else:
259
+ try:
260
+ value, stop = _block(lines, index, _indent(lines[index]))
261
+ if stop != following:
262
+ raise _Unsupported
263
+ result[key] = value
264
+ except _Unsupported:
265
+ unparsed.append(key)
266
+ index = following
267
+ return result, normalized[closing + 5 :], tuple(unparsed)
268
+
269
+
270
+ def split_frontmatter(markdown: str) -> tuple[dict[str, Any], str]:
271
+ """Parse the deterministic JSON-in-YAML subset used by v1 and legacy records.
272
+
273
+ JSON values are valid YAML and let the runtime round-trip nested provenance
274
+ without a YAML dependency. Legacy block values (an indented list of
275
+ scalars, or a mapping of them) are read too. Anything else under a key
276
+ keeps its one-line reading; ``unparsed_frontmatter`` names those keys so a
277
+ write never drops them.
278
+ """
279
+
280
+ fields, body, _ = _parse_frontmatter(markdown)
281
+ return fields, body
282
+
283
+
284
+ def unparsed_frontmatter(markdown: str) -> tuple[str, ...]:
285
+ """Keys whose block value is outside the supported subset."""
286
+
287
+ return _parse_frontmatter(markdown)[2]
288
+
289
+
290
+ def _refuse_unparsed(warnings: tuple[str, ...]) -> None:
291
+ unread = next((note for note in warnings if note.startswith(UNPARSED_FRONTMATTER)), None)
292
+ if unread:
293
+ raise ArtifactSchemaError(f"{unread}; rewriting this record would drop them")
186
294
 
187
295
 
188
296
  def _as_strings(value: Any) -> tuple[str, ...]:
@@ -263,6 +371,11 @@ def _infer_type(fields: Mapping[str, Any], canonical_path: str) -> str:
263
371
  return "artifact"
264
372
 
265
373
 
374
+ def plain_revision(markdown: str) -> str:
375
+ """Version of a plain canonical file: a hash of its exact text."""
376
+ return "sha256:" + hashlib.sha256(markdown.encode("utf-8")).hexdigest()[:16]
377
+
378
+
266
379
  def store_managed_reason(markdown: str) -> str | None:
267
380
  """Say why raw Markdown belongs to the artifact store, or None if it does not.
268
381
 
@@ -303,6 +416,34 @@ def _relationships(value: Any) -> tuple[ArtifactRelationship, ...]:
303
416
  return tuple(rows)
304
417
 
305
418
 
419
+ def _execution_provenance(value: Any) -> ExecutionAuthority | None:
420
+ """Read bounded historical attribution, never usable as a credential."""
421
+ if value is None:
422
+ return None
423
+ text_fields = (
424
+ "executor_actor_id", "worker_id", "member_account_id", "member_actor_id",
425
+ "membership_id", "org_id", "grant_id", "binding_id", "audience", "revision",
426
+ )
427
+ if not isinstance(value, Mapping) or set(value) != {*text_fields, "executor_kind", "expires_at"}:
428
+ raise ArtifactSchemaError("invalid execution provenance")
429
+ if any(not isinstance(value[key], str) or not re.fullmatch(r"[A-Za-z0-9._:-]{1,160}", value[key])
430
+ for key in text_fields):
431
+ raise ArtifactSchemaError("invalid execution provenance reference")
432
+ try:
433
+ kind = ActorKind(value["executor_kind"])
434
+ if kind not in {ActorKind.AGENT, ActorKind.SERVICE}:
435
+ raise ValueError("invalid executor kind")
436
+ expires = datetime.fromisoformat(str(value["expires_at"]).replace("Z", "+00:00"))
437
+ if expires.tzinfo is None:
438
+ raise ValueError("timezone required")
439
+ except (ValueError, TypeError):
440
+ raise ArtifactSchemaError("invalid execution provenance") from None
441
+ return ExecutionAuthority(
442
+ **{key: value[key] for key in text_fields},
443
+ executor_kind=kind, expires_at=expires,
444
+ )
445
+
446
+
306
447
  def _provenance(value: Any) -> tuple[SourceProvenance, ...]:
307
448
  if value is None:
308
449
  return ()
@@ -326,6 +467,7 @@ def _provenance(value: Any) -> tuple[SourceProvenance, ...]:
326
467
  observed_at=_datetime(item["observed_at"], field_name="provenance.observed_at"),
327
468
  source_uri=str(item["source_uri"]) if item.get("source_uri") else None,
328
469
  imported_by=str(item["imported_by"]) if item.get("imported_by") else None,
470
+ execution=_execution_provenance(item.get("execution")),
329
471
  )
330
472
  )
331
473
  return tuple(rows)
@@ -356,6 +498,11 @@ def validate_artifact(artifact: CanonicalArtifact, body: str) -> CanonicalArtifa
356
498
  raise ArtifactSchemaError("content_hash does not match the normalized Markdown body")
357
499
  if any(not row.relation or not row.target_id for row in artifact.relationships):
358
500
  raise ArtifactSchemaError("relationships require relation and target_id")
501
+ for source in artifact.provenance:
502
+ if source.execution is not None:
503
+ if not isinstance(source.execution, ExecutionAuthority):
504
+ raise ArtifactSchemaError("invalid execution provenance")
505
+ _execution_provenance(source.execution.to_dict())
359
506
  return replace(
360
507
  artifact,
361
508
  canonical_path=canonical_path,
@@ -382,7 +529,7 @@ def parse_canonical_markdown(
382
529
  """
383
530
 
384
531
  path = canonical_relative_path(canonical_path)
385
- fields, raw_body = split_frontmatter(markdown)
532
+ fields, raw_body, unparsed = _parse_frontmatter(markdown)
386
533
  body = normalize_markdown_body(raw_body)
387
534
  schema = fields.get("schema_version") or fields.get("schema")
388
535
  is_v1 = schema == ARTIFACT_SCHEMA_VERSION
@@ -406,6 +553,7 @@ def parse_canonical_markdown(
406
553
  or fields.get("date")
407
554
  or fields.get("ingested_at")
408
555
  or fields.get("started")
556
+ or fields.get("created")
409
557
  or (default_created_at if not is_v1 else None)
410
558
  )
411
559
  if created_raw is None:
@@ -482,6 +630,8 @@ def parse_canonical_markdown(
482
630
  if key in fields:
483
631
  policy_hints[key] = fields[key]
484
632
  warnings = () if is_v1 else ("legacy frontmatter migrated in memory; source file was not rewritten",)
633
+ if unparsed:
634
+ warnings += (f"{UNPARSED_FRONTMATTER}: {', '.join(unparsed)}",)
485
635
  return CanonicalDocument(
486
636
  artifact=artifact,
487
637
  body=body,
@@ -533,7 +683,7 @@ class LocalMarkdownArtifactStore:
533
683
  root: Path,
534
684
  *,
535
685
  mutable_types: frozenset[str] = frozenset(
536
- {"scroll", "thread", "question", "quest", "todo", "issue"}
686
+ {"scroll", "thread", "question", "quest", "todo", "issue", "design"}
537
687
  ),
538
688
  ):
539
689
  self.root = root.resolve()
@@ -567,8 +717,18 @@ class LocalMarkdownArtifactStore:
567
717
  # stale digest, decides whether that replacement is legitimate.
568
718
  recompute_digest=document.replaces_existing,
569
719
  )
720
+ identical = (artifact.artifact_type not in self.mutable_types and not document.replaces_existing
721
+ and existing.artifact.content_hash == artifact.content_hash and document.migrated_from is None)
722
+ if not identical:
723
+ # The reader keeps only a one-line reading of an unparsed key,
724
+ # so replacing a stored file that has one would drop the rest.
725
+ # Refusing here, in the check, stops a batch before any write.
726
+ _refuse_unparsed(existing.warnings)
570
727
  if document.expected_revision is not None and existing.artifact.revision != document.expected_revision:
571
728
  raise ArtifactConflictError("canonical source revision changed; reopen before retrying")
729
+ if (document.expected_sha256 is not None
730
+ and hashlib.sha256(target.read_bytes()).hexdigest() != document.expected_sha256):
731
+ raise ArtifactConflictError("canonical source changed; reopen before retrying")
572
732
  if existing.artifact.artifact_id != artifact.artifact_id:
573
733
  raise ArtifactConflictError("artifact path belongs to a different stable id")
574
734
  if artifact.artifact_type not in self.mutable_types and not document.replaces_existing:
@@ -579,6 +739,7 @@ class LocalMarkdownArtifactStore:
579
739
  return target
580
740
  else:
581
741
  raise ArtifactConflictError(f"{artifact.artifact_type} artifacts are append-only")
742
+ _refuse_unparsed(document.warnings) # a document built from such a read drops them too
582
743
  return target
583
744
 
584
745
  @contextmanager
@@ -612,6 +773,51 @@ class LocalMarkdownArtifactStore:
612
773
  raise ArtifactConflictError("canonical source changed during identity transfer")
613
774
  return self._persist(document)
614
775
 
776
+ def replace_plain_if_unchanged(self, canonical_path: str, expected_revision: str, text: str) -> Path:
777
+ """Replace a plain canonical Markdown file while its bytes still match.
778
+
779
+ For files that carry no artifact envelope (people pages, ledgers, older
780
+ notes). ``expected_revision`` is ``sha256:`` plus the first 16 hex of the
781
+ current file. A file the store manages is refused: those go through a
782
+ document write so their envelope stays valid.
783
+ """
784
+ with self._write_lock(canonical_path):
785
+ target = self._target(canonical_path)
786
+ if not target.is_file():
787
+ raise ArtifactConflictError("canonical source disappeared; reopen before retrying")
788
+ current = target.read_text(encoding="utf-8")
789
+ if plain_revision(current) != expected_revision:
790
+ raise ArtifactConflictError("canonical source revision changed; reopen before retrying")
791
+ for markdown in (current, text):
792
+ reason = store_managed_reason(markdown)
793
+ if reason is not None:
794
+ raise ArtifactConflictError(f"this file is store-managed ({reason}); write it as a document")
795
+ temporary = target.with_name(f".{target.name}.{os.getpid()}.tmp")
796
+ temporary.write_text(text, encoding="utf-8")
797
+ temporary.replace(target)
798
+ return target
799
+
800
+ def restore_if(self, canonical_path: str, previous: str, holds: Callable[[str], bool]) -> bool:
801
+ """Put ``previous`` back while the file still holds a write nothing versioned.
802
+
803
+ ``holds`` says whether the current text is that write. Under the same
804
+ lock as every store write. True when the file holds ``previous`` again
805
+ (or never left it); False when it moved on, so nothing was touched.
806
+ """
807
+ with self._write_lock(canonical_path):
808
+ target = self._target(canonical_path)
809
+ if not target.is_file():
810
+ return False
811
+ current = target.read_text(encoding="utf-8")
812
+ if current == previous:
813
+ return True
814
+ if not holds(current):
815
+ return False
816
+ temporary = target.with_name(f".{target.name}.{os.getpid()}.tmp")
817
+ temporary.write_text(previous, encoding="utf-8")
818
+ temporary.replace(target)
819
+ return True
820
+
615
821
  def _persist(self, document: CanonicalDocument) -> Path:
616
822
  target = self.check_write(document)
617
823
  artifact = validate_artifact(document.artifact, document.body)
@@ -204,6 +204,14 @@ class PolicyDecision(Serializable):
204
204
  resource_ids: tuple[str, ...] = ()
205
205
  scopes: tuple[str, ...] = ()
206
206
  reasons: tuple[str, ...] = ()
207
+ audience: str = "member"
208
+ scope: str = "read"
209
+ read_prefixes: tuple[str, ...] = ()
210
+ write_prefixes: tuple[str, ...] = ()
211
+ append_prefixes: tuple[str, ...] = ()
212
+ holds_epoch: str = ""
213
+ released: tuple[str, ...] = ()
214
+ memory_revision: str = ""
207
215
 
208
216
 
209
217
  @dataclass(frozen=True, slots=True)
@@ -550,6 +558,8 @@ class ArtifactWriteback(Protocol):
550
558
  self,
551
559
  actor: ActorContext,
552
560
  document: "CanonicalDocument",
561
+ *,
562
+ message: str | None = None,
553
563
  ) -> WritebackEvent: ...
554
564
 
555
565
  def write_documents(
@@ -559,12 +569,29 @@ class ArtifactWriteback(Protocol):
559
569
  ) -> Sequence[WritebackEvent]: ...
560
570
 
561
571
 
572
+ class CommitMessage(str):
573
+ """A commit's subject (and body) that names its own trailers, as ``Key: value`` lines.
574
+
575
+ It is a string, so every transport can commit it as text. The Git transport
576
+ writes ``trailers`` as the message's one trailer paragraph and adds only the
577
+ provenance trailers they do not name. The caller keeps every value on one line.
578
+ """
579
+
580
+ trailers: tuple[str, ...]
581
+
582
+ def __new__(cls, text: str, trailers: Sequence[str] = ()) -> "CommitMessage":
583
+ made = super().__new__(cls, text)
584
+ made.trailers = tuple(trailers)
585
+ return made
586
+
587
+
562
588
  @runtime_checkable
563
589
  class SyncTransport(Protocol):
564
590
  def status(self) -> SyncStatus: ...
565
591
 
566
592
  def pull(self) -> SyncStatus: ...
567
593
 
594
+ # ``message`` may be a CommitMessage that carries its own trailers.
568
595
  def push(self, *, message: str, paths: Sequence[str] = ()) -> SyncStatus: ...
569
596
 
570
597
 
@@ -15,7 +15,7 @@ from typing import Any, Callable
15
15
  from .contracts import TemporalScope
16
16
  from .errors import AuthorizationDenied, EgregoreRuntimeError
17
17
  from .lifecycle import AuthorityState
18
- from .policy import scope_allows_path
18
+ from .policy import check
19
19
 
20
20
  ELIGIBILITY_VERSION = 'egregore-eligibility/v1'
21
21
  MAX_DOCUMENTS = 50_000
@@ -51,7 +51,7 @@ class EligibilitySnapshot:
51
51
  'documents': [document.to_wire() for document in self.documents]}
52
52
 
53
53
 
54
- def build_eligibility(retriever, request, *, actor, admin_gate=None, lifecycle=None,
54
+ def build_eligibility(retriever, request, *, actor, decision, lifecycle=None,
55
55
  path_filter: Callable[[str], bool] | None = None,
56
56
  metadata_filter: Callable[[str, Any], bool] | None = None):
57
57
  """Read current scoped files once and fence the resulting view by revision.
@@ -66,20 +66,13 @@ def build_eligibility(retriever, request, *, actor, admin_gate=None, lifecycle=N
66
66
  root = retriever.memory_root.resolve()
67
67
  rows = []
68
68
  warnings = []
69
- is_admin = admin_gate is None or admin_gate.actor_is_admin(actor)
70
69
  for path in sorted(root.rglob('*.md')):
71
70
  canonical = 'memory/' + path.relative_to(root).as_posix()
72
- if not scope_allows_path(canonical, request.authorized_scopes):
73
- continue
74
- resolved = path.resolve()
75
- if not path.is_file() or not resolved.is_relative_to(root):
76
- continue
77
- if not scope_allows_path('memory/' + resolved.relative_to(root).as_posix(), request.authorized_scopes):
71
+ verdict = check(decision, canonical, "read", root=root)
72
+ if not verdict.allowed or not verdict.resolved.is_file():
78
73
  continue
79
74
  if path_filter and not path_filter(canonical):
80
75
  continue
81
- if not is_admin and admin_gate.path_is_admin(canonical):
82
- continue
83
76
  metadata = retriever.describe_source(canonical)
84
77
  if request.allowed_artifact_ids and metadata.artifact_id not in request.allowed_artifact_ids:
85
78
  continue
@@ -98,7 +91,7 @@ def build_eligibility(retriever, request, *, actor, admin_gate=None, lifecycle=N
98
91
  try:
99
92
  lifecycle_by_path, _ = lifecycle.resolve_paths(
100
93
  [row.canonical_path for row in rows], source_revision=revision,
101
- authorized_scopes=request.authorized_scopes,
94
+ decision=decision,
102
95
  allowed_artifact_ids=request.allowed_artifact_ids)
103
96
  except Exception as error:
104
97
  # Preserve Observe's advisory lifecycle fallback, but do not claim
@@ -13,11 +13,11 @@ import fcntl
13
13
  import hashlib
14
14
  import json
15
15
  import os
16
- from pathlib import Path
17
16
  import secrets
18
17
  import time
19
18
 
20
19
  from .errors import EgregoreRuntimeError
20
+ from .installation import installation_root
21
21
 
22
22
  MAX_QUESTION_BYTES = 262144
23
23
 
@@ -25,9 +25,20 @@ class EpisodeBindingError(EgregoreRuntimeError):
25
25
  pass
26
26
 
27
27
 
28
+ def _store(root):
29
+ """One binding store per installation, shared by its main checkout and task worktrees.
30
+
31
+ A prompt is bound from wherever its hook runs (Claude keeps CLAUDE_PROJECT_DIR
32
+ at the launch checkout after the session enters a worktree, and a prompt that
33
+ names a worktree moves the hook there); its tools, a subagent's included, run
34
+ in whichever checkout they stand in. All of them must see one current prompt.
35
+ """
36
+ return installation_root(root) / ".egregore/runtime/bindings"
37
+
38
+
28
39
  def _directory(root):
29
- directory = Path(root) / ".egregore/runtime/bindings"
30
- if not directory.resolve().is_relative_to(Path(root).resolve()):
40
+ directory = _store(root)
41
+ if not directory.resolve().is_relative_to(installation_root(root)):
31
42
  raise EpisodeBindingError("episode bindings must remain inside this instance")
32
43
  directory.mkdir(parents=True, exist_ok=True, mode=0o700)
33
44
  os.chmod(directory, 0o700)
@@ -148,7 +159,7 @@ def resolve(root, episode_id):
148
159
 
149
160
 
150
161
  def requires_binding(root):
151
- directory = Path(root) / ".egregore/runtime/bindings"
162
+ directory = _store(root)
152
163
  return directory.exists() and any(directory.glob("*.current"))
153
164
 
154
165
 
@@ -168,7 +179,7 @@ def resolve_native(root, *, harness, session_id):
168
179
  if not session_id or len(session_id) > 200:
169
180
  raise EpisodeBindingError('native session identity is required')
170
181
  conversation = _key(f'{harness}:{session_id}')
171
- path = Path(root) / '.egregore/runtime/bindings' / (conversation + '.current')
182
+ path = _store(root) / (conversation + '.current')
172
183
  if not path.exists():
173
184
  return None
174
185
  current = _load(path)
@@ -13,7 +13,7 @@ from urllib.parse import urlencode
13
13
  from uuid import uuid4
14
14
 
15
15
  from .contracts import Permission
16
- from .policy import scope_allows_path
16
+ from .policy import allows, action_for
17
17
  from .issue_markers import artifact_url, issue_request_id, observation_id, request_id
18
18
 
19
19
  REPO = re.compile(r"[A-Za-z0-9][A-Za-z0-9-]{0,38}/(?!(?:\.|\.\.)$)[A-Za-z0-9_.-]{1,100}")
@@ -76,7 +76,7 @@ class GitHubIssues:
76
76
  permissions += [Permission.WRITE, Permission.SHARE]
77
77
  for permission in permissions:
78
78
  decision = self.runtime.authorize(self.actor, permission, (f"github:{self.repo}:issues",))
79
- if not decision.allowed or not scope_allows_path("memory/knowledge/issues/", decision.scopes):
79
+ if not decision.allowed or not allows(decision, "memory/knowledge/issues/", action_for(decision.permission), root=self.runtime.canonical_root):
80
80
  raise PermissionError(f"GitHub issues {permission.value} denied or whole issue namespace not authorized")
81
81
 
82
82
  def number(self, reference):
@@ -23,10 +23,10 @@ from pathlib import Path
23
23
  from typing import Any, Iterable
24
24
 
25
25
  from .artifacts import split_frontmatter
26
- from .contracts import ActorContext, Permission
26
+ from .contracts import ActorContext, Permission, PolicyDecision
27
27
  from .errors import AuthorizationDenied
28
28
  from .identity import organization_member_presentations
29
- from .policy import scope_allows_path
29
+ from .policy import check
30
30
  from .questions import actor_aliases
31
31
 
32
32
  _CLOSED_STATUSES = frozenset(
@@ -72,13 +72,13 @@ def _occurred_at(fields: Any, path_stem: str) -> str:
72
72
  return f"0000-00-00T00:00:00Z:{path_stem}"
73
73
 
74
74
 
75
- def _member_terms(root: Path, actor: ActorContext, name: str) -> frozenset[str]:
76
- """Stable-identity terms for a NAMED member (addressing resolution)."""
75
+ def _member_terms(root: Path, actor: ActorContext, name: str, decision: PolicyDecision) -> frozenset[str]:
76
+ """Stable-identity terms for a NAMED member (addressing resolution), from readable profiles."""
77
77
 
78
78
  needle = name.strip().removeprefix("@").casefold()
79
79
  if not needle:
80
80
  return frozenset()
81
- for member in organization_member_presentations(root, actor):
81
+ for member in organization_member_presentations(root, actor, decision=decision):
82
82
  values = {
83
83
  str(member.display_name).casefold(),
84
84
  *(str(alias).casefold() for alias in member.aliases),
@@ -113,16 +113,6 @@ class HandoffLookupService:
113
113
  decision = self.runtime.authorize(actor, Permission.READ)
114
114
  if not decision.allowed:
115
115
  raise AuthorizationDenied("; ".join(decision.reasons))
116
- scopes = tuple(decision.scopes)
117
-
118
- gate = getattr(self.runtime, "admin_gate", None)
119
- admin_visible = True
120
- if gate is not None:
121
- try:
122
- admin_visible = bool(gate.actor_is_admin(actor))
123
- except Exception:
124
- admin_visible = False
125
-
126
116
  recipient_terms = self._terms(actor, addressed_to)
127
117
  sender_terms = self._terms(actor, sent_by)
128
118
 
@@ -135,12 +125,11 @@ class HandoffLookupService:
135
125
  continue
136
126
  relative = path.relative_to(self.memory_root).as_posix()
137
127
  canonical = f"memory/{relative}"
138
- if not scope_allows_path(canonical, scopes):
139
- continue
140
- if not admin_visible and gate is not None and gate.path_is_admin(canonical):
128
+ verdict = check(decision, canonical, "read", root=self.memory_root)
129
+ if not verdict.allowed:
141
130
  continue
142
131
  try:
143
- fields, _ = split_frontmatter(path.read_text(encoding="utf-8"))
132
+ fields, _ = split_frontmatter(verdict.resolved.read_text(encoding="utf-8"))
144
133
  except (OSError, ValueError):
145
134
  continue
146
135
  status = (_scalar(fields, "status") or "active").casefold()
@@ -181,7 +170,7 @@ class HandoffLookupService:
181
170
  return None
182
171
  if who.strip().casefold() in ("me", "self", actor.actor.actor_id.casefold()):
183
172
  return _self_terms(actor)
184
- return _member_terms(self.root, actor, who)
173
+ return _member_terms(self.root, actor, who, self.runtime.authorize(actor, Permission.READ))
185
174
 
186
175
  @staticmethod
187
176
  def _addressed_match(text: str, terms: frozenset[str]) -> bool: