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,487 @@
1
+ """Read-only capability discovery. Local files and remote authority stay distinct."""
2
+ from __future__ import annotations
3
+ import hashlib
4
+ import http.client
5
+ import ipaddress
6
+ import json
7
+ import os
8
+ from pathlib import Path
9
+ import re
10
+ import stat
11
+ import subprocess
12
+ import sys
13
+ from datetime import datetime, timezone, timedelta
14
+ from urllib.parse import urlencode, urlsplit
15
+
16
+ RUNTIMES = ('claude', 'codex', 'pi', 'prime')
17
+ FACETS = ('declared', 'installed', 'exposed', 'permitted', 'operational', 'verified', 'adopted')
18
+ HASH = re.compile(r'^[a-f0-9]{64}$')
19
+ ID = re.compile(r'^[a-z][a-z0-9._/-]{0,159}$')
20
+ MAX_BYTES = 2_000_000
21
+ class DiscoveryError(ValueError):
22
+ """Safe, stable error without provider or local path details."""
23
+
24
+ def canonical(value):
25
+ try:
26
+ return json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False, allow_nan=False).encode('utf-8')
27
+ except (ValueError, TypeError, UnicodeError, RecursionError):
28
+ raise DiscoveryError('invalid-json') from None
29
+
30
+ def _text(value, maximum, *, blank=False):
31
+ if not isinstance(value, str): return False
32
+ try: size = len(value.encode('utf-8'))
33
+ except UnicodeError: return False
34
+ return size <= maximum and (blank or bool(value.strip())) and not any(ord(c) < 32 or ord(c) == 127 for c in value)
35
+
36
+ def _json_strings(value):
37
+ pending = [(value, 0)]; count = 0
38
+ while pending:
39
+ item, depth = pending.pop(); count += 1
40
+ if depth > 64 or count > 200000: raise DiscoveryError('invalid-json')
41
+ if isinstance(item, str):
42
+ try: item.encode('utf-8')
43
+ except UnicodeError: raise DiscoveryError('invalid-json') from None
44
+ elif isinstance(item, dict):
45
+ pending.extend((part, depth + 1) for pair in item.items() for part in pair)
46
+ elif isinstance(item, list): pending.extend((part, depth + 1) for part in item)
47
+
48
+ def _unique(pairs):
49
+ result = {}
50
+ for key, value in pairs:
51
+ if key in result:
52
+ raise DiscoveryError('invalid-json')
53
+ result[key] = value
54
+ return result
55
+
56
+ def parse_json(data):
57
+ try:
58
+ value = json.loads(data, object_pairs_hook=_unique, parse_constant=lambda _: (_ for _ in ()).throw(DiscoveryError('invalid-json')))
59
+ _json_strings(value)
60
+ return value
61
+ except (ValueError, UnicodeError, TypeError, RecursionError):
62
+ raise DiscoveryError('invalid-json') from None
63
+
64
+ def query_request(value):
65
+ if not isinstance(value, dict):
66
+ raise DiscoveryError('invalid-query')
67
+ op = value.get('operation')
68
+ fields = {'capabilities.list': {'operation'}, 'capabilities.find': {'operation', 'query'},
69
+ 'capabilities.describe': {'operation', 'id'}, 'capabilities.explain': {'operation', 'id'},
70
+ 'system.explain': {'operation', 'id'} if 'id' in value else {'operation'}}
71
+ if not isinstance(op, str) or op not in fields or set(value) != fields[op]:
72
+ raise DiscoveryError('invalid-query')
73
+ for field, maximum in [('query', 200), ('id', 160)]:
74
+ if field in value and not _text(value[field], maximum):
75
+ raise DiscoveryError('invalid-query')
76
+ return value
77
+
78
+ def _safe_relative(value):
79
+ if not _text(value, 4096) or value.startswith('/') or '\\' in value:
80
+ return False
81
+ parts=value.split('/')
82
+ denied={'.git','memory','.egregore','.spirit-workflow-state','tmp','node_modules','sessions','logs','auth.json','credentials.json','history.jsonl'}
83
+ return all(part and part not in ('.','..') and part not in denied and not part.startswith('.env') for part in parts)
84
+
85
+ def _read(file, maximum=MAX_BYTES, private=False):
86
+ # Descriptor-relative traversal prevents ancestor symlink/rename swaps from
87
+ # redirecting later opens. Each child is opened while its parent FD is held.
88
+ file = Path(os.path.abspath(file))
89
+ parent = os.open(file.anchor, os.O_RDONLY | os.O_DIRECTORY)
90
+ fd = None
91
+ try:
92
+ for part in file.parts[1:-1]:
93
+ child = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent)
94
+ os.close(parent); parent = child
95
+ fd = os.open(file.name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=parent)
96
+ st = os.fstat(fd)
97
+ if not stat.S_ISREG(st.st_mode) or st.st_nlink != 1 or st.st_size > maximum or private and (st.st_mode & 0o077):
98
+ raise DiscoveryError('unreadable-evidence')
99
+ data = os.read(fd, maximum + 1)
100
+ if len(data) > maximum:
101
+ raise DiscoveryError('unreadable-evidence')
102
+ return data
103
+ finally:
104
+ if fd is not None: os.close(fd)
105
+ os.close(parent)
106
+
107
+ def _json(file, maximum=MAX_BYTES, private=False):
108
+ return parse_json(_read(file, maximum, private))
109
+
110
+ def _snapshot(root, runtime):
111
+ raw = _read(root / 'bin' / 'data' / 'capabilities' / f'{runtime}.json')
112
+ value = parse_json(raw)
113
+ fields = {'schema', 'runtime', 'audience', 'provenance', 'items', 'files', 'digest'}
114
+ if not isinstance(value, dict) or set(value) != fields or value['schema'] != 'egregore.capability-snapshot/v1' or value['runtime'] != runtime or value['audience'] != 'oss' or value['provenance'] != 'build-projection-unattested':
115
+ raise DiscoveryError('invalid-snapshot')
116
+ if not HASH.fullmatch(str(value['digest'])) or hashlib.sha256(canonical({k:v for k,v in value.items() if k != 'digest'})).hexdigest() != value['digest']:
117
+ raise DiscoveryError('invalid-snapshot')
118
+ if not isinstance(value['files'], list) or len(value['files']) > 4096 or not isinstance(value['items'], list) or len(value['items']) > 512:
119
+ raise DiscoveryError('invalid-snapshot')
120
+ files = {}
121
+ for record in value['files']:
122
+ if not isinstance(record, dict) or set(record) != {'path', 'sha256'} or not _safe_relative(record['path']) or not isinstance(record['sha256'], str) or not HASH.fullmatch(record['sha256']) or record['path'] in files:
123
+ raise DiscoveryError('invalid-snapshot')
124
+ files[record['path']] = record['sha256']
125
+ ids = set()
126
+ for item in value['items']:
127
+ if not isinstance(item, dict) or set(item) != {'id', 'kind', 'summary', 'state_owner', 'requires', 'files', 'unmeasured_dependencies', 'contract', 'availability'} or not isinstance(item['id'], str) or not ID.fullmatch(item['id']) or item['id'] in ids or item['kind'] not in ('catalog-component','catalog-capability') or item['availability'] != 'oss' or item['contract'] != 'unknown':
128
+ raise DiscoveryError('invalid-snapshot')
129
+ for field in ('summary', 'state_owner'):
130
+ if not _text(item[field], 8000):
131
+ raise DiscoveryError('invalid-snapshot')
132
+ if not isinstance(item['files'], list) or any(not isinstance(file, str) or file not in files for file in item['files']) or not isinstance(item['requires'], list) or not isinstance(item['unmeasured_dependencies'], list):
133
+ raise DiscoveryError('invalid-snapshot')
134
+ if len(item['files']) != len(set(item['files'])): raise DiscoveryError('invalid-snapshot')
135
+ ids.add(item['id'])
136
+ if any(not isinstance(dep, str) or dep not in ids for item in value['items'] for dep in item['requires'] + item['unmeasured_dependencies']):
137
+ raise DiscoveryError('invalid-snapshot')
138
+ value['_raw_sha256'] = hashlib.sha256(raw).hexdigest()
139
+ return value
140
+
141
+ def _receipt_path(root, runtime):
142
+ """Per-checkout receipt; never borrow the main checkout's receipt."""
143
+ marker = root / '.git'
144
+ if marker.exists():
145
+ mode = marker.lstat().st_mode
146
+ if stat.S_ISLNK(mode):
147
+ raise DiscoveryError('unreadable-evidence')
148
+ if stat.S_ISDIR(mode):
149
+ return marker / 'info' / f'egregore-{runtime}-runtime.json'
150
+ if not stat.S_ISREG(mode):
151
+ raise DiscoveryError('unreadable-evidence')
152
+ text = _read(marker, 4096).decode().strip()
153
+ if not text.startswith('gitdir: '):
154
+ raise DiscoveryError('unreadable-evidence')
155
+ directory = Path(text[8:])
156
+ if not directory.is_absolute(): directory = root / directory
157
+ directory = directory.absolute()
158
+ from .installation import installation_root
159
+ main = installation_root(root)
160
+ if main == root or directory.parent.parent != main / '.git' or directory.parent.name != 'worktrees':
161
+ raise DiscoveryError('unreadable-evidence')
162
+ # Reopen only the validated backlink path; _read rejects symlink traversal.
163
+ backlink = _read(directory / 'gitdir', 4096).decode().strip()
164
+ if Path(os.path.abspath(directory / backlink)) != marker:
165
+ raise DiscoveryError('unreadable-evidence')
166
+ return directory / 'info' / f'egregore-{runtime}-runtime.json'
167
+ return root / '.egregore' / f'{runtime}-runtime-manifest.json'
168
+
169
+ def _receipt(root, runtime):
170
+ data = _json(_receipt_path(root, runtime))
171
+ if not isinstance(data, dict) or data.get('package') != 'create-egregore' or not isinstance(data.get('version'), str) or not isinstance(data.get('installedAt'), str) or not isinstance(data.get('files'), dict):
172
+ raise DiscoveryError('invalid-installer-record')
173
+ for name, record in data['files'].items():
174
+ if not _safe_relative(name) or not isinstance(record, dict) or set(record) != {'hash'} or not isinstance(record['hash'], str) or not HASH.fullmatch(record['hash']):
175
+ raise DiscoveryError('invalid-installer-record')
176
+ return data
177
+
178
+ def _empty_view(kind, state, query, runtime=None):
179
+ result = {'kind': kind, 'state': state, 'execution_authority': False, 'coverage': {'state': state, 'denominator': 'unknown'}}
180
+ if runtime: result['runtime'] = runtime
181
+ result['item' if query['operation'] in ('capabilities.describe','capabilities.explain','system.explain') else 'items'] = None if query['operation'] in ('capabilities.describe','capabilities.explain','system.explain') else []
182
+ return result
183
+
184
+ def _select(items, query):
185
+ if 'id' in query: return [item for item in items if item['id'] == query['id']]
186
+ if query['operation'] == 'capabilities.find':
187
+ words = query['query'].casefold().split()
188
+ return [item for item in items if all(word in (item['id']+' '+item['summary']).casefold() for word in words)]
189
+ return items
190
+
191
+ def local_view(root, runtime, query, at, until):
192
+ if runtime is None:
193
+ found = []; invalid = False
194
+ for candidate in RUNTIMES:
195
+ try: _snapshot(root, candidate); found.append(candidate)
196
+ except FileNotFoundError: pass
197
+ except (OSError, ValueError): invalid = True
198
+ if len(found) != 1:
199
+ return _empty_view('local-installed-snapshot', 'ambiguous' if found else 'unavailable' if invalid else 'unconfigured', query)
200
+ runtime = found[0]
201
+ try: snapshot = _snapshot(root, runtime)
202
+ except FileNotFoundError: return _empty_view('local-installed-snapshot', 'unconfigured', query, runtime)
203
+ except (OSError, ValueError): return _empty_view('local-installed-snapshot', 'unavailable', query, runtime)
204
+ try: receipt = _receipt(root, runtime); receipt_state = 'local-installer-record'
205
+ except FileNotFoundError: receipt = {'files': {}}; receipt_state = 'unrecorded'
206
+ except (OSError, ValueError): receipt = {'files': {}}; receipt_state = 'unreadable'
207
+ snapshot_path = f'bin/data/capabilities/{runtime}.json'
208
+ try:
209
+ actual_snapshot = snapshot['_raw_sha256']
210
+ snapshot_record = receipt['files'].get(snapshot_path, {}).get('hash')
211
+ snapshot_state = 'unreadable' if receipt_state == 'unreadable' else 'unrecorded' if snapshot_record is None else 'record-mismatch' if snapshot_record != actual_snapshot else 'recorded-and-matching'
212
+ except (OSError, ValueError): snapshot_state = 'unreadable'
213
+ observed = {}
214
+ byte_budget = 16_000_000
215
+ def file_state(relative):
216
+ nonlocal byte_budget
217
+ if relative not in observed:
218
+ try:
219
+ data = _read(root / relative, min(MAX_BYTES, max(byte_budget, 0)))
220
+ byte_budget -= len(data)
221
+ actual = hashlib.sha256(data).hexdigest()
222
+ expected = next(file['sha256'] for file in snapshot['files'] if file['path'] == relative)
223
+ recorded = receipt['files'].get(relative, {}).get('hash')
224
+ observed[relative] = 'modified' if actual != expected else 'unreadable' if receipt_state == 'unreadable' else 'unrecorded' if recorded is None else 'record-mismatch' if recorded != actual else 'recorded-and-matching'
225
+ except FileNotFoundError: observed[relative] = 'missing'
226
+ except (OSError, ValueError): observed[relative] = 'unreadable'
227
+ return observed[relative]
228
+ def facet(state, source):
229
+ return {'state': state, 'source': source, 'as_of': at, 'expires_at': until, 'subject_digest': snapshot['digest']}
230
+ records=[]
231
+ for item in _select(snapshot['items'], query):
232
+ states = [file_state(name) for name in item['files']] + ([snapshot_state] if item['files'] else [])
233
+ installed = next((state for state in ('unreadable','missing','modified','record-mismatch','unrecorded') if state in states), 'recorded-and-matching' if states else 'unsupported')
234
+ if installed == 'recorded-and-matching' and item['unmeasured_dependencies']: installed = 'partial'
235
+ facets = {name: facet('unknown', 'no current owner-bound evidence') for name in FACETS}
236
+ facets['declared'] = facet('declared', 'audience-filtered build projection; publisher trust unverified')
237
+ facets['installed'] = facet(installed, 'current checkout bytes joined to local installer ownership record; not authenticated release attestation')
238
+ record = {k:item[k] for k in ('id','kind','summary','state_owner','requires','unmeasured_dependencies','contract')}
239
+ record.update({'subject_digest':snapshot['digest'], 'execution_authority':False, 'callable_here':False, 'installation_scope':'enumerated delivered files; non-file dependencies unmeasured', 'facets':facets})
240
+ if query['operation'] != 'capabilities.list':
241
+ record['files'] = [{'path':name,'state':file_state(name)} for name in item['files']]
242
+ records.append(record)
243
+ view={'kind':'local-installed-snapshot','runtime':runtime,'state':'observed','scope':'current checkout; local installation evidence only', 'execution_authority':False,
244
+ 'coverage':{'state':'declared-snapshot-only','denominator':'unknown','registry':'unknown','installer_record':receipt_state,'snapshot_installer_record':snapshot_state,'max_observed_bytes':16000000,'source_map':'not-read','connect_overlay':'not-admitted','runtime_dependency_closure':'not-enumerated'}}
245
+ if query['operation'] == 'system.explain':
246
+ view['item'] = {'state_owner':'local installer ownership record and current checkout files','capabilities':records,'limitations':['registry and action permission are not inferred from installation','operational, evaluation and release adoption evidence are unknown']} if 'id' not in query or records else None
247
+ elif query['operation'] in ('capabilities.describe','capabilities.explain'): view['item']=records[0] if records else None
248
+ else: view['items']=records
249
+ return view
250
+
251
+ def _identity(root):
252
+ state = _json(root / '.egregore-state.json', 200000)
253
+ config = _json(root / 'egregore.json', 200000)
254
+ # A read cannot create an ephemeral identity via compatibility migrations.
255
+ if not isinstance(state, dict) or any(not _text(state.get(key), 2000) or state[key] != state[key].strip() for key in ('account_id','actor_id','membership_id')):
256
+ raise DiscoveryError('caller-unavailable')
257
+ if not isinstance(config, dict) or not _text(config.get('org_id'),2000) or config['org_id'] != config['org_id'].strip():
258
+ raise DiscoveryError('caller-unavailable')
259
+ from .identity import LocalIdentityResolver
260
+ from .installation import installation_key
261
+ actor = LocalIdentityResolver(root, require_existing=True).resolve(session_id='discovery-read', harness='shell')
262
+ if (actor.membership.status != 'active' or (state.get('membership_status') or 'active') != actor.membership.status or actor.account.account_id != state['account_id']
263
+ or actor.actor.account_id != state['account_id'] or actor.actor.actor_id != state['actor_id']
264
+ or actor.membership.actor_id != state['actor_id'] or actor.membership.membership_id != state['membership_id']
265
+ or actor.membership.org_id != config['org_id'] or actor.profile.org_id != config['org_id']):
266
+ raise DiscoveryError('caller-unavailable')
267
+ return actor.profile.org_id, installation_key(root, existing_org_id=config['org_id']), actor.actor.actor_id
268
+
269
+ def _http_get_inner(origin, params, headers):
270
+ url = urlsplit(origin)
271
+ if url.username or url.password or url.path not in ('','/') or url.query or url.fragment or url.scheme not in ('http','https') or not url.hostname:
272
+ raise DiscoveryError('invalid-binding')
273
+ if url.scheme == 'http':
274
+ try: loopback = ipaddress.ip_address(url.hostname).is_loopback
275
+ except ValueError: loopback = False
276
+ if not loopback: raise DiscoveryError('invalid-binding')
277
+ connection = (http.client.HTTPSConnection if url.scheme == 'https' else http.client.HTTPConnection)(url.hostname, url.port, timeout=5)
278
+ try:
279
+ connection.request('GET', '/capabilities?' + urlencode(params), headers=headers)
280
+ response=connection.getresponse()
281
+ if response.status != 200: raise DiscoveryError('provider-unavailable')
282
+ data=response.read(MAX_BYTES+1)
283
+ if len(data)>MAX_BYTES: raise DiscoveryError('provider-unavailable')
284
+ return parse_json(data)
285
+ except (OSError, ValueError, http.client.HTTPException):
286
+ raise DiscoveryError('provider-unavailable') from None
287
+ finally: connection.close()
288
+
289
+ def _http_get(origin, params, headers):
290
+ # One short-lived child gives DNS resolution and trickling response bodies a
291
+ # true total lifetime bound. Secrets travel only on stdin, never argv/env.
292
+ module_root = str(Path(__file__).absolute().parent.parent)
293
+ env = {'PYTHONPATH': module_root, 'PYTHONSAFEPATH': '1', 'PYTHONDONTWRITEBYTECODE': '1'}
294
+ process = subprocess.Popen([sys.executable, '-B', '-m', 'egregore_runtime.spirit_discovery_transport'],
295
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=env)
296
+ try:
297
+ output, _ = process.communicate(canonical({'origin':origin,'params':params,'headers':headers}), timeout=7)
298
+ except subprocess.TimeoutExpired:
299
+ process.kill(); process.communicate()
300
+ raise DiscoveryError('provider-unavailable') from None
301
+ if process.returncode != 0 or len(output)>MAX_BYTES:
302
+ raise DiscoveryError('provider-unavailable')
303
+ return parse_json(output)
304
+
305
+ def _require(condition):
306
+ if not condition: raise DiscoveryError('provider-unavailable')
307
+
308
+ def _exact(value, fields):
309
+ return isinstance(value, dict) and set(value) == set(fields)
310
+
311
+ def _instant(value):
312
+ _require(_text(value, 64) and re.fullmatch(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})', value))
313
+ result = datetime.fromisoformat(value.replace('Z', '+00:00'))
314
+ _require(result.tzinfo is not None)
315
+ return result
316
+
317
+ def _strings(value, maximum=64, *, limit=2000):
318
+ _require(isinstance(value, list) and len(value) <= maximum and all(_text(item, limit) for item in value))
319
+ _require(len(value) == len(set(value)))
320
+
321
+ def _native_schema(value):
322
+ # Support standard JSON Schema vocabulary used by v1 contracts. Unknown
323
+ # extensions are unavailable, never silently stripped or returned as data.
324
+ _require(isinstance(value, dict) and len(canonical(value)) <= 16000)
325
+ maps = {'properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas'}
326
+ singles = {'additionalProperties', 'unevaluatedProperties', 'propertyNames', 'contains', 'not', 'if', 'then', 'else', 'unevaluatedItems', 'additionalItems', 'contentSchema'}
327
+ arrays = {'allOf', 'anyOf', 'oneOf', 'prefixItems'}
328
+ texts = {'$schema', '$id', '$anchor', '$dynamicAnchor', '$comment', 'title', 'description', 'format', 'pattern', 'contentEncoding', 'contentMediaType'}
329
+ numbers = {'multipleOf', 'maximum', 'minimum', 'exclusiveMaximum', 'exclusiveMinimum'}
330
+ sizes = {'minLength', 'maxLength', 'minItems', 'maxItems', 'minContains', 'maxContains', 'minProperties', 'maxProperties'}
331
+ flags = {'uniqueItems', 'readOnly', 'writeOnly', 'deprecated'}
332
+ known = maps | singles | arrays | texts | numbers | sizes | flags | {'type', 'enum', 'const', 'default', 'examples', 'required', 'dependentRequired', '$ref', '$dynamicRef', 'items'}
333
+ pending = [(value, 0)]
334
+ while pending:
335
+ item, depth = pending.pop()
336
+ _require(depth <= 32)
337
+ if isinstance(item, bool): continue
338
+ _require(isinstance(item, dict) and set(item) <= known)
339
+ for key, part in item.items():
340
+ if key in maps:
341
+ _require(isinstance(part, dict) and len(part) <= 256 and all(_text(name, 500, blank=True) for name in part))
342
+ pending.extend((schema, depth + 1) for schema in part.values())
343
+ elif key in singles: pending.append((part, depth + 1))
344
+ elif key in arrays or key == 'items' and isinstance(part, list):
345
+ _require(isinstance(part, list) and 0 < len(part) <= 64)
346
+ pending.extend((schema, depth + 1) for schema in part)
347
+ elif key == 'items': pending.append((part, depth + 1))
348
+ elif key in texts: _require(_text(part, 16000, blank=True))
349
+ elif key in {'$ref', '$dynamicRef'}: _require(_text(part, 1000) and part.startswith('#/'))
350
+ elif key in numbers:
351
+ _require(type(part) in (int, float) and abs(part) < float('inf') and (key != 'multipleOf' or part > 0))
352
+ elif key in sizes: _require(type(part) is int and part >= 0)
353
+ elif key in flags: _require(type(part) is bool)
354
+ elif key == 'type':
355
+ types = [part] if isinstance(part, str) else part
356
+ _strings(types, 7)
357
+ _require(bool(types) and set(types) <= {'null', 'boolean', 'object', 'array', 'number', 'string', 'integer'})
358
+ elif key == 'required': _strings(part, 256, limit=500)
359
+ elif key == 'dependentRequired':
360
+ _require(isinstance(part, dict) and len(part) <= 256 and all(_text(name, 500) for name in part))
361
+ for names in part.values(): _strings(names, 256, limit=500)
362
+ elif key == 'enum':
363
+ _require(isinstance(part, list) and 0 < len(part) <= 256 and len({canonical(option) for option in part}) == len(part))
364
+ elif key == 'examples': _require(isinstance(part, list) and len(part) <= 256)
365
+ # const/default are JSON values, not provider metadata.
366
+
367
+ def _native_adoption(value):
368
+ _require(_exact(value, ('state','provenance','source_check','authority')) and value['authority'] is False)
369
+ _require(value['state'] in ('stale','not-adopted','unknown','local-unverified','source-bound'))
370
+ _require(value['provenance'] in ('unknown','local-unverified','authenticated-github-service','simulated-github-service'))
371
+ check = value['source_check']
372
+ _require(_exact(check, ('state','checked_at','expires_at','scope','current_authority')))
373
+ _require(check['state'] in ('unknown','not-checked','recorded-at-startup','expired') and check['scope'] == 'startup-only' and check['current_authority'] == 'unknown')
374
+ if check['state'] in ('recorded-at-startup','expired'):
375
+ _require(_instant(check['checked_at']) < _instant(check['expires_at']))
376
+ else: _require(check['checked_at'] is None and check['expires_at'] is None)
377
+
378
+ def _native_generation(value):
379
+ _require(isinstance(value, dict) and value.get('state') in ('off','enforced','drifted'))
380
+ _require(_exact(value, ('state','draining','digest') if value['state'] == 'enforced' else ('state','draining')))
381
+ _require(type(value['draining']) is bool)
382
+ if 'digest' in value: _require(isinstance(value['digest'], str) and HASH.fullmatch(value['digest']))
383
+
384
+ def _native_bindings(value):
385
+ _require(isinstance(value, list) and 0 < len(value) <= 64)
386
+ seen = set()
387
+ for binding in value:
388
+ _require(_exact(binding, ('adapter','phase','exposed','permitted','callable_here')))
389
+ _require(_text(binding['adapter'], 2000) and binding['adapter'] not in seen); seen.add(binding['adapter'])
390
+ _require(binding['phase'] in ('unsupported','parent-admission','child-execution'))
391
+ _require(binding['exposed'] in ('unsupported','unbound','bound','schema-mismatch','profile-unbound'))
392
+ _require(binding['permitted'] in ('unsupported','allowed','unsupported-permissions','requires-direct-message','requires-parent-assistant','draining','requires-owned-running-child','evidence-stale'))
393
+ _require(type(binding['callable_here']) is bool)
394
+ if binding['callable_here']: _require(binding['phase'] != 'unsupported' and binding['exposed'] == 'bound' and binding['permitted'] == 'allowed')
395
+
396
+ def _native_facets(value, envelope, subject=None):
397
+ states = {'declared':('declared','stale'), 'installed':('files-match','modified','missing','unreadable'),
398
+ 'exposed':('bound','unavailable'), 'permitted':('allowed-here','conditional-or-unavailable'),
399
+ 'operational':('unknown',), 'verified':('unknown',), 'adopted':('stale','not-adopted','unknown','local-unverified','source-bound')}
400
+ _require(_exact(value, FACETS))
401
+ for name, facet in value.items():
402
+ _require(_exact(facet, ('state','source','as_of','expires_at','subject_digest')))
403
+ _require(facet['state'] in states[name] and _text(facet['source'], 2000))
404
+ _require(facet['as_of'] == envelope['as_of'] and facet['expires_at'] == envelope['expires_at'])
405
+ _require(isinstance(facet['subject_digest'], str) and HASH.fullmatch(facet['subject_digest']))
406
+ if subject is None: subject = facet['subject_digest']
407
+ _require(facet['subject_digest'] == subject)
408
+
409
+ def _validate_native(response, query, now, remote_actor):
410
+ _json_strings(response)
411
+ _require(_exact(response, ('ok','binding','discovery')) and response['ok'] is True)
412
+ witness = response['binding']
413
+ _require(_exact(witness, ('schema','runtime_actor_id')) and witness['schema'] == 'egregore.discovery-caller/v1' and witness['runtime_actor_id'] == remote_actor)
414
+ native = response['discovery']; op = query['operation'].removeprefix('capabilities.')
415
+ collection = op in ('list','find')
416
+ _require(_exact(native, ('schema','operation','as_of','expires_at','scope','execution_authority','items' if collection else 'item')))
417
+ _require(native['schema'] == 'egregore.spirit-discovery/v1' and native['operation'] == op and native['execution_authority'] is False)
418
+ _require(native['scope'] == 'current-actor; read-only local candidate selection')
419
+ start, end = _instant(native['as_of']), _instant(native['expires_at'])
420
+ _require(start <= now + timedelta(seconds=1) and end > now and timedelta(0) < end - start <= timedelta(seconds=30))
421
+ if collection:
422
+ records = native['items']; _require(isinstance(records, list))
423
+ else:
424
+ _require(native['item'] is None or isinstance(native['item'], dict))
425
+ records = [native['item']] if native['item'] is not None else []
426
+ if op == 'system.explain' and records:
427
+ system = records[0]
428
+ _require(_exact(system, ('state_owner','capabilities','coverage')) and system['state_owner'] == 'host registry, current transport grants and generation owner')
429
+ _require(system['coverage'] == {'state':'current-caller-visible-contracts','denominator':'unknown','operational':'unknown','verified':'unknown','deployment_identity':'unknown'})
430
+ records = system['capabilities']; _require(isinstance(records, list) and bool(records))
431
+ _require(len(records) <= 32)
432
+ seen = set()
433
+ for record in records:
434
+ fields = {'id','summary','source_adoption','generation','bindings','facets'}
435
+ fields |= {'state_owners'} if op == 'system.explain' else {'subject_digest','trust','execution_authority','recipes'}
436
+ if op in ('describe','explain'): fields |= {'limitations','input_schema','output_schema'}
437
+ _require(_exact(record, fields))
438
+ _require(_text(record['id'],160) and re.fullmatch(r'[a-z][a-z0-9.-]*(?:/[a-z][a-z0-9.-]*)?',record['id']) and record['id'] not in seen)
439
+ seen.add(record['id']); _require('id' not in query or record['id'] == query['id'])
440
+ _require(_text(record['summary'],2000))
441
+ _native_adoption(record['source_adoption']); _native_generation(record['generation']); _native_bindings(record['bindings'])
442
+ if op == 'system.explain':
443
+ _strings(record['state_owners'],2)
444
+ _require(set(record['state_owners']) == {'WorkStore owned job' if binding['phase'] == 'child-execution' else 'current transport grants' for binding in record['bindings']})
445
+ _native_facets(record['facets'],native)
446
+ else:
447
+ _require(record['execution_authority'] is False and record['trust'] == 'local-candidate-untrusted')
448
+ _require(isinstance(record['subject_digest'],str) and HASH.fullmatch(record['subject_digest']))
449
+ _strings(record['recipes']); _native_facets(record['facets'],native,record['subject_digest'])
450
+ _require(record['facets']['adopted']['state'] == record['source_adoption']['state'])
451
+ if op in ('describe','explain'):
452
+ _strings(record['limitations']); _native_schema(record['input_schema']); _native_schema(record['output_schema'])
453
+ return native
454
+
455
+ def remote_view(root, query, now, environ=None):
456
+ environ = os.environ if environ is None else environ
457
+ file=root / '.egregore' / 'spirit-discovery' / 'binding.json'
458
+ try: binding=_json(file,64000,private=True)
459
+ except FileNotFoundError: return _empty_view('durable-authenticated','unconfigured',query)
460
+ except (OSError, ValueError): return _empty_view('durable-authenticated','unavailable',query)
461
+ try:
462
+ fields={'schema','org_id','installation_key','actor_id','origin','remote_runtime_actor_id','bridge_token_env','actor_token_env'}
463
+ if not isinstance(binding,dict) or set(binding)!=fields or binding['schema']!='egregore.discovery-binding/v1': raise DiscoveryError('invalid-binding')
464
+ if not all(_text(binding[field], 4096) for field in fields - {'schema'}): raise DiscoveryError('invalid-binding')
465
+ if _identity(root)!=(binding['org_id'],binding['installation_key'],binding['actor_id']): raise DiscoveryError('caller-unavailable')
466
+ credentials=[]
467
+ for key in ('bridge_token_env','actor_token_env'):
468
+ if not isinstance(binding[key],str) or not re.fullmatch(r'[A-Z][A-Z0-9_]{0,79}',binding[key]): raise DiscoveryError('invalid-binding')
469
+ value=environ.get(binding[key])
470
+ if not isinstance(value,str) or not value or len(value)>4096 or any(ord(c)<33 or ord(c)>126 for c in value): raise DiscoveryError('caller-unavailable')
471
+ credentials.append(value)
472
+ op=query['operation'].removeprefix('capabilities.')
473
+ response=_http_get(binding['origin'],{**query,'operation':op}, {'x-bridge-token':credentials[0],'x-actor-token':credentials[1]})
474
+ native=_validate_native(response, query, now, binding['remote_runtime_actor_id'])
475
+ return {'kind':'durable-authenticated','state':'observed','scope':'authenticated HTTP caller at fixed host; not local tool exposure',
476
+ 'execution_authority':False,'callable_here':False,'deployment_identity':'unknown','coverage':{'state':'provider-scoped','denominator':'unknown'},'native':native}
477
+ except (OSError, ValueError, TypeError, KeyError, AttributeError, RecursionError):
478
+ return _empty_view('durable-authenticated','unavailable',query)
479
+
480
+ def discover(root, request, *, runtime=None, now=None, environ=None):
481
+ query=query_request(request)
482
+ if runtime is not None and runtime not in RUNTIMES: raise DiscoveryError('invalid-adapter')
483
+ root=Path(root).absolute()
484
+ now=now or datetime.now(timezone.utc)
485
+ at=now.isoformat(); until=(now+timedelta(seconds=30)).isoformat()
486
+ return {'schema':'egregore.capability-query/v1','operation':query['operation'],'as_of':at,'expires_at':until,'execution_authority':False,
487
+ 'views':[local_view(root,runtime,query,at,until),remote_view(root,query,now,environ)]}
@@ -0,0 +1,25 @@
1
+ """Neutral discovery CLI; fixed adapters provide runtime outside request data."""
2
+ import json
3
+ from pathlib import Path
4
+ import sys
5
+ from .spirit_discovery import DiscoveryError, discover, parse_json, query_request
6
+
7
+ def main(*, runtime=None, argv=None):
8
+ args=list(sys.argv[1:] if argv is None else argv)
9
+ try:
10
+ if args == ['--json']:
11
+ raw=sys.stdin.buffer.read(4097)
12
+ if len(raw)>4096: raise DiscoveryError('invalid-query')
13
+ request=parse_json(raw)
14
+ elif len(args) in (1,2):
15
+ request={'operation':args[0]}
16
+ if len(args)==2: request['query' if args[0]=='capabilities.find' else 'id']=args[1]
17
+ else: raise DiscoveryError('invalid-query')
18
+ query_request(request)
19
+ root=Path(__file__).absolute().parent.parent
20
+ print(json.dumps(discover(root,request,runtime=runtime),ensure_ascii=False))
21
+ return 0
22
+ except (DiscoveryError, OSError, UnicodeError, ValueError, TypeError, RecursionError):
23
+ print(json.dumps({'schema':'egregore.capability-query-error/v1','error':'invalid-or-unavailable-discovery','execution_authority':False}))
24
+ return 2
25
+ if __name__=='__main__': sys.exit(main())
@@ -0,0 +1,18 @@
1
+ """Private bounded transport child; no provider or model execution."""
2
+ import json
3
+ import sys
4
+ from .spirit_discovery import _http_get_inner, parse_json
5
+
6
+ def main():
7
+ try:
8
+ raw=sys.stdin.buffer.read(20001)
9
+ if len(raw)>20000: return 2
10
+ value=parse_json(raw)
11
+ result=_http_get_inner(value['origin'],value['params'],value['headers'])
12
+ output=json.dumps(result,ensure_ascii=False).encode()
13
+ if len(output)>2000000: return 2
14
+ sys.stdout.buffer.write(output)
15
+ return 0
16
+ except Exception:
17
+ return 2
18
+ if __name__=='__main__': sys.exit(main())