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,729 @@
1
+ """Append-only outcome attribution and comparable intervention checkpoints.
2
+
3
+ All monetary observations come from canonical records. Manifests supply intent,
4
+ verification references and allocation, never prices or an inferred acceptance.
5
+ """
6
+ import argparse
7
+ from collections import defaultdict
8
+ from datetime import datetime, timezone
9
+ from decimal import Decimal, InvalidOperation
10
+ import hashlib
11
+ import json
12
+ import math
13
+ import os
14
+ from pathlib import Path
15
+ import re
16
+ import stat
17
+
18
+
19
+ OUTCOMES = "outcomes.jsonl"
20
+ INTERVENTIONS = "interventions.jsonl"
21
+ SCHEMAS = {OUTCOMES: "record-outcome/v1", INTERVENTIONS: "record-intervention/v1"}
22
+ IDENT = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}\Z")
23
+ EVENT = re.compile(r"[a-f0-9]{64}\Z")
24
+ ROLES = {"driver", "worker", "retry", "review"}
25
+ STATUSES = {"open", "accepted", "rejected"}
26
+ QUALITY = {"pass", "fail", "unknown"}
27
+ LIMIT = 65536
28
+
29
+
30
+ def canonical(value):
31
+ return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
32
+
33
+
34
+ def money(value):
35
+ """Keep arithmetic overflow as unavailable, never JSON Infinity."""
36
+ try:
37
+ result = float(value)
38
+ except (TypeError, ValueError, OverflowError):
39
+ return None
40
+ return round(result, 6) if math.isfinite(result) else None
41
+
42
+
43
+ def label(value, field, limit=512):
44
+ if (not isinstance(value, str) or not value.strip() or len(value) > limit
45
+ or re.search(r"[\x00-\x1f\x7f]", value)):
46
+ raise ValueError(f"{field} needs a nonempty, single-line value of at most {limit} characters")
47
+ return value.strip()
48
+
49
+
50
+ def ident(value, field="id"):
51
+ if not isinstance(value, str) or not IDENT.fullmatch(value):
52
+ raise ValueError(f"{field} must be a stable identifier of 1–128 letters, digits, dots, colons, underscores or hyphens")
53
+ return value
54
+
55
+
56
+ def references(value):
57
+ if not isinstance(value, list) or len(value) > 32:
58
+ raise ValueError("evidence must be a list of at most 32 verification references")
59
+ return list(dict.fromkeys(label(ref, "evidence reference", 2048) for ref in value))
60
+
61
+
62
+ def share(value):
63
+ if isinstance(value, bool):
64
+ raise ValueError("share must be greater than zero and at most one")
65
+ try:
66
+ number = Decimal(str(value))
67
+ except (InvalidOperation, ValueError):
68
+ raise ValueError("share must be greater than zero and at most one") from None
69
+ if not number.is_finite() or not 0 < number <= 1 or number.as_tuple().exponent < -9:
70
+ raise ValueError("share must be greater than zero and at most one, with at most 9 decimal places")
71
+ return format(number.normalize(), "f")
72
+
73
+
74
+ def outcome_body(value):
75
+ fields = {"id", "cohort", "status", "evidence", "attribution_complete", "quality", "sessions"}
76
+ if (not isinstance(value, dict) or not fields <= set(value)
77
+ or set(value) - fields - {"findings", "thread"}):
78
+ raise ValueError("outcome manifest requires exactly id, cohort, status, evidence, attribution_complete, quality and sessions")
79
+ body = dict(value)
80
+ body["id"] = ident(body["id"])
81
+ body["cohort"] = label(body["cohort"], "cohort", 160)
82
+ if (not isinstance(body["status"], str) or not isinstance(body["quality"], str)
83
+ or body["status"] not in STATUSES or body["quality"] not in QUALITY):
84
+ raise ValueError("status is open|accepted|rejected and quality is pass|fail|unknown")
85
+ if type(body["attribution_complete"]) is not bool:
86
+ raise ValueError("attribution_complete must be true or false")
87
+ body["evidence"] = references(body["evidence"])
88
+ if body["status"] != "open":
89
+ if not body["evidence"]:
90
+ raise ValueError("accepted and rejected outcomes require verification evidence")
91
+ if body["quality"] != ("pass" if body["status"] == "accepted" else "fail"):
92
+ raise ValueError("accepted outcomes require passing quality; rejected outcomes require failing quality")
93
+ sessions, seen = body["sessions"], set()
94
+ if not isinstance(sessions, list) or len(sessions) > 128:
95
+ raise ValueError("sessions must be a list of at most 128 contributing cost scopes")
96
+ normalized = []
97
+ for scope in sessions:
98
+ if not isinstance(scope, dict) or set(scope) != {"session", "role", "share"}:
99
+ raise ValueError("each cost scope requires exactly session, role and share")
100
+ session = ident(scope["session"], "session")
101
+ if session in seen or not isinstance(scope["role"], str) or scope["role"] not in ROLES:
102
+ raise ValueError("each session occurs once per outcome, with role driver|worker|retry|review")
103
+ seen.add(session)
104
+ normalized.append({"session": session, "role": scope["role"], "share": share(scope["share"])})
105
+ if body["attribution_complete"] and not normalized:
106
+ raise ValueError("complete attribution requires at least one contributing cost scope")
107
+ body["sessions"] = sorted(normalized, key=lambda row: row["session"])
108
+ findings = body.get("findings", [])
109
+ if not isinstance(findings, list) or len(findings) > 32:
110
+ raise ValueError("findings must contain at most 32 stable failure-class identifiers")
111
+ body["findings"] = sorted(set(ident(item, "finding") for item in findings))
112
+ thread = body.get("thread")
113
+ if thread is not None:
114
+ if not isinstance(thread, dict) or set(thread) != {"id", "revision"}:
115
+ raise ValueError("thread requires exactly id and revision")
116
+ if not isinstance(thread["id"], str) or not re.fullmatch(r"thread:[a-z0-9]+(?:-[a-z0-9]+)*", thread["id"]):
117
+ raise ValueError("thread id must be thread:<slug>")
118
+ body["thread"] = {"id": ident(thread["id"], "thread"),
119
+ "revision": label(thread["revision"], "thread revision", 256)}
120
+ # Omit the absent extension so legacy event digests remain valid.
121
+ return body
122
+
123
+
124
+ def intervention_body(value, internal=False):
125
+ fields = {"id", "cohort", "finding", "change", "baseline", "post", "decision", "evidence"}
126
+ if internal:
127
+ fields |= {"baseline_events", "baseline_records"}
128
+ if not isinstance(value, dict) or set(value) != fields:
129
+ raise ValueError("intervention manifest requires exactly id, cohort, finding, change, baseline, post, decision and evidence")
130
+ body = dict(value)
131
+ body["id"] = ident(body["id"])
132
+ for field in ("cohort", "finding", "change"):
133
+ body[field] = label(body[field], field, 160 if field == "cohort" else 2048)
134
+ for field in ("baseline", "post"):
135
+ values = body[field]
136
+ if not isinstance(values, list) or len(values) > 1000:
137
+ raise ValueError(f"{field} must contain at most 1000 outcome identifiers")
138
+ body[field] = [ident(item, field) for item in values]
139
+ if len(set(body[field])) != len(body[field]):
140
+ raise ValueError(f"{field} cannot repeat an outcome")
141
+ if not body["baseline"]:
142
+ raise ValueError("an intervention needs at least one comparable baseline outcome")
143
+ if set(body["baseline"]) & set(body["post"]):
144
+ raise ValueError("baseline and post outcomes must be disjoint")
145
+ if not isinstance(body["decision"], str) or body["decision"] not in {"observe", "keep", "revise", "revert"}:
146
+ raise ValueError("decision is observe|keep|revise|revert")
147
+ body["evidence"] = references(body["evidence"])
148
+ if body["decision"] != "observe" and not body["evidence"]:
149
+ raise ValueError("an intervention decision requires verification evidence")
150
+ if internal:
151
+ snapshots = body["baseline_events"]
152
+ if (not isinstance(snapshots, dict) or set(snapshots) != set(body["baseline"])
153
+ or any(not isinstance(event, str) or not EVENT.fullmatch(event) for event in snapshots.values())):
154
+ raise ValueError("baseline event references are incomplete")
155
+ records = body["baseline_records"]
156
+ if (not isinstance(records, dict)
157
+ or any(not isinstance(sid, str) or not IDENT.fullmatch(sid)
158
+ or (digest is not None and (not isinstance(digest, str) or not EVENT.fullmatch(digest)))
159
+ for sid, digest in records.items())):
160
+ raise ValueError("baseline record references are invalid")
161
+ return body
162
+
163
+
164
+ def read_history(sr, memory, ledger):
165
+ """Read the whole append-only ledger with bounded rows and fail-closed coverage."""
166
+ sessions = sr.sessions_directory(memory)
167
+ result = {"coverage": "missing", "latest": {}, "events": {}, "conflicts": []}
168
+ if sessions is None:
169
+ return result
170
+ try:
171
+ fd = os.open(sessions / ledger, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
172
+ except FileNotFoundError:
173
+ return result
174
+ except OSError:
175
+ result["coverage"] = "unavailable"
176
+ return result
177
+ grouped = defaultdict(dict)
178
+ result["coverage"] = "complete"
179
+ with os.fdopen(fd, "rb") as stream:
180
+ if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
181
+ result["coverage"] = "unavailable"
182
+ return result
183
+ while True:
184
+ raw = stream.readline(LIMIT + 1)
185
+ if not raw:
186
+ break
187
+ if len(raw) > LIMIT:
188
+ while raw and not raw.endswith(b"\n"):
189
+ raw = stream.readline(LIMIT + 1)
190
+ result["coverage"] = "partial"
191
+ continue
192
+ if not raw.strip():
193
+ continue
194
+ if not raw.endswith(b"\n"):
195
+ result["coverage"] = "partial"
196
+ try:
197
+ event = json.loads(raw)
198
+ if (not isinstance(event, dict)
199
+ or set(event) != {"schema", "event_id", "previous", "at", "by", "session", "body"}
200
+ or event["schema"] != SCHEMAS[ledger]
201
+ or not isinstance(event["event_id"], str) or not EVENT.fullmatch(event["event_id"])
202
+ or (event["previous"] is not None and
203
+ (not isinstance(event["previous"], str) or not EVENT.fullmatch(event["previous"])))):
204
+ raise ValueError("invalid event")
205
+ label(event["by"], "actor")
206
+ ident(event["session"], "native session")
207
+ stamp = datetime.fromisoformat(event["at"])
208
+ if stamp.tzinfo is None:
209
+ raise ValueError("event time needs timezone")
210
+ body = outcome_body(event["body"]) if ledger == OUTCOMES else intervention_body(event["body"], True)
211
+ if body != event["body"]:
212
+ raise ValueError("noncanonical event")
213
+ hashed = dict(event)
214
+ hashed.pop("event_id")
215
+ if hashlib.sha256(canonical(hashed).encode()).hexdigest() != event["event_id"]:
216
+ raise ValueError("event digest mismatch")
217
+ event_id, entity = event["event_id"], body["id"]
218
+ if event_id in result["events"] and event != result["events"][event_id]:
219
+ raise ValueError("conflicting duplicate event")
220
+ grouped[entity][event_id] = event
221
+ result["events"][event_id] = event
222
+ except (ValueError, TypeError, KeyError, RecursionError, OverflowError):
223
+ result["coverage"] = "partial"
224
+ for entity, events in grouped.items():
225
+ children = defaultdict(list)
226
+ for event in events.values():
227
+ children[event["previous"]].append(event)
228
+ current, visited = None, set()
229
+ invalid_chain = False
230
+ while len(children[current["event_id"] if current else None]) == 1:
231
+ next_event = children[current["event_id"] if current else None][0]
232
+ if current:
233
+ old, new = current["body"], next_event["body"]
234
+ immutable = ("cohort",) if ledger == OUTCOMES else (
235
+ "cohort", "finding", "change", "baseline", "baseline_events", "baseline_records")
236
+ if (any(old[field] != new[field] for field in immutable)
237
+ or (ledger == INTERVENTIONS and not set(old["post"]) <= set(new["post"]))):
238
+ invalid_chain = True
239
+ break
240
+ current = next_event
241
+ if current["event_id"] in visited:
242
+ break
243
+ visited.add(current["event_id"])
244
+ if invalid_chain or current is None or len(visited) != len(events):
245
+ result["conflicts"].append(entity)
246
+ result["coverage"] = "partial"
247
+ else:
248
+ result["latest"][entity] = current
249
+ return result
250
+
251
+
252
+ def record_index(sr, memory):
253
+ index, conflicts, withheld = {}, set(), set()
254
+ for _, meta, _ in sr.record_files(memory):
255
+ sid = meta.get("session")
256
+ if meta.get("kind") != "session-record" or not isinstance(sid, str):
257
+ continue
258
+ if meta.get("detail") == "stub":
259
+ withheld.add(sid)
260
+ try:
261
+ revision = int(meta.get("record_revision", 0))
262
+ except (TypeError, ValueError):
263
+ revision = 0
264
+ old = index.get(sid)
265
+ if old is None or revision > old[0]:
266
+ index[sid] = (revision, meta)
267
+ conflicts.discard(sid)
268
+ elif revision == old[0] and meta != old[1]:
269
+ conflicts.add(sid)
270
+ return {sid: dict(meta, detail="stub") if sid in withheld else meta
271
+ for sid, (_, meta) in index.items()}, conflicts
272
+
273
+
274
+ def record_digest(meta):
275
+ if meta is None:
276
+ return None
277
+ fields = ("session", "status", "detail", "record_revision", "cost_usd", "cost_source",
278
+ "cost_coverage", "cost_sessions", "cost_basis")
279
+ return hashlib.sha256(canonical({key: meta.get(key) for key in fields}).encode()).hexdigest()
280
+
281
+
282
+ def join_outcomes(sr, history, records, conflicts, ctx=None):
283
+ """Allocate aggregate record scopes, retaining incomplete observations."""
284
+ rows, allocations, components, scope_users = {}, defaultdict(Decimal), {}, defaultdict(set)
285
+ # Correcting an attribution changes the projection, not the accounting
286
+ # universe. A formerly linked source remains visibly unallocated at zero.
287
+ for event in history["events"].values():
288
+ for scope in event["body"]["sessions"]:
289
+ allocations[scope["session"]] += 0
290
+ for entity, event in history["latest"].items():
291
+ body = event["body"]
292
+ reasons, priced, amount, roles, withheld = [], 0, Decimal(0), defaultdict(Decimal), False
293
+ joined_sessions = set()
294
+ if not body["attribution_complete"]:
295
+ reasons.append("attribution-not-closed")
296
+ if not body["sessions"]:
297
+ reasons.append("no-contributing-sessions")
298
+ for scope in body["sessions"]:
299
+ sid, fraction = scope["session"], Decimal(scope["share"])
300
+ allocations[sid] += fraction
301
+ scope_users[sid].add(entity)
302
+ meta = records.get(sid)
303
+ if (ctx is not None and ctx.held(sid)) or (meta and meta.get("detail") == "stub"):
304
+ withheld = True
305
+ reasons.append("withheld-record")
306
+ continue
307
+ if meta is None:
308
+ reasons.append("missing-record")
309
+ continue
310
+ joined_sessions.add(sid)
311
+ included = meta.get("cost_sessions")
312
+ if (not isinstance(included, list) or not included or sid not in included
313
+ or any(not isinstance(item, str) or not IDENT.fullmatch(item) for item in included)
314
+ or len(set(included)) != len(included)):
315
+ reasons.append("cost-scope-unknown")
316
+ else:
317
+ components[sid] = set(included)
318
+ if any(records.get(item, {}).get("detail") == "stub"
319
+ or (ctx is not None and ctx.held(item)) for item in included):
320
+ withheld = True
321
+ reasons.append("withheld-record")
322
+ continue
323
+ if sid in conflicts:
324
+ reasons.append("conflicting-session-records")
325
+ if meta.get("status") != "final":
326
+ reasons.append("session-in-progress")
327
+ if meta.get("cost_coverage") != "complete":
328
+ reasons.append("cost-coverage-incomplete")
329
+ value = sr.vitals_cost(meta.get("cost_usd"))
330
+ if value is None or meta.get("cost_source") == "unavailable" or sid in conflicts:
331
+ reasons.append("cost-unavailable")
332
+ else:
333
+ allocated = Decimal(str(value)) * fraction
334
+ amount += allocated
335
+ roles[scope["role"]] += allocated
336
+ priced += 1
337
+ observed = money(amount) if priced else None
338
+ if priced and observed is None:
339
+ reasons.append("cost-arithmetic-overflow")
340
+ rows[entity] = {"id": entity, "cohort": body["cohort"], "status": body["status"],
341
+ "quality": body["quality"], "evidence": body["evidence"], "event_id": event["event_id"],
342
+ "findings": body["findings"],
343
+ "thread": body.get("thread"),
344
+ "observed_cost_usd": observed,
345
+ "cost_by_scope_role_usd": {role: money(cost) for role, cost in roles.items()
346
+ if money(cost) is not None},
347
+ "priced_scopes": priced, "total_scopes": len(body["sessions"]),
348
+ "reasons": reasons, "_withheld": withheld, "_joined_sessions": joined_sessions}
349
+ unallocated = Decimal(0)
350
+ unallocated_known = False
351
+ for sid, fraction in allocations.items():
352
+ if fraction != 1:
353
+ reason = "overallocated-session" if fraction > 1 else "unallocated-session-share"
354
+ for entity in scope_users[sid]:
355
+ rows[entity]["reasons"].append(reason)
356
+ if fraction > 1:
357
+ rows[entity]["observed_cost_usd"] = None
358
+ rows[entity]["cost_by_scope_role_usd"] = {}
359
+ if fraction == 0:
360
+ for row in rows.values():
361
+ row["reasons"].append("unallocated-historical-session")
362
+ meta = records.get(sid, {})
363
+ value = sr.vitals_cost(meta.get("cost_usd"))
364
+ included = meta.get("cost_sessions")
365
+ private = (meta.get("detail") == "stub" or (ctx is not None and ctx.held(sid))
366
+ or (isinstance(included, list) and any(isinstance(item, str)
367
+ and (records.get(item, {}).get("detail") == "stub"
368
+ or (ctx is not None and ctx.held(item))) for item in included)))
369
+ if (fraction < 1 and value is not None and meta.get("cost_source") != "unavailable"
370
+ and not private and not any(rows[entity]["_withheld"] for entity in scope_users[sid])):
371
+ unallocated += Decimal(str(value)) * (1 - fraction)
372
+ unallocated_known = True
373
+ overlapping = set()
374
+ by_component = defaultdict(set)
375
+ for sid, members in components.items():
376
+ for member in members:
377
+ by_component[member].add(sid)
378
+ for aggregate_ids in by_component.values():
379
+ if len(aggregate_ids) > 1:
380
+ overlapping.update(aggregate_ids)
381
+ for sid in overlapping:
382
+ for entity in scope_users[sid]:
383
+ rows[entity]["reasons"].append("overlapping-parent-worker-cost")
384
+ # Neither total nor per-role spend may double count this scope.
385
+ rows[entity]["observed_cost_usd"] = None
386
+ rows[entity]["cost_by_scope_role_usd"] = {}
387
+ for row in rows.values():
388
+ if history["coverage"] != "complete":
389
+ row["reasons"].append("ledger-coverage-incomplete")
390
+ row["reasons"] = sorted(set(row["reasons"]))
391
+ row["cost_complete"] = not row["reasons"] and not row["_withheld"]
392
+ row["cost_usd"] = row["observed_cost_usd"] if row["cost_complete"] else None
393
+ if row["_withheld"]:
394
+ row["observed_cost_usd"] = None
395
+ row["cost_by_scope_role_usd"] = {}
396
+ return rows, money(unallocated) if unallocated_known else None
397
+
398
+
399
+ def economics(rows, coverage="complete"):
400
+ values = list(rows)
401
+ visible = [row for row in values if not row["_withheld"]]
402
+ accepted = sum(row["status"] == "accepted" for row in visible)
403
+ costs = [row["observed_cost_usd"] for row in visible if row["observed_cost_usd"] is not None]
404
+ complete = bool(values) and coverage == "complete" and all(row["cost_complete"] for row in values)
405
+ total = money(sum(Decimal(str(value)) for value in costs)) if costs else None
406
+ complete = complete and total is not None
407
+ statuses = {}
408
+ for status in sorted(STATUSES):
409
+ selected = [row for row in visible if row["status"] == status]
410
+ observed = [row["observed_cost_usd"] for row in selected if row["observed_cost_usd"] is not None]
411
+ statuses[status] = {"outcomes": len(selected), "observed_cost_usd": money(sum(observed)) if observed else None}
412
+ terminal = [row for row in visible if row["status"] != "open"]
413
+ failed = sum(row["quality"] == "fail" for row in terminal)
414
+ return {"status": "complete" if complete else "unavailable" if not values else "partial",
415
+ "cost_per_accepted_outcome_usd": money(total / accepted) if complete and accepted else None,
416
+ "observed_cost_usd": total,
417
+ "accepted_outcomes": accepted if values else None,
418
+ "joined_sessions": len({sid for row in visible for sid in row["_joined_sessions"]}) if values else None,
419
+ "outcomes": len(visible), "withheld_outcomes": len(values) - len(visible),
420
+ "complete_outcomes": sum(row["cost_complete"] for row in visible),
421
+ "by_status": statuses, "terminal_outcomes": len(terminal), "failed_outcomes": failed,
422
+ "failure_rate": failed / len(terminal) if terminal else None,
423
+ "cost_basis": "canonical-record estimates; all allocated accepted, rejected and open spend",
424
+ "scope": "declared outcomes only; undeclared organizational work is not inferred"}
425
+
426
+
427
+ def intervention_rows(history, outcome_history, joined, sr, records, conflicts, ctx):
428
+ result = []
429
+ for event in history["latest"].values():
430
+ body = event["body"]
431
+ selected = body["baseline"] + body["post"]
432
+ if any(joined.get(item, {}).get("_withheld") for item in selected):
433
+ result.append({"status": "withheld"})
434
+ continue
435
+ reasons = []
436
+ if history["coverage"] != "complete" or outcome_history["coverage"] != "complete":
437
+ reasons.append("ledger-coverage-incomplete")
438
+ if any(record_digest(records.get(sid)) != digest for sid, digest in body["baseline_records"].items()):
439
+ reasons.append("baseline-cost-record-revised")
440
+ if any(joined.get(item, {}).get("event_id") != ref for item, ref in body["baseline_events"].items()):
441
+ reasons.append("baseline-outcome-revised")
442
+ baseline_events = {item: outcome_history["events"].get(ref) for item, ref in body["baseline_events"].items()}
443
+ if any(event is None or event["body"]["id"] != item for item, event in baseline_events.items()):
444
+ reasons.append("missing-baseline-snapshot")
445
+ snapshots = {item: event for item, event in baseline_events.items() if event is not None}
446
+ baseline_joined, _ = join_outcomes(sr, {**outcome_history, "latest": snapshots,
447
+ "events": {event["event_id"]: event for event in snapshots.values()}},
448
+ records, conflicts, ctx)
449
+ if any(row["_withheld"] for row in baseline_joined.values()):
450
+ result.append({"status": "withheld"})
451
+ continue
452
+ # Allocation completeness is assessed against the whole outcome ledger,
453
+ # not the subset. A source can legitimately be shared across cohorts.
454
+ for item, row in baseline_joined.items():
455
+ if joined.get(item, {}).get("cost_complete"):
456
+ row["reasons"] = [reason for reason in row["reasons"] if reason != "unallocated-session-share"]
457
+ row["cost_complete"] = not row["reasons"]
458
+ row["cost_usd"] = row["observed_cost_usd"] if row["cost_complete"] else None
459
+ post = [joined[item] for item in body["post"] if item in joined]
460
+ if any(item not in joined for item in selected):
461
+ reasons.append("missing-outcome")
462
+ if any(joined[item]["cohort"] != body["cohort"] for item in selected if item in joined):
463
+ reasons.append("cohort-changed")
464
+ before, after = economics(baseline_joined.values(), outcome_history["coverage"]), economics(post, outcome_history["coverage"])
465
+ if before["status"] != "complete" or after["status"] != "complete":
466
+ reasons.append("cost-coverage-incomplete")
467
+ if before["terminal_outcomes"] < 5:
468
+ reasons.append("baseline-below-five")
469
+ observed = after["terminal_outcomes"]
470
+ regression = (after["failure_rate"] is not None and before["failure_rate"] is not None
471
+ and after["failure_rate"] > before["failure_rate"])
472
+ # Reopened previously terminal outcomes are also a direct quality signal.
473
+ reopened = any(joined[item]["status"] == "open"
474
+ and any(old["body"]["id"] == item and old["body"]["status"] != "open"
475
+ for old in outcome_history["events"].values())
476
+ for item in body["post"] if item in joined)
477
+ regression = regression or reopened
478
+ checkpoint = ("quality-review-now" if regression else "decide-at-ten" if observed >= 10
479
+ else "inspect-at-five" if observed >= 5 else "collect")
480
+ prior, current = before["cost_per_accepted_outcome_usd"], after["cost_per_accepted_outcome_usd"]
481
+ delta = round(current - prior, 6) if not reasons and prior is not None and current is not None else None
482
+ result.append({"id": body["id"], "cohort": body["cohort"], "finding": body["finding"],
483
+ "change": body["change"], "decision": body["decision"], "checkpoint": checkpoint,
484
+ "quality_regression": regression, "baseline": before, "post": after,
485
+ "unit_cost_delta_usd": delta, "reasons": sorted(set(reasons)),
486
+ "recommendation": ("Revisit the change now; verify the failed or reopened outcome before continuing."
487
+ if regression else "Choose keep, revise or revert using cost coverage and the same quality rubric."
488
+ if observed >= 10 else "Inspect the leading recurring expense and verify one focused repair."
489
+ if observed >= 5 else "Continue capturing comparable outcomes and complete cost scopes."),
490
+ "limitation": "Small observational cohorts guide decisions; a cost change does not establish causation."})
491
+ return result
492
+
493
+
494
+ def scorecard(sr, memory, ctx=None):
495
+ outcome_history = read_history(sr, memory, OUTCOMES)
496
+ intervention_history = read_history(sr, memory, INTERVENTIONS)
497
+ records, conflicts = record_index(sr, memory) if outcome_history["latest"] else ({}, set())
498
+ joined, unallocated = join_outcomes(sr, outcome_history, records, conflicts, ctx)
499
+ total = economics(joined.values(), outcome_history["coverage"])
500
+ total["unallocated_observed_cost_usd"] = unallocated
501
+ visible = [row for row in joined.values() if not row["_withheld"]]
502
+ public_rows = [{key: value for key, value in row.items() if not key.startswith("_")} for row in visible]
503
+ cohorts = {cohort: economics((row for row in joined.values() if row["cohort"] == cohort), outcome_history["coverage"])
504
+ for cohort in sorted({row["cohort"] for row in visible})}
505
+ by_thread = {
506
+ thread_id: {
507
+ "reference_basis": "explicit outcome attribution; source revision is not revalidated by this report",
508
+ "revisions": sorted({row["thread"]["revision"] for row in visible
509
+ if row["thread"] and row["thread"]["id"] == thread_id}),
510
+ **economics((row for row in joined.values() if row["thread"]
511
+ and row["thread"]["id"] == thread_id), outcome_history["coverage"]),
512
+ }
513
+ for thread_id in sorted({row["thread"]["id"] for row in visible if row["thread"]})
514
+ }
515
+ findings = []
516
+ def add(code, observation, action, verification, cost=None, affected=None):
517
+ affected = visible if affected is None else affected
518
+ refs = list(dict.fromkeys(ref for row in affected for ref in row["evidence"]))
519
+ findings.append({"code": code, "severity": "warning", "observation": observation,
520
+ "observed_cost_usd": cost,
521
+ "outcome_ids": [row["id"] for row in affected][:32], "evidence": refs[:32],
522
+ "coverage": {"outcome_ledger": outcome_history["coverage"],
523
+ "complete": total["complete_outcomes"], "declared": total["outcomes"],
524
+ "limitation": "Declared scope only; missing or withheld costs remain unknown."},
525
+ "action": action, "verification": verification})
526
+ if total["status"] != "complete":
527
+ add("accepted_outcome_link_missing" if not joined else "outcome_attribution_gap",
528
+ "Accepted-outcome unit cost is unavailable until all declared contributing scopes are complete.",
529
+ "Close contributor attribution, finish eligible records, repair missing cost coverage and allocate each aggregate scope exactly once.",
530
+ "Every declared outcome has complete final costs; parent and worker scopes do not overlap.")
531
+ for status, code in (("rejected", "rejected_outcome_spend"), ("open", "unfinished_outcome_spend")):
532
+ row = total["by_status"][status]
533
+ if row["outcomes"]:
534
+ add(code, f"{row['outcomes']} {status} outcomes have ${row['observed_cost_usd']:.2f} observed spend."
535
+ if row["observed_cost_usd"] is not None else f"Spend for {row['outcomes']} {status} outcomes is unknown.",
536
+ "Inspect the linked verification evidence; fix a recurring failure or explicitly resolve obsolete work.",
537
+ "The failure is reproduced and repaired, or the outcome receives an explicit terminal disposition.",
538
+ row["observed_cost_usd"], [item for item in visible if item["status"] == status])
539
+ for role in ("retry", "review"):
540
+ affected = [row for row in visible if role in row["cost_by_scope_role_usd"]]
541
+ role_cost = money(sum(row["cost_by_scope_role_usd"][role] for row in affected)) if affected else None
542
+ if role_cost is not None:
543
+ add(role + "_scope_spend", f"Explicit {role} scopes account for ${role_cost:.2f} observed spend.",
544
+ "Inspect the most expensive retry scope and repair its repeatable cause before broadening reruns."
545
+ if role == "retry" else "Inspect repeated review scope and the regressions it catches before changing review depth.",
546
+ "The targeted failure passes and subsequent comparable outcomes need less retry spend."
547
+ if role == "retry" else "The same quality checks pass and no verified regression follows a narrower review.",
548
+ role_cost, affected)
549
+ for finding in sorted({tag for row in visible for tag in row["findings"]}):
550
+ affected = [row for row in visible if finding in row["findings"]]
551
+ observed = [row["observed_cost_usd"] for row in affected if row["observed_cost_usd"] is not None]
552
+ if len(affected) < 2:
553
+ continue
554
+ cost = money(sum(observed)) if observed else None
555
+ add("recurring:" + finding,
556
+ f"{finding} appears in {len(affected)} declared outcomes"
557
+ + (f" associated with ${cost:.2f} observed spend." if cost is not None else "; associated spend is unknown."),
558
+ "Open the linked verification evidence, reproduce the shared failure and implement one focused repair.",
559
+ "The original failure passes and the next comparable cohort shows its recurrence and unit cost.",
560
+ cost, affected)
561
+ findings[-1]["coverage"].update(affected_outcomes=len(affected), priced_outcomes=len(observed),
562
+ limitation="Associated spend is not causal or recoverable waste; finding classes overlap and are not additive.")
563
+ interventions = intervention_rows(intervention_history, outcome_history, joined, sr, records, conflicts, ctx)
564
+ for intervention in interventions:
565
+ if intervention.get("quality_regression"):
566
+ add("intervention_quality:" + intervention["id"],
567
+ f"{intervention['id']} has a failed-outcome rate increase or a reopened outcome.",
568
+ intervention["recommendation"],
569
+ "Reproduce the failed quality check; record keep, revise or revert only with explicit verification evidence.",
570
+ affected=[row for row in visible if row["id"] in intervention_history["latest"][intervention["id"]]["body"]["post"]])
571
+ findings.sort(key=lambda item: (not item["code"].startswith("intervention_quality:"),
572
+ item["observed_cost_usd"] is None, -(item["observed_cost_usd"] or 0), item["code"]))
573
+ return {"outcome_coverage": outcome_history["coverage"], "intervention_coverage": intervention_history["coverage"],
574
+ "unit_economics": total, "cohorts": cohorts, "outcomes": sorted(public_rows, key=lambda row: row["id"]),
575
+ "threads": by_thread,
576
+ "thread_attribution": {"linked": sum(bool(row["thread"]) for row in visible),
577
+ "unlinked": sum(not row["thread"] for row in visible),
578
+ "rule": "one primary Thread per outcome; no inferred joins or lineage cost rollups"},
579
+ "interventions": interventions,
580
+ "findings": findings}
581
+
582
+
583
+ def read_manifest(path):
584
+ try:
585
+ fd = os.open(Path(path), os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
586
+ with os.fdopen(fd, "rb") as stream:
587
+ if not stat.S_ISREG(os.fstat(stream.fileno()).st_mode):
588
+ raise ValueError("manifest must be a regular file")
589
+ raw = stream.read(LIMIT + 1)
590
+ if len(raw) > LIMIT:
591
+ raise ValueError("manifest exceeds 64 KiB")
592
+ return json.loads(raw)
593
+ except (OSError, UnicodeError, json.JSONDecodeError) as exc:
594
+ raise ValueError("manifest could not be read as a bounded JSON file") from exc
595
+
596
+
597
+ def record(sr, ctx, manifest, kind):
598
+ ledger = OUTCOMES if kind == "outcome" else INTERVENTIONS
599
+ body = outcome_body(manifest) if kind == "outcome" else intervention_body(manifest)
600
+ current_session = sr.gate_session(ctx.root)
601
+ if not current_session or not ctx.actor_id:
602
+ raise ValueError("native session and Runtime actor identity are required")
603
+ event = {}
604
+ def validate():
605
+ if not ctx.enabled() or not ctx.actor_id or ctx.held(current_session):
606
+ raise ValueError("record controls withhold this event")
607
+ history = read_history(sr, ctx.memory, ledger)
608
+ if history["coverage"] not in {"complete", "missing"}:
609
+ raise ValueError("ledger coverage is incomplete; repair it before appending")
610
+ previous = history["latest"].get(body["id"])
611
+ stored = dict(body)
612
+ if kind == "outcome":
613
+ if any(ctx.held(row["session"]) for row in body["sessions"]):
614
+ raise ValueError("record controls withhold a contributing session")
615
+ records, _ = record_index(sr, ctx.memory)
616
+ for scope in body["sessions"]:
617
+ meta = records.get(scope["session"], {})
618
+ if meta.get("detail") == "stub":
619
+ raise ValueError("record controls withhold a contributing session")
620
+ included = meta.get("cost_sessions")
621
+ if isinstance(included, list) and any(isinstance(sid, str)
622
+ and (ctx.held(sid) or records.get(sid, {}).get("detail") == "stub") for sid in included):
623
+ raise ValueError("record controls withhold a contributing session")
624
+ if previous and previous["body"]["cohort"] != body["cohort"]:
625
+ raise ValueError("an outcome's cohort is immutable; use a successor outcome for changed scope")
626
+ totals = defaultdict(Decimal)
627
+ for old in history["latest"].values():
628
+ if old["body"]["id"] != body["id"]:
629
+ for scope in old["body"]["sessions"]:
630
+ totals[scope["session"]] += Decimal(scope["share"])
631
+ for scope in body["sessions"]:
632
+ totals[scope["session"]] += Decimal(scope["share"])
633
+ if any(value > 1 for value in totals.values()):
634
+ raise ValueError("session shares would exceed one; append explicit corrected allocations first")
635
+ else:
636
+ outcomes = read_history(sr, ctx.memory, OUTCOMES)
637
+ if outcomes["coverage"] != "complete":
638
+ raise ValueError("complete outcome history is required for an intervention")
639
+ records, _ = record_index(sr, ctx.memory)
640
+ for item in body["baseline"] + body["post"]:
641
+ outcome = outcomes["latest"].get(item)
642
+ if outcome is None or outcome["body"]["cohort"] != body["cohort"]:
643
+ raise ValueError("every baseline and post outcome must exist in the same cohort")
644
+ if any(ctx.held(scope["session"]) for scope in outcome["body"]["sessions"]):
645
+ raise ValueError("record controls withhold a contributing outcome")
646
+ for scope in outcome["body"]["sessions"]:
647
+ meta = records.get(scope["session"], {})
648
+ included = meta.get("cost_sessions")
649
+ if (meta.get("detail") == "stub"
650
+ or (isinstance(included, list)
651
+ and any(isinstance(sid, str) and (ctx.held(sid) or records.get(sid, {}).get("detail") == "stub")
652
+ for sid in included))):
653
+ raise ValueError("record controls withhold a contributing outcome")
654
+ if any(outcomes["latest"][item]["body"]["status"] == "open" for item in body["baseline"]):
655
+ raise ValueError("baseline outcomes must have a verified terminal disposition")
656
+ if previous:
657
+ old = previous["body"]
658
+ for sid in old["baseline_records"]:
659
+ meta = records.get(sid, {})
660
+ included = meta.get("cost_sessions")
661
+ if (ctx.held(sid) or meta.get("detail") == "stub"
662
+ or (isinstance(included, list) and any(isinstance(item, str)
663
+ and (ctx.held(item) or records.get(item, {}).get("detail") == "stub") for item in included))):
664
+ raise ValueError("record controls withhold a frozen baseline")
665
+ if any(old[field] != body[field] for field in ("cohort", "finding", "change", "baseline")):
666
+ raise ValueError("intervention cohort, finding, change and baseline are immutable")
667
+ if not set(old["post"]) <= set(body["post"]):
668
+ raise ValueError("post outcomes cannot be removed from an intervention")
669
+ started = min(event["at"] for event in history["events"].values()
670
+ if event["body"]["id"] == body["id"])
671
+ for item in body["post"]:
672
+ declared = min(event["at"] for event in outcomes["events"].values()
673
+ if event["body"]["id"] == item)
674
+ if datetime.fromisoformat(declared) <= datetime.fromisoformat(started):
675
+ raise ValueError("post outcomes must first be declared after intervention registration")
676
+ stored["baseline_events"] = old["baseline_events"]
677
+ stored["baseline_records"] = old["baseline_records"]
678
+ else:
679
+ if body["post"]:
680
+ raise ValueError("register the baseline before linking post-change outcomes")
681
+ stored["baseline_events"] = {item: outcomes["latest"][item]["event_id"] for item in body["baseline"]}
682
+ included = {scope["session"] for item in body["baseline"]
683
+ for scope in outcomes["latest"][item]["body"]["sessions"]}
684
+ stored["baseline_records"] = {sid: record_digest(records.get(sid)) for sid in sorted(included)}
685
+ if previous and previous["body"] == stored:
686
+ event.update(previous)
687
+ return False
688
+ event.update(schema=SCHEMAS[ledger], previous=previous["event_id"] if previous else None,
689
+ at=datetime.now(timezone.utc).isoformat(timespec="microseconds"), by=ctx.actor_id,
690
+ session=current_session, body=stored)
691
+ event["event_id"] = hashlib.sha256(canonical(event).encode()).hexdigest()
692
+ return True
693
+ sr.append_ledger(ctx, ledger, event, f"chore(sessions): record {kind} {body['id']}", validate=validate)
694
+ return {"id": body["id"], "event_id": event["event_id"], "status": "recorded"}
695
+
696
+
697
+ def command(sr, ctx, argv, kind="outcome"):
698
+ parser = argparse.ArgumentParser(prog=f"records.sh {kind}")
699
+ sub = parser.add_subparsers(dest="action", required=True)
700
+ write = sub.add_parser("record")
701
+ write.add_argument("--file", required=True)
702
+ report = sub.add_parser("report")
703
+ report.add_argument("--json", action="store_true")
704
+ args = parser.parse_args(argv)
705
+ if not ctx.enabled():
706
+ print(json.dumps({"status": "disabled", "reason": "session-records-off"}))
707
+ return 0
708
+ if args.action == "record":
709
+ print(json.dumps(record(sr, ctx, read_manifest(args.file), kind)))
710
+ return 0
711
+ data = scorecard(sr, ctx.memory, ctx)
712
+ selected = data if kind == "outcome" else {"coverage": data["intervention_coverage"], "interventions": data["interventions"]}
713
+ if args.json:
714
+ print(json.dumps(selected))
715
+ elif kind == "intervention":
716
+ for row in selected["interventions"]:
717
+ print("withheld intervention" if row.get("status") == "withheld"
718
+ else f"{row['id']}: {row['checkpoint']} · {row['recommendation']}")
719
+ if not selected["interventions"]:
720
+ print("No interventions recorded.")
721
+ else:
722
+ economics_data = data["unit_economics"]
723
+ cost = economics_data["cost_per_accepted_outcome_usd"]
724
+ print(f"Declared outcomes: {economics_data['outcomes']} · accepted: {economics_data['accepted_outcomes']}")
725
+ print("Cost per accepted outcome: " + (f"${cost:.2f}" if cost is not None else "unavailable"))
726
+ print(f"Cost coverage: {economics_data['complete_outcomes']}/{economics_data['outcomes']} complete")
727
+ for finding in data["findings"]:
728
+ print(f"{finding['observation']}\n→ {finding['action']}")
729
+ return 0