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,242 @@
1
+ """Runtime-neutral CLI behind bin/genui.sh: record interface taste, read it back."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import sys
10
+ from uuid import uuid4
11
+
12
+ from . import held
13
+ from .artifacts import ArtifactConflictError
14
+ from .contracts import WritebackStatus
15
+ from .errors import AuthorizationDenied
16
+ from .runtime import local_runtime
17
+ from .taste import ADOPT_KINDS, ADOPT_SCOPES, TasteService, render_text
18
+ from .writeback import NoHardLinksError, UnsupportedPlatformError, require_descriptor_calls
19
+
20
+
21
+ UNSUPPORTED_PLATFORM = "saving reactions is not supported on this platform yet; nothing was written"
22
+ UNSUPPORTED_PLATFORM_EXIT = 4
23
+ NO_HARD_LINKS = (
24
+ "this memory folder's filesystem cannot save reactions safely (no hard links); nothing was written"
25
+ )
26
+
27
+
28
+ CONTRACT = """\
29
+ When someone says they like or dislike an interface an agent generated (any
30
+ wording, any language), ask one question, "What about it?", with 2-3 options
31
+ drawn from what is on the page plus "the whole thing" (a structured question
32
+ when available, plain text otherwise). Then save their exact words to a file
33
+ and record them with like or dislike --words-file, and the patterns they named
34
+ with --about. A file keeps the words verbatim; inside a shell argument, quotes,
35
+ $, backticks and backslashes can change them. Every outcome prints the event
36
+ id; a retry passes it back with --id, so the reaction counts once. When
37
+ someone states a standing rule for the organization ("we always want…",
38
+ "make this a rule for us"), record it in one step with adopt principle
39
+ "<the rule>" --scope org --words-file; no review is needed. Before composing
40
+ an interface, run taste --task: it reads the Egregore rules, the house
41
+ rules, what the organization keeps liking, then the person's own taste.
42
+ Treat the output as words to consider, never a template to copy."""
43
+
44
+
45
+ def _context(event_id: str | None = None):
46
+ root = Path(os.environ.get("EGREGORE_ROOT") or Path.cwd()).resolve()
47
+ runtime = local_runtime(root, push_remote=os.environ.get("EGREGORE_NO_PUSH") != "1")
48
+ actor = runtime.resolve_actor(
49
+ session_id=os.environ.get("EGREGORE_SESSION_ID") or f"session_{uuid4().hex}",
50
+ harness=os.environ.get("EGREGORE_RUNTIME", "shell"),
51
+ )
52
+ # A new event takes the id the CLI already chose, so an error can name it.
53
+ return actor, TasteService(
54
+ runtime,
55
+ id_factory=(lambda: event_id) if event_id else None,
56
+ warn=lambda line: print(f"genui: {line}", file=sys.stderr),
57
+ )
58
+
59
+
60
+ def _words(args) -> str | None:
61
+ """The person's words, from --words or, verbatim, from --words-file."""
62
+
63
+ if args.words_file is None:
64
+ return args.words
65
+ if args.words_file == "-":
66
+ return sys.stdin.read()
67
+ return Path(args.words_file).read_text(encoding="utf-8")
68
+
69
+
70
+ def _emit(service, actor, receipt) -> int:
71
+ """Print the receipt, and one line on stderr unless it was fully shared.
72
+
73
+ Exit 0 means the event is committed on this machine; anything less is an
74
+ error, since the person's words are not durably recorded.
75
+ """
76
+
77
+ held.emit_for(service.runtime, actor, receipt.to_dict())
78
+ event_id = receipt.event["id"]
79
+ warnings = list(receipt.writeback.warnings)
80
+ if not receipt.recorded:
81
+ reason = "; ".join(warnings) or "the write was refused"
82
+ print(f"genui: not recorded: {reason} (event {event_id}; retry with --id {event_id})", file=sys.stderr)
83
+ return 1
84
+ if receipt.writeback.status is WritebackStatus.PARTIAL:
85
+ unshared = [warning for warning in warnings if "push" in warning or "remote" in warning]
86
+ if unshared:
87
+ line = f"saved on this machine, not yet shared: {'; '.join(unshared)}"
88
+ else:
89
+ line = f"saved on this machine, with a warning: {'; '.join(warnings) or 'partial writeback'}"
90
+ print(f"genui: {line} (event {event_id})", file=sys.stderr)
91
+ return 0
92
+
93
+
94
+ def _record(args, record) -> int:
95
+ """Run one recording command so that every outcome names the event id."""
96
+
97
+ event_id = (args.id or "").strip() or str(uuid4())
98
+ try:
99
+ require_descriptor_calls() # before anything is opened or written
100
+ actor, service = _context(None if args.id else event_id)
101
+ receipt = record(actor, service, args.id)
102
+ except UnsupportedPlatformError:
103
+ # No retry can help on this platform.
104
+ print(f"genui: {UNSUPPORTED_PLATFORM}", file=sys.stderr)
105
+ return UNSUPPORTED_PLATFORM_EXIT
106
+ except NoHardLinksError:
107
+ # Nor on this filesystem: the staged copy is gone, nothing is written.
108
+ print(f"genui: not recorded: {NO_HARD_LINKS} (event {event_id})", file=sys.stderr)
109
+ return 1
110
+ except ArtifactConflictError as exc:
111
+ # A retry cannot fix a different reaction under a used id.
112
+ print(f"genui: not recorded: {exc} (event {event_id})", file=sys.stderr)
113
+ return 1
114
+ except (AuthorizationDenied, OSError, RuntimeError, ValueError) as exc:
115
+ print(f"genui: not recorded: {exc} (event {event_id}; retry with --id {event_id})", file=sys.stderr)
116
+ return 1
117
+ return _emit(service, actor, receipt)
118
+
119
+
120
+ def _react(args) -> int:
121
+ return _record(args, lambda actor, service, event_id: service.react(
122
+ actor,
123
+ "liked" if args.command == "like" else "disliked",
124
+ args.target,
125
+ words=_words(args),
126
+ about=args.about,
127
+ region=args.region,
128
+ task=args.task,
129
+ audience=args.audience,
130
+ form=args.form,
131
+ event_id=event_id,
132
+ ))
133
+
134
+
135
+ def _chose(args) -> int:
136
+ return _record(args, lambda actor, service, event_id: service.choose(
137
+ actor,
138
+ args.winner,
139
+ args.over,
140
+ words=_words(args),
141
+ about=args.about,
142
+ task=args.task,
143
+ audience=args.audience,
144
+ form=args.form,
145
+ event_id=event_id,
146
+ ))
147
+
148
+
149
+ def _adopt(args) -> int:
150
+ return _record(args, lambda actor, service, event_id: service.adopt(
151
+ actor, args.kind, args.value, scope=args.scope, words=_words(args), event_id=event_id,
152
+ ))
153
+
154
+
155
+ def _retract(args) -> int:
156
+ return _record(args, lambda actor, service, event_id: service.retract(
157
+ actor, args.event_id, words=_words(args), event_id=event_id,
158
+ ))
159
+
160
+
161
+ def _taste(args) -> int:
162
+ actor, service = _context()
163
+ summary = service.summary(actor, person=args.person, task=args.task, form=args.form)
164
+ return held.emit_for(service.runtime, actor, summary, **({} if args.json else {"render": render_text, "end": ""}))
165
+
166
+
167
+ def _parser() -> argparse.ArgumentParser:
168
+ parser = argparse.ArgumentParser(
169
+ prog="bin/genui.sh",
170
+ description="Record what people like in generated interfaces and read it back as words.",
171
+ epilog=CONTRACT,
172
+ formatter_class=argparse.RawDescriptionHelpFormatter,
173
+ )
174
+ commands = parser.add_subparsers(dest="command", required=True)
175
+
176
+ def words(command, *, required: bool) -> None:
177
+ given = command.add_mutually_exclusive_group(required=required)
178
+ given.add_argument("--words", help="what the person said, verbatim, as one shell argument")
179
+ given.add_argument(
180
+ "--words-file", metavar="PATH",
181
+ help="a file holding what the person said, kept verbatim; '-' reads standard input",
182
+ )
183
+
184
+ def context(command, *, region: bool = True) -> None:
185
+ command.add_argument("--about", action="append", default=[], metavar="PATTERN",
186
+ help="a pattern the person named; repeat for each")
187
+ if region:
188
+ command.add_argument("--region", help="the part of the page, as free text or a selector")
189
+ command.add_argument("--task", help="what the interface was for")
190
+ command.add_argument("--audience", help="who it was for")
191
+ command.add_argument("--form", help="the kind of interface, e.g. cards, scroll, mock, view")
192
+ command.add_argument("--id", help="event UUID; reuse it when retrying the same event")
193
+
194
+ for name, verb in (("like", "liked"), ("dislike", "disliked")):
195
+ react = commands.add_parser(name, help=f"record that someone {verb} a generated interface")
196
+ react.add_argument("target", help="registry id, hosted URL, local path or claude.ai artifact link")
197
+ words(react, required=True)
198
+ context(react)
199
+ react.set_defaults(handler=_react)
200
+
201
+ chose = commands.add_parser("chose", help="record that someone chose one interface over another")
202
+ chose.add_argument("winner", help="the interface they chose")
203
+ chose.add_argument("--over", required=True, help="the interface they passed over")
204
+ words(chose, required=False)
205
+ context(chose, region=False)
206
+ chose.set_defaults(handler=_chose)
207
+
208
+ adopt = commands.add_parser(
209
+ "adopt",
210
+ help="make a palette or pattern a default, or a principle a standing rule (--scope org: a house rule)",
211
+ )
212
+ adopt.add_argument("kind", choices=ADOPT_KINDS)
213
+ adopt.add_argument("value", help="the palette, pattern or principle, in a few words")
214
+ adopt.add_argument("--scope", choices=ADOPT_SCOPES, default="person")
215
+ words(adopt, required=False)
216
+ adopt.add_argument("--id", help="event UUID; reuse it when retrying the same event")
217
+ adopt.set_defaults(handler=_adopt)
218
+
219
+ retract = commands.add_parser("retract", help="withdraw one of your own taste events")
220
+ retract.add_argument("event_id")
221
+ words(retract, required=False)
222
+ retract.add_argument("--id", help="event UUID; reuse it when retrying the same event")
223
+ retract.set_defaults(handler=_retract)
224
+
225
+ taste = commands.add_parser("taste", help="read back what someone liked, in their words")
226
+ taste.add_argument("--person", help="a name or actor id; defaults to you")
227
+ taste.add_argument("--task", help="the work about to be composed; closest reactions come first")
228
+ taste.add_argument("--form", help="the kind of interface about to be composed")
229
+ taste.add_argument("--json", action="store_true")
230
+ taste.set_defaults(handler=_taste)
231
+ return parser
232
+
233
+
234
+ def main(argv: list[str] | None = None) -> int:
235
+ """Run one command; bin/genui.sh reaches it through bin/lib/genui-entry.py."""
236
+
237
+ args = _parser().parse_args(argv)
238
+ try:
239
+ return args.handler(args)
240
+ except (AuthorizationDenied, OSError, RuntimeError, ValueError) as exc:
241
+ print(f"genui: {exc}", file=sys.stderr)
242
+ return 1
@@ -0,0 +1,91 @@
1
+ """Egregore rules: good design for any reader, whatever the brand.
2
+
3
+ These ship with the Runtime to every organization and are read first, before
4
+ the organization's house rules, what the organization keeps liking, and one
5
+ person's own taste; later layers win where they conflict. The must-pass
6
+ checks (contrast, accessibility, colours from tokens, sandboxed hosting, no
7
+ internal notes in the outside copy) live in code and always hold.
8
+
9
+ Each rule says when to skip it and why it exists, so an agent can set it
10
+ aside with a reason instead of following it blindly.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ EGREGORE_RULES: tuple[dict[str, str], ...] = (
16
+ {
17
+ "rule": "Design every page from its content; never ship a stock template.",
18
+ "skip": "A form the reader already knows and expects, such as a receipt.",
19
+ "why": "Examples inspire; a skeleton reused as-is makes every page read the same.",
20
+ },
21
+ {
22
+ "rule": "The first view is a complete frame: nothing cut by the fold, nothing overflowing sideways, no controls over content.",
23
+ "skip": "Never.",
24
+ "why": "The first screen decides whether the reader continues.",
25
+ },
26
+ {
27
+ "rule": "Give one strong visual room, and keep nothing off-chart: extend the axes, or cut the items and say so.",
28
+ "skip": "Pages with no visual argument to make.",
29
+ "why": "A cramped or clipped figure hides the point it exists to show.",
30
+ },
31
+ {
32
+ "rule": "Leave clear space between groups, and check overlaps mechanically, not by eye.",
33
+ "skip": "Never.",
34
+ "why": "Crowding reads as noise, and the eye misses overlaps a check catches.",
35
+ },
36
+ {
37
+ "rule": "Choose sections per piece, never because a template lists them.",
38
+ "skip": "A form whose sections the reader relies on, such as a changelog.",
39
+ "why": "Standing sections pad a page with headings that say nothing.",
40
+ },
41
+ {
42
+ "rule": "Use plain, established words and the organization's own vocabulary; coin no terms in reader-facing text.",
43
+ "skip": "A term the organization already uses.",
44
+ "why": "A coined word is one more thing the reader must learn before understanding.",
45
+ },
46
+ {
47
+ "rule": "Introduce every term before using it, and write for someone who was not there.",
48
+ "skip": "Pages written for the people who were in the room.",
49
+ "why": "Pages travel further than the conversation that produced them.",
50
+ },
51
+ {
52
+ "rule": "Reveal depth step by step, but let the page answer its question with no interaction.",
53
+ "skip": "Tools and forms whose purpose is the interaction.",
54
+ "why": "A reader who never clicks should still leave with the answer.",
55
+ },
56
+ {
57
+ "rule": "Keep one design system as the chassis and one visual device per element.",
58
+ "skip": "Never.",
59
+ "why": "Mixed systems and stacked devices compete for attention.",
60
+ },
61
+ {
62
+ "rule": "Keep geometry honest: one meaning per axis, status readable without colour, every item accounted for.",
63
+ "skip": "Never.",
64
+ "why": "A figure that misleads is worse than no figure.",
65
+ },
66
+ {
67
+ "rule": "Move things only to explain them, and respect reduced motion.",
68
+ "skip": "Never.",
69
+ "why": "Decorative motion distracts and can make readers unwell.",
70
+ },
71
+ {
72
+ "rule": "Keep minimum craft: no cropped glyphs, labels that fit, focus always visible and styled, never removed.",
73
+ "skip": "Never.",
74
+ "why": "Small defects cost trust out of proportion to their size.",
75
+ },
76
+ {
77
+ "rule": "Serve two audiences from one source: mark internal notes and strip them from the outside copy.",
78
+ "skip": "Pages that never leave the organization.",
79
+ "why": "One source keeps both copies true; marked notes cannot leak by accident.",
80
+ },
81
+ {
82
+ "rule": "A page may carry its own palette when the content calls for it, if it passes the must-pass checks.",
83
+ "skip": "An organization with a house palette rule.",
84
+ "why": "Colour carries meaning; the checks keep it readable.",
85
+ },
86
+ {
87
+ "rule": "Draw no coloured side stripes: mark reading emphasis with a marker, a margin label or type, and status with a quiet panel whose label carries a shape.",
88
+ "skip": "A rail that connects timeline items.",
89
+ "why": "A stripe says only 'look here' and fails without colour; a shaped label reads in any theme.",
90
+ },
91
+ )
@@ -24,6 +24,7 @@ from urllib import request as urllib_request
24
24
  from uuid import uuid4
25
25
 
26
26
  from .contracts import TelemetryEvent
27
+ from .sandbox import state_directory
27
28
 
28
29
 
29
30
  TELEMETRY_SCHEMA_VERSION = "egregore.telemetry/v1"
@@ -138,6 +139,7 @@ class TelemetryStatus:
138
139
  storage_path: Path
139
140
  local_only: bool = True
140
141
  retention: Mapping[str, Any] | None = None
142
+ warnings: tuple[str, ...] = ()
141
143
 
142
144
 
143
145
  @dataclass(frozen=True, slots=True)
@@ -236,6 +238,8 @@ def validate_event(event: TelemetryEvent) -> None:
236
238
 
237
239
  for artifact_id in event.artifact_ids:
238
240
  _safe_identifier(artifact_id, field="artifact_ids[]", max_length=256)
241
+ if artifact_id.startswith(("memory/", "/", ".", "~")) or artifact_id.endswith((".md", ".html")):
242
+ raise TelemetryPrivacyError("artifact_ids[] must be opaque identifiers, never paths")
239
243
 
240
244
  if event.event_type in {"operation.start", "operation.result"}:
241
245
  required = {"operation", "layer", "invocation_id", "harness"}
@@ -368,7 +372,10 @@ class LocalTelemetrySink:
368
372
  ``~/.egregore/telemetry/``: on-device, outside canonical repositories, and
369
373
  unable to mix multiple Egregore instances. ``EGREGORE_TELEMETRY_DIR`` or
370
374
  an explicit ``storage_dir`` may relocate one sink for tests or managed
371
- local deployments.
375
+ local deployments. When that per-user directory refuses writes (a
376
+ sandboxed session), the buffer moves to the workspace's own
377
+ ``.egregore/telemetry/``, and ``status()`` names the cause first, and
378
+ names both failures when that copy is refused too.
372
379
  """
373
380
 
374
381
  def __init__(
@@ -382,6 +389,9 @@ class LocalTelemetrySink:
382
389
  instance_root: Path | None = None,
383
390
  ) -> None:
384
391
  env_dir = os.environ.get("EGREGORE_TELEMETRY_DIR")
392
+ # Set when the per-user directory refuses writes: with the workspace
393
+ # copy in use, or naming why that copy was refused too.
394
+ self.state_warning: str | None = None
385
395
  if storage_dir is not None:
386
396
  self.storage_dir = storage_dir
387
397
  elif env_dir:
@@ -390,6 +400,16 @@ class LocalTelemetrySink:
390
400
  root = instance_root or (state_file.parent if state_file else Path.cwd())
391
401
  namespace = hashlib.sha256(str(root.resolve()).encode()).hexdigest()[:20]
392
402
  self.storage_dir = Path.home() / ".egregore" / "telemetry" / namespace
403
+ if state_file is not None:
404
+ # The state file names the workspace, the one place a
405
+ # sandboxed session can still write.
406
+ workspace = state_file.parent
407
+ self.storage_dir, self.state_warning = state_directory(
408
+ self.storage_dir,
409
+ workspace / ".egregore" / "telemetry",
410
+ workspace=workspace,
411
+ home=Path.home(),
412
+ )
393
413
  self.buffer_path = self.storage_dir / "telemetry.jsonl"
394
414
  self.lock_path = self.storage_dir / "telemetry.lock"
395
415
  self.share_consent_path = self.storage_dir / "telemetry-share-consent.json"
@@ -550,6 +570,7 @@ class LocalTelemetrySink:
550
570
  byte_count=byte_count,
551
571
  storage_path=self.buffer_path,
552
572
  retention=self._retention() if self.retention_path.exists() else None,
573
+ warnings=(self.state_warning,) if self.state_warning else (),
553
574
  )
554
575
 
555
576
  def set_enabled(self, enabled: bool) -> None:
@@ -722,11 +743,13 @@ class HttpSharedTelemetrySink:
722
743
  "Content-Type": "application/x-ndjson",
723
744
  "User-Agent": "egregore-runtime-telemetry/1",
724
745
  }
725
- if self.bearer_token:
726
- headers["Authorization"] = f"Bearer {self.bearer_token}"
727
746
  outbound = urllib_request.Request(
728
747
  self.endpoint, data=payload, headers=headers, method="POST"
729
748
  )
749
+ if self.bearer_token:
750
+ # urllib copies only Request.headers to a redirect; the bearer
751
+ # token stays with this request and never reaches another host.
752
+ outbound.add_unredirected_header("Authorization", f"Bearer {self.bearer_token}")
730
753
  with urllib_request.urlopen(outbound, timeout=self.timeout_seconds) as response:
731
754
  body = response.read(4096).decode("utf-8", errors="replace")
732
755
  return body or str(response.status)
@@ -3,13 +3,13 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
- import json
7
6
  import os
8
7
  from pathlib import Path
9
8
  from uuid import uuid4
10
9
 
10
+ from . import held
11
11
  from .contracts import Permission
12
- from .policy import scope_allows_path
12
+ from .policy import action_for, check
13
13
  from .runtime import local_runtime
14
14
  from .todos import CanonicalTodoService
15
15
 
@@ -40,22 +40,25 @@ def _require(
40
40
  decision = runtime.authorize(actor, permission, (resource,))
41
41
  if not decision.allowed:
42
42
  raise PermissionError("; ".join(decision.reasons) or f"todo {permission.value} denied")
43
- if canonical_path and not scope_allows_path(canonical_path, decision.scopes):
43
+ if not canonical_path:
44
+ return decision, None
45
+ verdict = check(decision, canonical_path, action_for(decision.permission), root=runtime.canonical_root)
46
+ if not verdict.allowed:
44
47
  raise PermissionError("todo path is outside the actor's authorized scope")
45
- return decision
48
+ return decision, verdict.resolved # readers open the path the predicate resolved
46
49
 
47
50
 
48
51
  def _list(args):
49
52
  runtime, actor, service, default_person = _context()
50
53
  person = args.person or default_person
51
- _require(
54
+ decision, source = _require(
52
55
  runtime,
53
56
  actor,
54
57
  Permission.READ,
55
58
  f"todos:{person}",
56
59
  canonical_path=service.canonical_path(person),
57
60
  )
58
- snapshot = service.read(person)
61
+ snapshot = service.read(person, source=source)
59
62
  rows = list(snapshot.todos)
60
63
  if not args.all:
61
64
  rows = [row for row in rows if row.get("status") in {"open", "blocked", "deferred"}]
@@ -63,8 +66,8 @@ def _list(args):
63
66
  key=lambda row: (int(row.get("priority") or 0), str(row.get("created") or "")),
64
67
  reverse=True,
65
68
  )
66
- print(json.dumps({"person": snapshot.person, "todos": rows}, ensure_ascii=False))
67
- return 0
69
+ return held.emit(decision, {"person": snapshot.person, "todos": rows}, root=runtime.canonical_root,
70
+ paths=(service.canonical_path(person),))
68
71
 
69
72
 
70
73
  def _add(args):
@@ -84,8 +87,8 @@ def _add(args):
84
87
  priority=args.priority,
85
88
  quest=args.quest,
86
89
  )
87
- print(json.dumps({"todo": row, "writeback": receipt.to_dict()}, ensure_ascii=False))
88
- return 0 if receipt.status.value in {"accepted", "partial"} else 1
90
+ return held.emit_for(runtime, actor, {"todo": row, "writeback": receipt.to_dict()}) or (
91
+ 0 if receipt.status.value in {"accepted", "partial"} else 1)
89
92
 
90
93
 
91
94
  def _transition(args):
@@ -104,8 +107,8 @@ def _transition(args):
104
107
  todo_id=args.id,
105
108
  status=args.command,
106
109
  )
107
- print(json.dumps({"todo": row, "writeback": receipt.to_dict()}, ensure_ascii=False))
108
- return 0 if receipt.status.value in {"accepted", "partial"} else 1
110
+ return held.emit_for(runtime, actor, {"todo": row, "writeback": receipt.to_dict()}) or (
111
+ 0 if receipt.status.value in {"accepted", "partial"} else 1)
109
112
 
110
113
 
111
114
  def _parser():
@@ -159,8 +159,9 @@ class CanonicalTodoService:
159
159
  def canonical_path(person: str) -> str:
160
160
  return f"memory/todos/{_slug(person)}.md"
161
161
 
162
- def read(self, person: str) -> TodoSnapshot:
163
- path = self._path(person)
162
+ def read(self, person: str, *, source: Path | None = None) -> TodoSnapshot:
163
+ """Read a person's todos; a read surface passes the ``source`` the predicate resolved."""
164
+ path = source or self._path(person)
164
165
  if not path.exists():
165
166
  return TodoSnapshot(_slug(person), False, ())
166
167
  return parse_todo_markdown(path.read_text(encoding="utf-8"), person=person)