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
@@ -7,16 +7,18 @@ composed here and are not part of the harness contract.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from dataclasses import dataclass
10
+ from dataclasses import dataclass, field
11
11
  from datetime import UTC, datetime
12
12
  import hashlib
13
+ import json
13
14
  import os
14
15
  from pathlib import Path
15
- from typing import Callable, Sequence
16
+ from typing import TYPE_CHECKING, Callable, Sequence
16
17
  from uuid import uuid4
17
18
 
18
19
  from .adapters.qmd import QmdLocalRetriever
19
- from .admin_gate import AdminGate, document_is_admin
20
+ from .held import document_is_admin, redact
21
+ from .access import authorization_inputs
20
22
  from .artifacts import (
21
23
  CanonicalArtifactStore,
22
24
  CanonicalDocument,
@@ -59,12 +61,21 @@ from .lifecycle import (
59
61
  LifecycleRecord,
60
62
  )
61
63
  from .observe import DefaultOrgContextCompiler
64
+ from .observers import MAX_EVENT_PATHS, Observer, RuntimeEvent
62
65
  from .sync_health import grade_retrieval, sync_remedy
63
- from .policy import all_access_dogfood_policy, scope_allows_path
66
+ from .policy import check, is_admin, policy_for
67
+ from .projection import observer as projection_observer
68
+ from .optional_modules import optional_module
69
+
70
+ _record = optional_module(".record")
71
+ record_observer = _record.observer if _record is not None else None
64
72
  from .sync import LocalGitSyncTransport
65
73
  from .telemetry import LocalTelemetrySink, TELEMETRY_SCHEMA_VERSION
66
74
  from .writeback import CanonicalArtifactWriteback
67
75
 
76
+ if TYPE_CHECKING:
77
+ from .member_authority import AuthenticatedMemberResolver
78
+
68
79
 
69
80
  PolicyFactory = Callable[[ActorContext], ActionAuthorizer]
70
81
 
@@ -82,8 +93,20 @@ class EgregoreRuntime:
82
93
  policy_factory: PolicyFactory
83
94
  lifecycle: CanonicalLifecycleService | None = None
84
95
  canonical_root: Path | None = None
85
- admin_gate: AdminGate | None = None
86
96
  instance_root: Path | None = None
97
+ observers: list[Observer] = field(default_factory=list)
98
+
99
+ def _notify(self, kind, actor, paths):
100
+ paths = tuple(dict.fromkeys(path for path in paths if path))[:MAX_EVENT_PATHS]
101
+ if not paths:
102
+ return
103
+ event = RuntimeEvent(kind, actor.actor.actor_id, actor.session_id, actor.harness, paths,
104
+ execution=actor.execution)
105
+ for observer in self.observers:
106
+ try:
107
+ observer(event)
108
+ except Exception:
109
+ pass
87
110
 
88
111
  def resolve_actor(self, *, session_id: str, harness: str) -> ActorContext:
89
112
  return self.identity.resolve(session_id=session_id, harness=harness)
@@ -108,13 +131,15 @@ class EgregoreRuntime:
108
131
  retriever=self.retriever,
109
132
  telemetry=self.telemetry,
110
133
  lifecycle=self.lifecycle,
111
- admin_gate=self.admin_gate,
134
+ memory_root=self.canonical_root,
112
135
  )
113
- return compiler.observe(
136
+ context = compiler.observe(
114
137
  actor,
115
138
  request,
116
139
  token_budget=token_budget or actor.profile.default_context_budget,
117
140
  )
141
+ self._notify("observed", actor, (item.canonical_path for item in context.evidence))
142
+ return context
118
143
 
119
144
  def retrieval_health(self) -> RetrieverHealth:
120
145
  return self.retriever.health()
@@ -236,6 +261,54 @@ class EgregoreRuntime:
236
261
  def open_source(self, actor: ActorContext, canonical_path: str) -> str:
237
262
  """Authorize, open, and record one canonical source through Runtime."""
238
263
 
264
+ content, artifact_id, canonical = self._served(actor, canonical_path)
265
+ try:
266
+ self.telemetry.emit(
267
+ TelemetryEvent(
268
+ schema_version=TELEMETRY_SCHEMA_VERSION,
269
+ event_id=f"evt_{uuid4().hex}",
270
+ event_type="source.opened",
271
+ occurred_at=datetime.now(UTC),
272
+ org_id=actor.profile.org_id,
273
+ actor_id=actor.actor.actor_id,
274
+ session_id=actor.session_id,
275
+ org_revision=actor.profile.revision,
276
+ index_spec_version=self._index_spec_version(),
277
+ metrics={
278
+ "opened_count": 1,
279
+ "result": "authorized",
280
+ "success": True,
281
+ },
282
+ artifact_ids=(artifact_id,),
283
+ )
284
+ )
285
+ except Exception:
286
+ # Source access is canonical behavior; telemetry is disposable.
287
+ pass
288
+ self._notify("opened", actor, (canonical,))
289
+ return content
290
+
291
+ def serve_source(self, actor: ActorContext, canonical_path: str) -> str:
292
+ """One canonical source as ``open_source`` serves it (authorized, the admin guard, redacted), without
293
+ recording it as opened: for a listing that reads many files to show a few (``shown`` records those)."""
294
+
295
+ return self._served(actor, canonical_path)[0]
296
+
297
+ def shown(self, actor: ActorContext, canonical_paths: Sequence[str]) -> None:
298
+ """Record that a listing showed the actor what these canonical sources hold, as an observe does."""
299
+
300
+ self._notify("observed", actor, canonical_paths)
301
+
302
+ def _index_spec_version(self) -> str:
303
+ """The retriever's index spec: its own constant when it states one, since its health check can be a
304
+ subprocess (QMD's runs ``qmd status``) that every source open would pay for."""
305
+
306
+ known = getattr(self.retriever, "index_spec_version", None)
307
+ return known if isinstance(known, str) and known else self.retriever.health().index_spec_version
308
+
309
+ def _served(self, actor: ActorContext, canonical_path: str) -> tuple[str, str, str]:
310
+ """(content, stable artifact id, canonical path) of one authorized, redacted canonical source."""
311
+
239
312
  path = canonical_path.strip().replace("\\", "/")
240
313
  relative = path.removeprefix("memory/")
241
314
  artifact_id = artifact_id_from_path(relative)
@@ -243,13 +316,14 @@ class EgregoreRuntime:
243
316
  # stable declared id is resolved from the authorized source and then
244
317
  # checked separately before content is returned to the harness.
245
318
  decision = self.authorize(actor, Permission.READ)
246
- if not decision.allowed or not scope_allows_path(path, decision.scopes):
319
+ verdict = check(decision, path, "read", root=self.canonical_root)
320
+ if not verdict.allowed:
247
321
  raise AuthorizationDenied("canonical source is outside the actor read scope")
248
322
 
249
323
  content = self.retriever.open_source(
250
324
  RetrievalHit(
251
325
  artifact_id=artifact_id,
252
- canonical_path=path,
326
+ canonical_path=verdict.canonical,
253
327
  rank=1,
254
328
  score=1,
255
329
  retrieval_types=(RetrievalMode.LEX,),
@@ -259,44 +333,31 @@ class EgregoreRuntime:
259
333
  # only to organization admins. Repository members can still read the
260
334
  # raw Git file — this guards the product surface, not the filesystem.
261
335
  if (
262
- self.admin_gate is not None
263
- and document_is_admin(content)
264
- and not self.admin_gate.actor_is_admin(actor)
336
+ not is_admin(decision) and document_is_admin(content)
265
337
  ):
266
338
  raise AuthorizationDenied(
267
339
  "this document is admin-marked; Egregore serves it to organization admins only"
268
340
  )
269
341
  stable_artifact_id = canonical_artifact_id(content, path)
270
342
  stable_decision = self.authorize(actor, Permission.READ, (stable_artifact_id,))
271
- if not stable_decision.allowed:
343
+ if not stable_decision.allowed or (
344
+ stable_decision.resource_ids and stable_artifact_id not in stable_decision.resource_ids
345
+ ):
272
346
  raise AuthorizationDenied(
273
347
  "canonical artifact identity is outside the actor read scope"
274
348
  )
275
- artifact_id = stable_artifact_id
276
- try:
277
- self.telemetry.emit(
278
- TelemetryEvent(
279
- schema_version=TELEMETRY_SCHEMA_VERSION,
280
- event_id=f"evt_{uuid4().hex}",
281
- event_type="source.opened",
282
- occurred_at=datetime.now(UTC),
283
- org_id=actor.profile.org_id,
284
- actor_id=actor.actor.actor_id,
285
- session_id=actor.session_id,
286
- org_revision=actor.profile.revision,
287
- index_spec_version=self.retriever.health().index_spec_version,
288
- metrics={
289
- "opened_count": 1,
290
- "result": "authorized",
291
- "success": True,
292
- },
293
- artifact_ids=(artifact_id,),
294
- )
349
+ if not check(stable_decision, path, "read", root=self.canonical_root).allowed:
350
+ raise AuthorizationDenied(
351
+ "canonical source is outside the current actor read scope"
295
352
  )
296
- except Exception:
297
- # Source access is canonical behavior; telemetry is disposable.
298
- pass
299
- return content
353
+ if not is_admin(stable_decision) and document_is_admin(content):
354
+ raise AuthorizationDenied(
355
+ "this document is admin-marked; Egregore serves it to organization admins only"
356
+ )
357
+ artifact_id = stable_artifact_id
358
+ # The one packaging: names of the documents held from this reader leave the text.
359
+ content = redact(stable_decision, content, root=self.canonical_root)
360
+ return content, artifact_id, verdict.canonical
300
361
 
301
362
  def write_artifact(
302
363
  self,
@@ -304,21 +365,36 @@ class EgregoreRuntime:
304
365
  artifact: CanonicalArtifact,
305
366
  content: str,
306
367
  ) -> WritebackEvent:
307
- return self._writeback(actor).write(actor, artifact, content)
368
+ receipt = self._writeback(actor).write(actor, artifact, content)
369
+ if receipt.status in (WritebackStatus.ACCEPTED, WritebackStatus.PARTIAL):
370
+ self._notify("wrote", actor, ("memory/" + receipt.artifact.canonical_path,))
371
+ return receipt
308
372
 
309
373
  def write_document(
310
374
  self,
311
375
  actor: ActorContext,
312
376
  document: CanonicalDocument,
377
+ *,
378
+ message: str | None = None,
313
379
  ) -> WritebackEvent:
314
- return self._writeback(actor).write_document(actor, document)
380
+ """``message``: the commit's own message (a document edit's file, why and trailers); every other
381
+ write keeps the writeback's."""
382
+ receipt = self._writeback(actor).write_document(actor, document, **({"message": message} if message else {}))
383
+ if receipt.status in (WritebackStatus.ACCEPTED, WritebackStatus.PARTIAL):
384
+ self._notify("wrote", actor, ("memory/" + receipt.artifact.canonical_path,))
385
+ return receipt
315
386
 
316
387
  def write_documents(
317
388
  self,
318
389
  actor: ActorContext,
319
390
  documents: tuple[CanonicalDocument, ...],
320
391
  ) -> tuple[WritebackEvent, ...]:
321
- return self._writeback(actor).write_documents(actor, documents)
392
+ receipts = self._writeback(actor).write_documents(actor, documents)
393
+ self._notify("wrote", actor, (
394
+ "memory/" + receipt.artifact.canonical_path for receipt in receipts
395
+ if receipt.status in (WritebackStatus.ACCEPTED, WritebackStatus.PARTIAL)
396
+ ))
397
+ return receipts
322
398
 
323
399
  def commit_canonical_files(
324
400
  self,
@@ -331,9 +407,12 @@ class EgregoreRuntime:
331
407
 
332
408
  if self.canonical_root is None:
333
409
  raise RuntimeError("canonical file writeback is unavailable")
334
- return self._writeback(actor).commit_files(
410
+ receipt = self._writeback(actor).commit_files(
335
411
  actor, paths, memory_root=self.canonical_root, message=message,
336
412
  )
413
+ if receipt.status in (WritebackStatus.ACCEPTED, WritebackStatus.PARTIAL):
414
+ self._notify("wrote", actor, ("memory/" + path for path in receipt.canonical_paths))
415
+ return receipt
337
416
 
338
417
  def register_ingest_manifest(
339
418
  self,
@@ -385,7 +464,7 @@ class EgregoreRuntime:
385
464
  aliases.update(str(value) for value in actor.account.provider_aliases.values())
386
465
  recipient_aliases = tuple(sorted(value for value in aliases if value.strip()))
387
466
  return self.lifecycle.scan(
388
- authorized_scopes=decision.scopes,
467
+ decision=decision,
389
468
  artifact_types=artifact_types,
390
469
  user=recipient_aliases,
391
470
  )
@@ -413,12 +492,8 @@ class EgregoreRuntime:
413
492
  matches = [record for record in plan.records if record.artifact_id == target_artifact_id]
414
493
  if len(matches) != 1:
415
494
  raise LifecycleError("lifecycle target is missing or ambiguous")
416
- administer = bool(
417
- actor.membership
418
- and {role.casefold() for role in actor.membership.roles}
419
- & {"admin", "administrator", "owner", "founder"}
420
- and self.authorize(actor, Permission.ADMINISTER, (target_artifact_id,)).allowed
421
- )
495
+ admin = self.authorize(actor, Permission.ADMINISTER, (target_artifact_id,))
496
+ administer = is_admin(admin)
422
497
  if self.lifecycle is None:
423
498
  raise LifecycleError("canonical lifecycle service is unavailable")
424
499
  transition = self.lifecycle.prepare_transition(
@@ -477,6 +552,8 @@ def local_runtime(
477
552
  *,
478
553
  push_remote: bool = True,
479
554
  policy_factory: PolicyFactory | None = None,
555
+ identity: IdentityResolver | None = None,
556
+ observers: Sequence[Observer] | None = None,
480
557
  ) -> EgregoreRuntime:
481
558
  """Compose the Local architecture; all projections remain disposable."""
482
559
 
@@ -488,14 +565,12 @@ def local_runtime(
488
565
  state_dir=Path.home() / ".egregore" / "runtime" / "lifecycle" / instance_id,
489
566
  )
490
567
 
491
- def dogfood(actor: ActorContext) -> ActionAuthorizer:
492
- return all_access_dogfood_policy(
493
- org_id=actor.profile.org_id,
494
- actor_id=actor.actor.actor_id,
495
- )
496
-
568
+ active_observers = list(observers) if observers is not None else [
569
+ *([record_observer(repository_root)] if record_observer is not None else []),
570
+ projection_observer(repository_root),
571
+ ]
497
572
  return EgregoreRuntime(
498
- identity=LocalIdentityResolver(repository_root),
573
+ identity=identity if identity is not None else LocalIdentityResolver(repository_root),
499
574
  retriever=QmdLocalRetriever(
500
575
  repository_root=repository_root,
501
576
  memory_root=memory_root,
@@ -511,9 +586,43 @@ def local_runtime(
511
586
  lifecycle=lifecycle,
512
587
  canonical_root=memory_root,
513
588
  instance_root=repository_root,
514
- policy_factory=policy_factory or dogfood,
515
- admin_gate=AdminGate(
516
- config_path=repository_root / "egregore.json",
517
- memory_root=memory_root,
518
- ),
589
+ observers=active_observers,
590
+ policy_factory=policy_factory or (lambda actor: policy_for(authorization_inputs(repository_root))),
591
+ )
592
+
593
+
594
+ def authenticated_member_runtime(
595
+ root: Path,
596
+ *,
597
+ identity: AuthenticatedMemberResolver,
598
+ push_remote: bool = True,
599
+ policy_factory: PolicyFactory | None = None,
600
+ observers: Sequence[Observer] = (),
601
+ ) -> EgregoreRuntime:
602
+ """Compose current member authority over Local policy, without Local login.
603
+
604
+ The trusted host supplies an authenticated resolver. Missing/unavailable
605
+ verification never falls back to the machine owner's identity. This factory
606
+ does not issue grants, connect transports, or grant paid capabilities.
607
+ """
608
+ from .member_authority import AuthenticatedMemberResolver, MemberAuthorityPolicy
609
+
610
+ if not isinstance(identity, AuthenticatedMemberResolver):
611
+ raise ValueError("authenticated member identity is required")
612
+ repository_root = runtime_root(root)
613
+ # Verify the configured workspace independently of the server response.
614
+ # Merely presenting a valid grant for another organization cannot select it.
615
+ try:
616
+ config = json.loads((repository_root / "egregore.json").read_text())
617
+ if config.get("org_id") != identity.expected.org_id:
618
+ raise ValueError("different organization")
619
+ except (OSError, ValueError, AttributeError):
620
+ raise ValueError("member authority does not match this workspace") from None
621
+ base = policy_factory or (lambda actor: policy_for(authorization_inputs(repository_root)))
622
+ return local_runtime(
623
+ repository_root, push_remote=push_remote, identity=identity,
624
+ policy_factory=lambda actor: MemberAuthorityPolicy(identity, base),
625
+ # Legacy projection refresh resolves Local identity during composition.
626
+ # Hosts opt into observers only after adapting their authority boundary.
627
+ observers=observers,
519
628
  )
@@ -0,0 +1,194 @@
1
+ """Sandbox markers, and where per-user state goes when it refuses writes.
2
+
3
+ A session started outside the ``egregore`` launcher can run inside Codex's
4
+ sandbox or a macOS app sandbox, where the per-user state under ``~/.egregore``
5
+ refuses writes. A sandbox is named only from positive markers, the predicate
6
+ ``bin/lib/secret_entry.py`` and ``bin/reflexes/20-sandbox.sh`` also use; a
7
+ failing write alone never names one.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from contextlib import suppress
13
+ import errno
14
+ import os
15
+ from pathlib import Path
16
+ import stat
17
+ import tempfile
18
+ from typing import Mapping
19
+
20
+ # The errors that mean a location refuses writes. Anything else (a file where
21
+ # a directory belongs, a full disk) keeps its own failure: a fallback must not
22
+ # hide an unrelated fault.
23
+ REFUSED = frozenset({errno.EACCES, errno.EPERM, errno.EROFS})
24
+ REMEDY = "start through `egregore` to lift it"
25
+ # The workspace copy ignores itself, so it never reaches a commit in a
26
+ # checkout whose .gitignore predates it.
27
+ IGNORE_EVERYTHING = "# Egregore state for a sandboxed session\n*\n"
28
+
29
+
30
+ class FallbackRefused(Exception):
31
+ """The workspace copy cannot be used; the message says why."""
32
+
33
+
34
+ def sandboxed(environment: Mapping[str, str] | None = None) -> bool:
35
+ """True when CODEX_SANDBOX or APP_SANDBOX_CONTAINER_ID says so."""
36
+ env = os.environ if environment is None else environment
37
+ return (str(env.get("CODEX_SANDBOX") or "").lower() not in {"", "none", "false", "0"}
38
+ or bool(env.get("APP_SANDBOX_CONTAINER_ID")))
39
+
40
+
41
+ def reason(error: OSError) -> str:
42
+ return (error.strerror or type(error).__name__).lower()
43
+
44
+
45
+ def write_probe(directory: Path) -> OSError | None:
46
+ """Create and remove one file where ``directory`` is, or would be created.
47
+
48
+ The file goes in the nearest existing ancestor, so the probe creates no
49
+ directory. A file that cannot be removed fails the probe as surely as one
50
+ that cannot be created. Returns the error, or None when both succeeded.
51
+ """
52
+ anchor = Path(directory)
53
+ while not os.path.exists(anchor) and anchor.parent != anchor:
54
+ anchor = anchor.parent
55
+ try:
56
+ descriptor, probe = tempfile.mkstemp(prefix=".egregore-write-probe-", dir=anchor)
57
+ except OSError as error:
58
+ return error
59
+ removal: OSError | None = None
60
+ try:
61
+ os.close(descriptor)
62
+ except OSError:
63
+ pass
64
+ finally:
65
+ try:
66
+ os.unlink(probe)
67
+ except OSError as error:
68
+ removal = error
69
+ return removal
70
+
71
+
72
+ def display(path: Path, home: Path | None = None) -> str:
73
+ """``path`` with the home directory written as ``~``.
74
+
75
+ Both the home as given and its resolved form count, so a resolved path
76
+ under a linked home does not show the physical directory.
77
+ """
78
+ try:
79
+ base = Path(home) if home is not None else Path.home()
80
+ except (KeyError, OSError, RuntimeError):
81
+ return str(path)
82
+ bases = [base]
83
+ with suppress(OSError, RuntimeError):
84
+ if base.resolve() != base:
85
+ bases.append(base.resolve())
86
+ for candidate in bases:
87
+ if candidate == candidate.parent:
88
+ continue
89
+ try:
90
+ relative = Path(path).relative_to(candidate)
91
+ except ValueError:
92
+ continue
93
+ return "~" if relative == Path(".") else f"~/{relative.as_posix()}"
94
+ return str(path)
95
+
96
+
97
+ def _ignore_everything(directory: Path, home: Path | None) -> None:
98
+ """Make ``directory/.gitignore`` a regular file that ignores everything.
99
+
100
+ A file that already says so is kept. Anything else (missing, empty,
101
+ edited, a link, not a file) is replaced through a temporary file and a
102
+ rename, which replaces a link itself and never writes through it.
103
+ """
104
+ ignore = directory / ".gitignore"
105
+ current = None
106
+ flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
107
+ with suppress(OSError):
108
+ descriptor = os.open(ignore, flags)
109
+ with os.fdopen(descriptor, "rb") as handle:
110
+ if stat.S_ISREG(os.fstat(handle.fileno()).st_mode):
111
+ current = handle.read(len(IGNORE_EVERYTHING) + 1)
112
+ if current == IGNORE_EVERYTHING.encode():
113
+ return
114
+ try:
115
+ descriptor, temporary = tempfile.mkstemp(prefix=".gitignore.", dir=directory)
116
+ except OSError as error:
117
+ raise FallbackRefused(f"{display(ignore, home)} cannot be written ({reason(error)})") from error
118
+ try:
119
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
120
+ handle.write(IGNORE_EVERYTHING)
121
+ os.replace(temporary, ignore)
122
+ except OSError as error:
123
+ with suppress(OSError):
124
+ os.unlink(temporary)
125
+ raise FallbackRefused(f"{display(ignore, home)} cannot be written ({reason(error)})") from error
126
+
127
+
128
+ def _prepare(fallback: Path, workspace: Path, home: Path | None) -> None:
129
+ """Create ``fallback`` inside ``workspace`` and make it ignore itself.
130
+
131
+ Raises FallbackRefused, naming why, when the copy would leave the
132
+ workspace (through a link under it or outright), or when it cannot be
133
+ created, marked ignored, or written.
134
+ """
135
+ root = Path(workspace)
136
+ try:
137
+ relative = Path(fallback).relative_to(root)
138
+ except ValueError:
139
+ relative = None
140
+ if relative is None or not relative.parts:
141
+ # Never the workspace root itself: its .gitignore would hide the checkout.
142
+ raise FallbackRefused(f"{display(fallback, home)} is not inside the workspace")
143
+ current = root
144
+ for part in relative.parts:
145
+ current = current / part
146
+ if os.path.islink(current):
147
+ raise FallbackRefused(f"{display(current, home)} is a link")
148
+ try:
149
+ os.mkdir(current, 0o700)
150
+ except FileExistsError:
151
+ if os.path.islink(current) or not os.path.isdir(current):
152
+ raise FallbackRefused(f"{display(current, home)} is not a directory") from None
153
+ except OSError as error:
154
+ raise FallbackRefused(f"{display(current, home)} cannot be created ({reason(error)})") from error
155
+ try:
156
+ inside = Path(os.path.realpath(fallback)).is_relative_to(os.path.realpath(root))
157
+ except (OSError, ValueError):
158
+ inside = False
159
+ if not inside:
160
+ raise FallbackRefused(f"{display(fallback, home)} resolves outside the workspace")
161
+ _ignore_everything(Path(fallback), home)
162
+ # An ignore file that already matched proves nothing about new files: a
163
+ # folder may take subfolders yet refuse files (a macOS deny add_file ACL).
164
+ error = write_probe(Path(fallback))
165
+ if error is not None:
166
+ raise FallbackRefused(f"{display(fallback, home)} is not writable ({reason(error)})")
167
+
168
+
169
+ def state_directory(
170
+ preferred: Path,
171
+ fallback: Path,
172
+ *,
173
+ workspace: Path,
174
+ environment: Mapping[str, str] | None = None,
175
+ home: Path | None = None,
176
+ ) -> tuple[Path, str | None]:
177
+ """``preferred``, or ``fallback`` when the first refuses writes; and a warning.
178
+
179
+ The fallback is taken only when ``preferred`` refuses writes and the
180
+ workspace copy can be created inside ``workspace`` and marked ignored.
181
+ The warning names the cause, the sandbox when a marker is set and the
182
+ operating system's reason otherwise. When the copy is refused too, the
183
+ preferred directory stays, and the warning names both failures.
184
+ """
185
+ error = write_probe(preferred)
186
+ if error is None or error.errno not in REFUSED:
187
+ return preferred, None
188
+ cause = "sandbox" if sandboxed(environment) else reason(error)
189
+ refused = f"per-user state at {display(preferred, home)} is not writable ({cause})"
190
+ try:
191
+ _prepare(fallback, workspace, home)
192
+ except FallbackRefused as why:
193
+ return preferred, f"{refused}; no workspace copy either: {why}"
194
+ return fallback, f"{refused}; using {display(fallback, home)}"
@@ -7,6 +7,7 @@ import json
7
7
  import os
8
8
  from uuid import uuid4
9
9
 
10
+ from . import held
10
11
  from .runtime import local_runtime, runtime_root
11
12
  from .scratch import read_and_consume
12
13
  from .scrolls import CanonicalScrollService, EXACT_SHARE_CONFIRMATION, ScrollError
@@ -45,8 +46,8 @@ def _receipts(rows):
45
46
 
46
47
  def _show(args):
47
48
  actor, service = _context()
48
- print(json.dumps({"scroll": _row(service.read(actor, args.slug))}, ensure_ascii=False))
49
- return 0
49
+ row = _row(service.read(actor, args.slug))
50
+ return held.emit_for(service.runtime, actor, {"scroll": row}, paths=(row["canonical_path"],))
50
51
 
51
52
 
52
53
  def _create(args):
@@ -65,8 +66,9 @@ def _create(args):
65
66
  attention_ttl_days=args.ttl_days,
66
67
  supersedes=args.supersedes,
67
68
  )
68
- print(json.dumps({"scroll": _row(snapshot), "writeback": _receipts(receipts)}, ensure_ascii=False))
69
- return 0 if all(row.status.value in {"accepted", "partial"} for row in receipts) else 1
69
+ ok = all(row.status.value in {"accepted", "partial"} for row in receipts)
70
+ return held.emit_for(service.runtime, actor, {"scroll": _row(snapshot), "writeback": _receipts(receipts)}) or (
71
+ 0 if ok else 1)
70
72
 
71
73
 
72
74
  def _event(args):
@@ -79,24 +81,18 @@ def _event(args):
79
81
  face_markdown=read_and_consume(args.face, runtime_root()).decode("utf-8") if args.face else None,
80
82
  source_html=args.html,
81
83
  )
82
- print(
83
- json.dumps(
84
- {
85
- "scroll": _row(receipt.scroll),
86
- "event_id": receipt.event_id,
87
- "disposition": receipt.disposition,
88
- "writeback": _receipts(receipt.receipts),
89
- },
90
- ensure_ascii=False,
91
- )
92
- )
93
- return 0 if all(row.status.value in {"accepted", "partial"} for row in receipt.receipts) else 1
84
+ ok = all(row.status.value in {"accepted", "partial"} for row in receipt.receipts)
85
+ return held.emit_for(service.runtime, actor, {
86
+ "scroll": _row(receipt.scroll),
87
+ "event_id": receipt.event_id,
88
+ "disposition": receipt.disposition,
89
+ "writeback": _receipts(receipt.receipts),
90
+ }) or (0 if ok else 1)
94
91
 
95
92
 
96
93
  def _ledger(args):
97
94
  actor, service = _context()
98
- print(json.dumps(service.project_ledger(actor, args.slug), ensure_ascii=False))
99
- return 0
95
+ return held.emit_for(service.runtime, actor, service.project_ledger(actor, args.slug))
100
96
 
101
97
 
102
98
  def _plan_share(args):
@@ -104,8 +100,7 @@ def _plan_share(args):
104
100
  plan = service.plan_share(
105
101
  actor, slug=args.slug, html_path=args.html, description=args.description
106
102
  )
107
- print(json.dumps(plan.to_dict(), ensure_ascii=False))
108
- return 0
103
+ return held.emit_for(service.runtime, actor, plan.to_dict())
109
104
 
110
105
 
111
106
  def _share(args):
@@ -116,18 +111,13 @@ def _share(args):
116
111
  digest=args.digest,
117
112
  confirmation=args.confirm,
118
113
  )
119
- print(
120
- json.dumps(
121
- {
122
- "plan_id": receipt.plan_id,
123
- "slug": receipt.slug,
124
- "url": receipt.url,
125
- "writeback": _receipts(receipt.writeback),
126
- },
127
- ensure_ascii=False,
128
- )
129
- )
130
- return 0 if all(row.status.value in {"accepted", "partial"} for row in receipt.writeback) else 1
114
+ ok = all(row.status.value in {"accepted", "partial"} for row in receipt.writeback)
115
+ return held.emit_for(service.runtime, actor, {
116
+ "plan_id": receipt.plan_id,
117
+ "slug": receipt.slug,
118
+ "url": receipt.url,
119
+ "writeback": _receipts(receipt.writeback),
120
+ }) or (0 if ok else 1)
131
121
 
132
122
 
133
123
  def _parser():