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
@@ -18,6 +18,205 @@
18
18
  # HEALTH_*, FRAMEWORK_VERSION, TIME_OF_DAY, EGREGORE_SESSION_ID,
19
19
  # FIRST_SESSION, DISPLAY_NAME_STATE, DASHBOARD_URL
20
20
 
21
+ # Session records reuse the index refreshed by the detached sweep.
22
+ # No transcript is opened by a greeting.
23
+ # Personal controls always win over the organization default.
24
+ _print_session_records() {
25
+ [ -n "${HOME:-}" ] || return 0
26
+ command -v python3 >/dev/null 2>&1 || return 0
27
+ python3 - "${CONFIG:-$SCRIPT_DIR/egregore.json}" "$STATE_FILE" "$SCRIPT_DIR" \
28
+ "${EGREGORE_MEMORY_ROOT:-$SCRIPT_DIR/memory}" "${AUTHOR:-}" <<'RECORDS_PY' 2>/dev/null || true
29
+ import datetime as dt
30
+ import json
31
+ import os
32
+ from pathlib import Path
33
+ import re
34
+ import stat
35
+ import sys
36
+
37
+ config_path, state_path, project_root, memory_root, fallback_author = sys.argv[1:]
38
+ def read_json(path):
39
+ try:
40
+ value = json.loads(Path(path).read_text())
41
+ return value if isinstance(value, dict) else {}
42
+ except (OSError, ValueError):
43
+ return {}
44
+
45
+ config = read_json(config_path)
46
+ policy = config.get("features", {}).get("session_records", {})
47
+ if not isinstance(policy, dict) or policy.get("enabled") is not True:
48
+ raise SystemExit(0)
49
+ state = read_json(state_path)
50
+ author = state.get("github_username", fallback_author)
51
+ sys.path.insert(0, str(Path(project_root) / "bin" / "lib"))
52
+ try:
53
+ from session_trail import instance_prefix, valid_trail_folder
54
+ from session_record import collect_digest, metadata, sessions_directory, classify_record, Context, native_record_reader
55
+ except ImportError:
56
+ raise SystemExit(0)
57
+ prefix = instance_prefix(project_root)
58
+ projects = Path(os.environ["HOME"]) / ".claude" / "projects"
59
+ trail_root = Path(os.environ["HOME"]) / ".egregore" / "trails" / prefix
60
+ cache = {}
61
+ cache_path = trail_root / "index.json"
62
+ try:
63
+ if (valid_trail_folder(trail_root, projects, prefix)
64
+ and stat.S_ISREG(cache_path.lstat().st_mode)
65
+ and cache_path.resolve().is_relative_to(trail_root.resolve())):
66
+ cache = read_json(cache_path)
67
+ except OSError:
68
+ pass
69
+ today = dt.datetime.now().astimezone().date()
70
+
71
+ def clean(value):
72
+ return re.sub(r"[\x00-\x1f\x7f]", " ", str(value)).strip()
73
+
74
+ def header(text, name):
75
+ match = re.search(r"^\*\*" + re.escape(name) + r"\*\*:\s*(.*)$", text, re.M)
76
+ return match.group(1).strip() if match else ""
77
+
78
+ def frontmatter(text, name):
79
+ match = re.search(r"^" + re.escape(name) + r":\s*(.*)$", text.partition("\n---")[0], re.M)
80
+ return match.group(1).strip().strip('"\'') if match else ""
81
+
82
+ def bullets(text, name):
83
+ match = re.search(r"^## " + re.escape(name) + r"\s*\n(.*?)(?=^## |\Z)", text, re.M | re.S)
84
+ return len(re.findall(r"^\s*[-*]\s+", match.group(1), re.M)) if match else 0
85
+
86
+ records = []
87
+ digest_records = []
88
+ recorded_ids = set()
89
+ scanned = 0
90
+ # Month filenames sort chronologically; a bounded newest-first scan keeps the
91
+ # greeting predictable even when an organization has years of sessions.
92
+ sessions_root = sessions_directory(memory_root)
93
+ try:
94
+ months = sorted((p for p in sessions_root.iterdir()
95
+ if stat.S_ISDIR(p.lstat().st_mode)
96
+ and p.resolve().is_relative_to(sessions_root)), reverse=True)[:120] if sessions_root else []
97
+ except OSError:
98
+ months = []
99
+ for month in months:
100
+ try:
101
+ paths = sorted(month.glob("*.md"), reverse=True)
102
+ except OSError:
103
+ continue
104
+ for path in paths[:2000 - scanned]:
105
+ scanned += 1
106
+ try:
107
+ if not stat.S_ISREG(path.lstat().st_mode) or not path.resolve().is_relative_to(sessions_root):
108
+ continue
109
+ with path.open() as source:
110
+ text = source.read(131072)
111
+ except (OSError, UnicodeError):
112
+ continue
113
+ if frontmatter(text, "kind") != "session-record":
114
+ continue
115
+ if state.get("session_records_noticed") is True:
116
+ digest_records.append((path, metadata(text), text))
117
+ if classify_record(metadata(text)) != "human-session":
118
+ continue
119
+ date = header(text, "Date") or frontmatter(text, "date")
120
+ owner = header(text, "Author") or frontmatter(text, "from")
121
+ session_id = header(text, "Session") or frontmatter(text, "session")
122
+ if session_id:
123
+ recorded_ids.add(session_id)
124
+ topic = frontmatter(text, "topic")
125
+ if not topic:
126
+ title = re.search(r"^# (?:Session: )?(.*)$", text, re.M)
127
+ topic = title.group(1) if title else "session"
128
+ commits = re.search(r"\b(\d+) commits\b", text)
129
+ records.append({"date": date, "author": owner, "topic": topic,
130
+ "duration": header(text, "Duration") or frontmatter(text, "duration") or "unknown",
131
+ "commits": int(commits.group(1)) if commits else 0,
132
+ "decisions": bullets(text, "Decided"), "open": bullets(text, "Open")})
133
+ if scanned >= 2000:
134
+ break
135
+
136
+ if state.get("session_records_noticed") is True:
137
+ # Open items are carried by the reflex line under this block
138
+ # (bin/reflexes/10-open-items.sh), so the digest skips the carry here.
139
+ has_native = any(classify_record(meta) != "human-session" for _, meta, _ in digest_records)
140
+ actor_id, reader = native_record_reader(Context(project_root)) if has_native else ("", None)
141
+ digest = collect_digest(memory_root, author=author, person=state.get("display_name", ""),
142
+ days=1, today=today, records=digest_records, carry_days=0,
143
+ actor_id=actor_id, native_reader=reader)
144
+ if digest["sessions"]:
145
+ print(f" ◦ records · yesterday {digest['sessions']} ({digest['yours']} yours) · {len(digest['decided'])} decided · {len(digest['open'])} open · /records digest")
146
+ def short(value, limit):
147
+ value = clean(value)
148
+ return value if len(value) <= limit else value[:limit - 1] + "…"
149
+ def preview(line, who):
150
+ suffix = " — " + short(who, 40)
151
+ print(" - " + short(line, 120 - 6 - len(suffix)) + suffix)
152
+ for item in digest["decided"][:3]:
153
+ preview(item["line"], item["who"])
154
+ if digest["spirit_intents"]:
155
+ noun = "intent" if digest["spirit_intents"] == 1 else "intents"
156
+ print(f" ◦ spirit records · {digest['spirit_intents']} {noun} ({digest['your_spirit_intents']} yours) · partial coverage · /records digest")
157
+ if digest["unsupported_native"]:
158
+ print(f" ◦ {digest['unsupported_native']} unsupported spirit records · metadata unavailable")
159
+ if digest["held_records"]:
160
+ print(f" ◦ {digest['held_records']} held records · details withheld")
161
+ if cache.get("waiting_push"):
162
+ print(" ◦ 1 records commit is waiting to push · /records status --push")
163
+ raise SystemExit(0)
164
+
165
+ decider = clean(policy.get("decided_by", "your organization"))
166
+ decided_on = clean(policy.get("decided_on", ""))
167
+ personal_names = {str(value).casefold() for value in (author, state.get("display_name", "")) if value}
168
+ if str(policy.get("decided_by", "")).casefold() in personal_names:
169
+ print(f"◇ Session records are on for {clean(config.get('github_org', ''))}/{clean(config.get('repo_name', 'egregore'))} · your decision, {decided_on}")
170
+ else:
171
+ print(f"◇ Session records are new · {decider} turned them on for this egregore on {decided_on}")
172
+ if state.get("session_records_enabled") is False:
173
+ print(" Your session records are off. /records on turns them back on.")
174
+ else:
175
+ print(" Each session now leaves a record in memory/sessions: asked, decided, left open,")
176
+ print(" touched, cost. Asks are paraphrased, never quoted. The full trail stays on this")
177
+ model_on = policy.get("model", "haiku") != "none" and state.get("session_records_model_call") is not False
178
+ if model_on:
179
+ print(" machine as a file; your own Claude account reads it once to write the outline,")
180
+ print(' through the Claude service. Say "off the record" in any session to hold it.')
181
+ else:
182
+ print(' machine. Say "off the record" in any session to hold it.')
183
+ print(" /records changes yours.")
184
+ if "unrecorded_count" in cache:
185
+ count = int(cache.get("unrecorded_count", 0))
186
+ try:
187
+ days = max(0, (today - dt.date.fromisoformat(cache["oldest_date"][:10])).days)
188
+ except (KeyError, TypeError, ValueError):
189
+ days = int(cache.get("days", 0))
190
+ if records:
191
+ newest = max(records, key=lambda row: row["date"])
192
+ if newest["author"] != author:
193
+ print(f" Here is one of {clean(newest['author'])}'s: {clean(newest['date'][:10])} · {clean(newest['topic'])} · {clean(newest['duration'])} · {newest['commits']} commits · {newest['decisions']} decisions · {newest['open']} open items")
194
+ if "unrecorded_count" in cache:
195
+ print(f" {count} past sessions of yours are still on this machine ({days} days). Record them?")
196
+ print(" y · show one · no")
197
+ else:
198
+ print(" Past sessions are being counted. The count appears at your next start.")
199
+ if cache.get("waiting_push"):
200
+ print(" ◦ 1 records commit is waiting to push · /records status --push")
201
+ RECORDS_PY
202
+ }
203
+
204
+ # The reflexes: probes in bin/reflexes/ that each print a fact about
205
+ # yesterday, or nothing. One runner, fail-soft, shared by every startup card.
206
+ # See bin/reflexes/README.md.
207
+ _print_reflexes() {
208
+ [ -f "$SCRIPT_DIR/bin/reflexes.sh" ] || return 0
209
+ bash "$SCRIPT_DIR/bin/reflexes.sh" \
210
+ --memory "${EGREGORE_MEMORY_ROOT:-$SCRIPT_DIR/memory}" \
211
+ --author "${AUTHOR:-}" --harness "${EGREGORE_HARNESS:-claude}" 2>/dev/null || true
212
+ }
213
+
214
+ # Other startup cards reuse this records block without rendering the Claude
215
+ # greeting or running its surrounding notices and bookkeeping.
216
+ if [ "${EGREGORE_GREETING_RECORDS_ONLY:-}" = "1" ]; then
217
+ return 0
218
+ fi
219
+
21
220
  # --- Output greeting for Claude to display ---
22
221
  cat << 'GREETING'
23
222
 
@@ -115,6 +314,10 @@ if [ -n "${SETTINGS_POSTURE_HELD:-}" ]; then
115
314
  echo " ◇ boundary posture was set to ${SETTINGS_POSTURE_HELD} elsewhere — relaxing access needs your confirm: bash bin/settings.sh posture ${SETTINGS_POSTURE_HELD}"
116
315
  fi
117
316
 
317
+
318
+ _print_session_records
319
+ _print_reflexes
320
+
118
321
  # --- Footer: health + repos + memory (tertiary) ---
119
322
  echo "$SEPARATOR"
120
323
 
@@ -126,7 +329,7 @@ _render_health_footer || true
126
329
  # Runtime update summary — cached durable state only, never a live probe, so
127
330
  # session start stays fast. One compact line; silent when nothing is staged.
128
331
  # Keyed by stable org identity composed with the main installation path
129
- # (mirrors egregore_runtime.upgrade.instance_key); no org_id → no summary.
332
+ # (mirrors egregore_runtime.installation.installation_key); no org_id → no summary.
130
333
  _UPGRADE_ORG=$(jq -r '.org_id // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
131
334
  _UPGRADE_MAIN=$(cd "${MAIN_PROJECT_DIR:-$SCRIPT_DIR}" 2>/dev/null && pwd -P || true)
132
335
  if [ -n "$_UPGRADE_ORG" ] && [ -n "$_UPGRADE_MAIN" ]; then
@@ -372,6 +575,43 @@ if [ "$ALIAS_VERSION" -lt 2 ] 2>/dev/null; then
372
575
  fi
373
576
  fi
374
577
 
578
+ # --- Card close: first-session welcome or tutorial tip (visible) ---
579
+ # Everything the user should see renders before the hidden context below, so
580
+ # a harness that displays the card directly (bin/lib/card-delivery.sh) shows
581
+ # all of it. In that mode the card also asks the opening question itself,
582
+ # the same way bin/codex-session-start.sh ends the Codex and Pi card.
583
+ if [ -z "$FIRST_SESSION" ] && [ -f "$STATE_FILE" ]; then
584
+ FIRST_SESSION=$(jq -r '.first_session // false' "$STATE_FILE" 2>/dev/null)
585
+ fi
586
+
587
+ if [ "$FIRST_SESSION" = "true" ]; then
588
+ echo ""
589
+ echo " Welcome! This is your first session."
590
+ if [ "${EGREGORE_CARD_DELIVERY:-text}" = "harness" ]; then
591
+ echo ""
592
+ echo " Want a quick tour first (/onboarding), or jump straight in?"
593
+ fi
594
+ # Clear the flag so it only shows once
595
+ jq '.first_session = false' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
596
+ else
597
+ # --- Tutorial tip (if onboarding done but tutorial not) ---
598
+ TUTORIAL_COMPLETE="true"
599
+ if [ -f "$STATE_FILE" ]; then
600
+ TUTORIAL_COMPLETE=$(jq -r '.tutorial_complete // false' "$STATE_FILE" 2>/dev/null || echo "true")
601
+ fi
602
+
603
+ if [ "$TUTORIAL_COMPLETE" != "true" ]; then
604
+ # A tip that has not been acted on after a few sessions is noise; the
605
+ # ledger retires it after NOTICE_TIP_MAX showings.
606
+ _notice tutorial-tip tip "tutorial" "tutorial" " Tip: Run /tutorial to learn the core loop."
607
+ fi
608
+ _notice_flush
609
+ if [ "${EGREGORE_CARD_DELIVERY:-text}" = "harness" ]; then
610
+ echo ""
611
+ echo " What are you working on?"
612
+ fi
613
+ fi
614
+
375
615
  echo ""
376
616
 
377
617
  # --- Session context (hidden, for Claude) ---
@@ -487,34 +727,21 @@ bash "$SCRIPT_DIR/bin/startup-check.sh" >/dev/null 2>&1 &
487
727
  # Clean up temp files
488
728
  rm -rf "$CTX_DIR"
489
729
 
490
- # --- First session welcome ---
491
- if [ -z "$FIRST_SESSION" ] && [ -f "$STATE_FILE" ]; then
492
- FIRST_SESSION=$(jq -r '.first_session // false' "$STATE_FILE" 2>/dev/null)
493
- fi
494
-
495
- if [ "$FIRST_SESSION" = "true" ]; then
496
- echo ""
497
- echo " Welcome! This is your first session."
498
- echo ""
730
+ # --- Instruction for the model ---
731
+ # Harness delivery: the user already sees the scripted card, question
732
+ # included, so the model must not retype it. Text delivery (a harness that
733
+ # cannot display hook output directly) keeps the display instruction. The
734
+ # branch rule that used to follow is gone: CLAUDE.md's "Before Project
735
+ # Changes" section is the one branching contract.
736
+ echo ""
737
+ if [ "${EGREGORE_CARD_DELIVERY:-text}" = "harness" ]; then
738
+ if [ "$FIRST_SESSION" = "true" ]; then
739
+ echo "STARTUP CARD: The harness has already shown the user the startup card above, ending with an offer of a quick tour (/onboarding). Do not reprint, summarize, or narrate it. Treat the user's first message as their answer."
740
+ else
741
+ echo "STARTUP CARD: The harness has already shown the user the startup card above, ending with \"What are you working on?\". Do not reprint, summarize, or narrate it. Treat the user's first message as the answer. If it carries no request (the launcher submits \`start\` automatically), reply with one short line and no tool calls."
742
+ fi
743
+ elif [ "$FIRST_SESSION" = "true" ]; then
499
744
  echo "IMPORTANT: Display the above greeting exactly as-is (ASCII art + ornamented status). Then ask the user if they'd like a quick onboarding tour (run /onboarding), or if they want to jump straight in."
500
- # Clear the flag so it only shows once
501
- jq '.first_session = false' "$STATE_FILE" > "$STATE_FILE.tmp" && mv "$STATE_FILE.tmp" "$STATE_FILE"
502
745
  else
503
- # --- Tutorial tip (if onboarding done but tutorial not) ---
504
- TUTORIAL_COMPLETE="true"
505
- if [ -f "$STATE_FILE" ]; then
506
- TUTORIAL_COMPLETE=$(jq -r '.tutorial_complete // false' "$STATE_FILE" 2>/dev/null || echo "true")
507
- fi
508
-
509
- if [ "$TUTORIAL_COMPLETE" != "true" ]; then
510
- # A tip that has not been acted on after a few sessions is noise; the
511
- # ledger retires it after NOTICE_TIP_MAX showings.
512
- _notice tutorial-tip tip "tutorial" "tutorial" " Tip: Run /tutorial to learn the core loop."
513
- fi
514
- _notice_flush
515
-
516
- echo ""
517
746
  echo "IMPORTANT: Display the above greeting to the user exactly as-is (preserve the ASCII art formatting and ornamented status) on their first message. Then ask: What are you working on?"
518
- echo ""
519
- echo "BRANCH RULE: When the user responds with what they're working on, your FIRST action is to create a working branch: git fetch origin $BASE_BRANCH --quiet && git checkout --no-track -b dev/{author}/{topic-slug} origin/$BASE_BRANCH. Do this BEFORE any other work. Derive the topic slug from their description. If they ask a pure question with no work intent, skip branching."
520
747
  fi
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env python3
2
+ """Handoff metadata for many files in one process.
3
+
4
+ bin/lib/handoff-meta.sh extracts a handoff's author, topic and date with a
5
+ grep | sed | xargs pipeline per field, and the startup gather of handoffs
6
+ addressed to the user added an awk, a read loop and a jq per file: over a
7
+ hundred processes for five handoffs. This module reads each file once and
8
+ reproduces those pipelines' results, including xargs' quote and whitespace
9
+ handling and the echo it runs, so the files the gather writes are
10
+ unchanged.
11
+
12
+ handoff_meta.py addressed NAMES_OUT RICH_OUT [FILE...]
13
+
14
+ writes the gather's two files: NAMES_OUT holds the handoff names, RICH_OUT
15
+ their author, topic, date and Repo State table rows. bin/tests/
16
+ test-handoff-metadata.sh holds this module to the shell functions.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import re
24
+ import sys
25
+
26
+ # POSIX [[:space:]]. On macOS, sed in a UTF-8 locale also counts Unicode
27
+ # spaces (a no-break space after "**Author**:" is stripped there).
28
+ _ASCII_SPACE = frozenset(" \t\n\v\f\r")
29
+ _DARWIN = sys.platform == "darwin"
30
+
31
+
32
+ def _is_space(character: str) -> bool:
33
+ return character in _ASCII_SPACE or (_DARWIN and character.isspace())
34
+
35
+
36
+ def _lstrip_space(text: str) -> str:
37
+ position = 0
38
+ while position < len(text) and _is_space(text[position]):
39
+ position += 1
40
+ return text[position:]
41
+
42
+
43
+ def _echo_operands(arguments: list[str]) -> list[str]:
44
+ """What /bin/echo prints of its arguments, as xargs runs it."""
45
+ if _DARWIN:
46
+ # FreeBSD echo: only an exact first "-n" is an option, and a "\c"
47
+ # ending the last argument is dropped with the newline.
48
+ if arguments and arguments[0] == "-n":
49
+ arguments = arguments[1:]
50
+ if arguments and arguments[-1].endswith("\\c"):
51
+ arguments = [*arguments[:-1], arguments[-1][:-2]]
52
+ return arguments
53
+ # GNU echo: leading arguments made only of n, e and E are options.
54
+ while arguments and re.fullmatch(r"-[neE]+", arguments[0]):
55
+ arguments = arguments[1:]
56
+ return arguments
57
+
58
+
59
+ def _xargs(text: str) -> str:
60
+ """`printf '%s\\n' "$text" | xargs`, as captured by $(...).
61
+
62
+ Blanks and newlines separate arguments, quotes group them, a backslash
63
+ outside quotes escapes the next character, and an unterminated quote
64
+ makes xargs fail before running echo.
65
+ """
66
+ arguments: list[str] = []
67
+ argument: list[str] = []
68
+ started = False
69
+ quote = ""
70
+ characters = iter(text + "\n")
71
+ for character in characters:
72
+ if quote:
73
+ if character == quote:
74
+ quote = ""
75
+ elif character == "\n":
76
+ return ""
77
+ else:
78
+ argument.append(character)
79
+ continue
80
+ if character in " \t\n":
81
+ if started:
82
+ arguments.append("".join(argument))
83
+ argument, started = [], False
84
+ continue
85
+ started = True
86
+ if character in "'\"":
87
+ quote = character
88
+ elif character == "\\":
89
+ escaped = next(characters, None)
90
+ if escaped is None:
91
+ break
92
+ argument.append(escaped)
93
+ else:
94
+ argument.append(character)
95
+ if quote:
96
+ return ""
97
+ if started:
98
+ arguments.append("".join(argument))
99
+ return " ".join(_echo_operands(arguments)).rstrip("\n")
100
+
101
+
102
+ def _bash_echo(value: str) -> str:
103
+ """`echo "$value"` in bash: an argument of only n, e and E is an option."""
104
+ return "" if re.fullmatch(r"-[neE]+", value) else value
105
+
106
+
107
+ def _first(lines: list[str], prefix: str) -> str | None:
108
+ return next((line for line in lines if line.startswith(prefix)), None)
109
+
110
+
111
+ def _after(lines: list[str], prefix: str) -> str:
112
+ """grep -m1 "^prefix" | sed "s/^prefix[[:space:]]*//" | xargs"""
113
+ line = _first(lines, prefix)
114
+ return "" if line is None else _xargs(_lstrip_space(line[len(prefix):]))
115
+
116
+
117
+ def _markdown_field(lines: list[str], key: str) -> str:
118
+ return _after(lines, f"**{key}**:") or _after(lines, f"**{key}:**")
119
+
120
+
121
+ def _yaml_field(lines: list[str], key: str) -> str:
122
+ # sed -n '/^---$/,/^---$/p' prints every such range, not only the first.
123
+ inside = False
124
+ ranged: list[str] = []
125
+ for line in lines:
126
+ if inside:
127
+ ranged.append(line)
128
+ inside = line != "---"
129
+ elif line == "---":
130
+ ranged.append(line)
131
+ inside = True
132
+ line = _first(ranged, f"{key}:")
133
+ if line is None:
134
+ return ""
135
+ value = _lstrip_space(line[len(key) + 1:])
136
+ for mark in ('"', "'"):
137
+ value = value[1:] if value.startswith(mark) else value
138
+ value = value[:-1] if value.endswith(mark) else value
139
+ return _xargs(value)
140
+
141
+
142
+ def _name(path: str) -> str:
143
+ name = os.path.basename(path)
144
+ return name[:-3] if name.endswith(".md") and name != ".md" else name
145
+
146
+
147
+ def _author_from_filename(path: str) -> str:
148
+ match = re.match(r"[0-9]{2}-([^-]+)-", _name(path)) or re.match(
149
+ r"[0-9]{4}-[0-9]{2}-[0-9]{2}-([^-]+)-", _name(path)
150
+ )
151
+ return match.group(1) if match else ""
152
+
153
+
154
+ def _date_from_filename(path: str) -> str:
155
+ name = _name(path)
156
+ directory = os.path.basename(os.path.dirname(path))
157
+ day = re.match(r"([0-9]{2})-", name)
158
+ if re.fullmatch(r"[0-9]{4}-[0-9]{2}", directory) and day:
159
+ return f"{directory}-{day.group(1)}"
160
+ dated = re.match(r"([0-9]{4}-[0-9]{2}-[0-9]{2})-", name)
161
+ return dated.group(1) if dated else ""
162
+
163
+
164
+ def author(lines: list[str], path: str) -> str:
165
+ found = (
166
+ _markdown_field(lines, "Author")
167
+ or _markdown_field(lines, "From")
168
+ or _after(lines, "From:")
169
+ or _yaml_field(lines, "author")
170
+ or _yaml_field(lines, "from")
171
+ or _author_from_filename(path)
172
+ )
173
+ # sed 's/[[:space:]]*→.*//'
174
+ arrow = found.find("→")
175
+ if arrow >= 0:
176
+ while arrow > 0 and _is_space(found[arrow - 1]):
177
+ arrow -= 1
178
+ found = found[:arrow]
179
+ return _xargs(found)
180
+
181
+
182
+ def topic(lines: list[str]) -> str:
183
+ heading = _first(lines, "# ")
184
+ found = ""
185
+ if heading is not None:
186
+ text = heading[2:]
187
+ if text.startswith("Handoff:"):
188
+ text = _lstrip_space(text[len("Handoff:"):])
189
+ found = _xargs(text)
190
+ return found or _yaml_field(lines, "topic")
191
+
192
+
193
+ def date(lines: list[str], path: str) -> str:
194
+ return (
195
+ _markdown_field(lines, "Date")
196
+ or _after(lines, "Date:")
197
+ or _yaml_field(lines, "date")
198
+ or _date_from_filename(path)
199
+ )
200
+
201
+
202
+ def _json_string(value: str) -> str:
203
+ # jq's compact output: raw UTF-8, with DEL escaped as well.
204
+ return json.dumps(value, ensure_ascii=False).replace("\x7f", "\\u007f")
205
+
206
+
207
+ def _cell(fields: list[str], position: int) -> str:
208
+ return _xargs(_bash_echo(fields[position] if position < len(fields) else ""))
209
+
210
+
211
+ def repo_state(lines: list[str]) -> str:
212
+ """The `## Repo State` table as the JSON array the gather builds.
213
+
214
+ Each row was printed into JSON text and parsed with `jq -s`, so a cell
215
+ jq could not parse empties the whole table, as it did there.
216
+ """
217
+ rows: list[str] = []
218
+ found = False
219
+ for line in lines:
220
+ if line.startswith("## Repo State"):
221
+ found = True
222
+ continue
223
+ if not found:
224
+ continue
225
+ if line.startswith("#"):
226
+ break
227
+ if len(line) > 1 and line[0] == "|" and line[1] != "-" and not line.startswith("| Repo"):
228
+ rows.append(line)
229
+ entries: list[str] = []
230
+ for row in rows:
231
+ fields = row.split("|")
232
+ repository, branch, base = _cell(fields, 1), _cell(fields, 2), _cell(fields, 4)
233
+ pull = _cell(fields, 3)
234
+ pull = (pull[1:] if pull.startswith("#") else pull).replace("—", "", 1)
235
+ if not repository:
236
+ continue
237
+ number = "null"
238
+ if re.fullmatch(r"[+-]?[0-9]+", pull) and -(2**63) <= int(pull) < 2**63:
239
+ number = ("-" if pull.startswith("-") else "") + (pull.lstrip("+-").lstrip("0") or "0")
240
+ try:
241
+ text = [json.loads(f'"{value}"') for value in (repository, branch, base)]
242
+ except ValueError:
243
+ return "[]"
244
+ entries.append(
245
+ f'{{"repo":{_json_string(text[0])},"branch":{_json_string(text[1])},'
246
+ f'"pr":{number},"base":{_json_string(text[2])}}}'
247
+ )
248
+ return "[" + ",".join(entries) + "]"
249
+
250
+
251
+ def _lines(path: str) -> list[str]:
252
+ try:
253
+ with open(path, "rb") as handle:
254
+ content = handle.read().decode("utf-8", errors="replace")
255
+ except OSError:
256
+ return []
257
+ lines = content.split("\n")
258
+ if lines and lines[-1] == "":
259
+ lines.pop()
260
+ return lines
261
+
262
+
263
+ def addressed(paths: list[str]) -> tuple[str, str]:
264
+ names: list[str] = []
265
+ rich: list[str] = []
266
+ for path in paths:
267
+ lines = _lines(path)
268
+ name = _name(path)
269
+ names.append(f'"{name}"')
270
+ try:
271
+ entry = (
272
+ f'{{"name":{_json_string(name)},"author":{_json_string(author(lines, path))},'
273
+ f'"topic":{_json_string(topic(lines))},"date":{_json_string(date(lines, path))},'
274
+ f'"repoState":{repo_state(lines)}}}'
275
+ )
276
+ except Exception: # one unreadable handoff keeps its place, not the list's
277
+ entry = f'{{"name":{_json_string(name)},"author":"","topic":"","date":"","repoState":[]}}'
278
+ rich.append(entry)
279
+ return "[" + ",".join(names) + "]", "[" + ",".join(rich) + "]"
280
+
281
+
282
+ def main(arguments: list[str]) -> int:
283
+ if len(arguments) < 3 or arguments[0] != "addressed":
284
+ print("usage: handoff_meta.py addressed NAMES_OUT RICH_OUT [FILE...]", file=sys.stderr)
285
+ return 2
286
+ names, rich = addressed(arguments[3:])
287
+ for path, content in ((arguments[1], names), (arguments[2], rich)):
288
+ with open(path, "w", encoding="utf-8") as handle:
289
+ handle.write(content + "\n")
290
+ return 0
291
+
292
+
293
+ if __name__ == "__main__":
294
+ raise SystemExit(main(sys.argv[1:]))