create-egregore 0.20.2 → 0.21.0-runtime-mvp.2

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 (706) hide show
  1. package/assets/egregore-launcher.js +1013 -16
  2. package/bin/cli.js +11 -0
  3. package/lib/claude-runtime.js +192 -0
  4. package/lib/local.js +38 -31
  5. package/lib/pi-runtime.js +1 -0
  6. package/lib/prime-runtime.js +1 -0
  7. package/lib/runtime-identity.js +146 -0
  8. package/lib/setup.js +116 -17
  9. package/package.json +10 -6
  10. package/runtime/claude/.claude/context/commit-format.md +110 -0
  11. package/runtime/claude/.claude/context/git-language.md +46 -0
  12. package/runtime/claude/.claude/context/notification-consent.md +66 -0
  13. package/runtime/claude/.claude/context/pr-format.md +67 -0
  14. package/runtime/claude/.claude/context/telemetry.md +133 -0
  15. package/runtime/claude/.claude/hooks/boundary-check.sh +333 -0
  16. package/runtime/claude/.claude/hooks/branch-guard.sh +231 -0
  17. package/runtime/claude/.claude/hooks/card-context.sh +38 -0
  18. package/runtime/claude/.claude/hooks/emissary-detect.sh +53 -0
  19. package/runtime/claude/.claude/hooks/memory-refresh.sh +67 -0
  20. package/runtime/claude/.claude/hooks/onboarding-guard.sh +86 -0
  21. package/runtime/claude/.claude/hooks/retrieval-context.sh +43 -0
  22. package/runtime/claude/.claude/rules/voice-bedrock.md +73 -0
  23. package/runtime/claude/.claude/settings.json +131 -0
  24. package/runtime/claude/.claude/skills/activity/SKILL.md +68 -0
  25. package/runtime/claude/.claude/skills/announce/SKILL.md +54 -0
  26. package/runtime/claude/.claude/skills/archive/SKILL.md +71 -0
  27. package/runtime/claude/.claude/skills/branch/SKILL.md +147 -0
  28. package/runtime/claude/.claude/skills/checkup/SKILL.md +159 -0
  29. package/runtime/claude/.claude/skills/commit/SKILL.md +103 -0
  30. package/runtime/claude/.claude/skills/contribute/SKILL.md +343 -0
  31. package/runtime/claude/.claude/skills/create-skill/SKILL.md +135 -0
  32. package/runtime/claude/.claude/skills/dark-mode/SKILL.md +410 -0
  33. package/runtime/claude/.claude/skills/dashboard/SKILL.md +43 -0
  34. package/runtime/claude/.claude/skills/deep-reflect/REPORT.md +140 -0
  35. package/runtime/claude/.claude/skills/deep-reflect/RESEARCH.md +132 -0
  36. package/runtime/claude/.claude/skills/deep-reflect/ROLES.md +104 -0
  37. package/runtime/claude/.claude/skills/deep-reflect/SKILL.md +170 -0
  38. package/runtime/claude/.claude/skills/delete-user/SKILL.md +150 -0
  39. package/runtime/claude/.claude/skills/emissary/SKILL.md +220 -0
  40. package/runtime/claude/.claude/skills/handoff/SKILL.md +124 -0
  41. package/runtime/claude/.claude/skills/harvest/AUDIT.md +546 -0
  42. package/runtime/claude/.claude/skills/harvest/FORMAT.md +436 -0
  43. package/runtime/claude/.claude/skills/harvest/PROCESS.md +395 -0
  44. package/runtime/claude/.claude/skills/harvest/QUESTION_PALETTE.md +76 -0
  45. package/runtime/claude/.claude/skills/harvest/SKILL.md +100 -0
  46. package/runtime/claude/.claude/skills/ingest/SKILL.md +112 -0
  47. package/runtime/claude/.claude/skills/ingest-corpus/SKILL.md +187 -0
  48. package/runtime/claude/.claude/skills/ingest-notion/SKILL.md +214 -0
  49. package/runtime/claude/.claude/skills/ingest-user-interview/SKILL.md +65 -0
  50. package/runtime/claude/.claude/skills/ingest-user-interview/references/analysis-contract.md +125 -0
  51. package/runtime/claude/.claude/skills/invite/SKILL.md +111 -0
  52. package/runtime/claude/.claude/skills/issue/SKILL.md +104 -0
  53. package/runtime/claude/.claude/skills/me/SKILL.md +81 -0
  54. package/runtime/claude/.claude/skills/meeting/SKILL.md +76 -0
  55. package/runtime/claude/.claude/skills/meeting/references/analysis-contract.md +102 -0
  56. package/runtime/claude/.claude/skills/note/SKILL.md +71 -0
  57. package/runtime/claude/.claude/skills/notion-connect/SKILL.md +124 -0
  58. package/runtime/claude/.claude/skills/onboarding/SKILL.md +159 -0
  59. package/runtime/claude/.claude/skills/pr/SKILL.md +85 -0
  60. package/runtime/claude/.claude/skills/pull/SKILL.md +79 -0
  61. package/runtime/claude/.claude/skills/push/SKILL.md +47 -0
  62. package/runtime/claude/.claude/skills/quest/SKILL.md +121 -0
  63. package/runtime/claude/.claude/skills/reflect/SKILL.md +69 -0
  64. package/runtime/claude/.claude/skills/review-pr/SKILL.md +276 -0
  65. package/runtime/claude/.claude/skills/save/SKILL.md +92 -0
  66. package/runtime/claude/.claude/skills/slack-connect/SKILL.md +148 -0
  67. package/runtime/claude/.claude/skills/summon/SKILL.md +240 -0
  68. package/runtime/claude/.claude/skills/sync-repos/SKILL.md +82 -0
  69. package/runtime/claude/.claude/skills/teams-connect/SKILL.md +216 -0
  70. package/runtime/claude/.claude/skills/telegram-connect/SKILL.md +110 -0
  71. package/runtime/claude/.claude/skills/telemetry/SKILL.md +117 -0
  72. package/runtime/claude/.claude/skills/test/SKILL.md +87 -0
  73. package/runtime/claude/.claude/skills/the-spiral/SKILL.md +492 -0
  74. package/runtime/claude/.claude/skills/tui-design/SKILL.md +188 -0
  75. package/runtime/claude/.claude/skills/tutorial/SKILL.md +100 -0
  76. package/runtime/claude/.claude/skills/update/SKILL.md +265 -0
  77. package/runtime/claude/.claude/skills/view/SKILL.md +393 -0
  78. package/runtime/claude/.claude/skills/view/compose-scaffold.html +221 -0
  79. package/runtime/claude/.claude/skills/visual-explain/SKILL.md +132 -0
  80. package/runtime/claude/.claude/skills/wrap/SKILL.md +98 -0
  81. package/runtime/claude/CLAUDE.md +447 -0
  82. package/runtime/claude/bin/activity-action.sh +92 -0
  83. package/runtime/claude/bin/activity-data.sh +17 -0
  84. package/runtime/claude/bin/agent.sh +783 -0
  85. package/runtime/claude/bin/artifact-register.sh +164 -0
  86. package/runtime/claude/bin/artifact-writeback.sh +53 -0
  87. package/runtime/claude/bin/artifacts.sh +288 -0
  88. package/runtime/claude/bin/attendant.sh +707 -0
  89. package/runtime/claude/bin/autosave.sh +112 -0
  90. package/runtime/claude/bin/boundary.sh +253 -0
  91. package/runtime/claude/bin/build-sealed-dataroom.mjs +722 -0
  92. package/runtime/claude/bin/capture-reconcile.sh +69 -0
  93. package/runtime/claude/bin/capture-run.sh +357 -0
  94. package/runtime/claude/bin/codex-install-skills.sh +78 -0
  95. package/runtime/claude/bin/codex-render-spec.mjs +462 -0
  96. package/runtime/claude/bin/codex-session-start.sh +275 -0
  97. package/runtime/claude/bin/codex-skill-render.mjs +349 -0
  98. package/runtime/claude/bin/codex-sync-skills.sh +237 -0
  99. package/runtime/claude/bin/connect-refresh.sh +33 -0
  100. package/runtime/claude/bin/contribute-guard.sh +79 -0
  101. package/runtime/claude/bin/corpus_map.py +806 -0
  102. package/runtime/claude/bin/corpus_survey.py +304 -0
  103. package/runtime/claude/bin/dashboard-data.sh +18 -0
  104. package/runtime/claude/bin/github-auth.sh +121 -0
  105. package/runtime/claude/bin/graph-batch.sh +86 -0
  106. package/runtime/claude/bin/graph-op.sh +1106 -0
  107. package/runtime/claude/bin/graph-projection.sh +54 -0
  108. package/runtime/claude/bin/graph-wal.sh +242 -0
  109. package/runtime/claude/bin/graph.sh +267 -0
  110. package/runtime/claude/bin/handoff-lifecycle-job.sh +146 -0
  111. package/runtime/claude/bin/handoff-lifecycle.sh +280 -0
  112. package/runtime/claude/bin/handoff-pr-backfill.sh +86 -0
  113. package/runtime/claude/bin/handoff-preview.sh +169 -0
  114. package/runtime/claude/bin/handoff-run.sh +692 -0
  115. package/runtime/claude/bin/handoff-save-egregore.sh +224 -0
  116. package/runtime/claude/bin/index-handoff.sh +322 -0
  117. package/runtime/claude/bin/ingest-graph.sh +86 -0
  118. package/runtime/claude/bin/ingest-item.sh +10 -0
  119. package/runtime/claude/bin/ingest-surface.html +495 -0
  120. package/runtime/claude/bin/ingest.py +1011 -0
  121. package/runtime/claude/bin/ingest.sh +308 -0
  122. package/runtime/claude/bin/ingest_graph.py +856 -0
  123. package/runtime/claude/bin/ingest_surface.py +544 -0
  124. package/runtime/claude/bin/invite.sh +6 -0
  125. package/runtime/claude/bin/issue.sh +10 -0
  126. package/runtime/claude/bin/knowledge.sh +10 -0
  127. package/runtime/claude/bin/lib/artifact-id.sh +46 -0
  128. package/runtime/claude/bin/lib/config.sh +136 -0
  129. package/runtime/claude/bin/lib/context.sh +642 -0
  130. package/runtime/claude/bin/lib/dashboard-artifact.sh +242 -0
  131. package/runtime/claude/bin/lib/ensure-develop.sh +183 -0
  132. package/runtime/claude/bin/lib/git-message.sh +37 -0
  133. package/runtime/claude/bin/lib/git-sync.sh +494 -0
  134. package/runtime/claude/bin/lib/greeting.sh +548 -0
  135. package/runtime/claude/bin/lib/handoff-meta.sh +97 -0
  136. package/runtime/claude/bin/lib/hash.sh +13 -0
  137. package/runtime/claude/bin/lib/identity.sh +213 -0
  138. package/runtime/claude/bin/lib/metrics.sh +374 -0
  139. package/runtime/claude/bin/lib/noncode.sh +31 -0
  140. package/runtime/claude/bin/lib/permissions.sh +106 -0
  141. package/runtime/claude/bin/lib/time.sh +56 -0
  142. package/runtime/claude/bin/lib/worktree-links.sh +85 -0
  143. package/runtime/claude/bin/node-run.sh +42 -0
  144. package/runtime/claude/bin/notification.sh +6 -0
  145. package/runtime/claude/bin/notify.sh +636 -0
  146. package/runtime/claude/bin/observe-context.sh +14 -0
  147. package/runtime/claude/bin/onboarding.sh +6 -0
  148. package/runtime/claude/bin/person.py +634 -0
  149. package/runtime/claude/bin/person.sh +481 -0
  150. package/runtime/claude/bin/pi-render-spec.mjs +140 -0
  151. package/runtime/claude/bin/post-update.sh +37 -0
  152. package/runtime/claude/bin/prime-render-spec.mjs +147 -0
  153. package/runtime/claude/bin/publish-artifact.sh +324 -0
  154. package/runtime/claude/bin/publish-references.sh +144 -0
  155. package/runtime/claude/bin/quest.sh +9 -0
  156. package/runtime/claude/bin/question.sh +8 -0
  157. package/runtime/claude/bin/repo-state.sh +96 -0
  158. package/runtime/claude/bin/research-ingest.sh +7 -0
  159. package/runtime/claude/bin/restore-owned-skills.sh +67 -0
  160. package/runtime/claude/bin/retrieval_eval.py +385 -0
  161. package/runtime/claude/bin/runtime-identity.sh +70 -0
  162. package/runtime/claude/bin/runtime-upgrade.sh +43 -0
  163. package/runtime/claude/bin/scroll.sh +9 -0
  164. package/runtime/claude/bin/search.sh +318 -0
  165. package/runtime/claude/bin/session-autosave.sh +220 -0
  166. package/runtime/claude/bin/session-log.sh +199 -0
  167. package/runtime/claude/bin/session-start.sh +696 -0
  168. package/runtime/claude/bin/settings.sh +532 -0
  169. package/runtime/claude/bin/startup-check.sh +136 -0
  170. package/runtime/claude/bin/statusline.sh +41 -0
  171. package/runtime/claude/bin/telemetry.sh +666 -0
  172. package/runtime/claude/bin/test-changes.sh +344 -0
  173. package/runtime/claude/bin/thread.sh +9 -0
  174. package/runtime/claude/bin/todo.sh +9 -0
  175. package/runtime/claude/bin/view-render.sh +126 -0
  176. package/runtime/claude/bin/worktree-create.sh +145 -0
  177. package/runtime/claude/bin/worktree-remove.sh +50 -0
  178. package/runtime/claude/bin/worktree.sh +198 -0
  179. package/runtime/claude/egregore_runtime/__init__.py +228 -0
  180. package/runtime/claude/egregore_runtime/adapters/__init__.py +5 -0
  181. package/runtime/claude/egregore_runtime/adapters/invitation.py +237 -0
  182. package/runtime/claude/egregore_runtime/adapters/notification.py +59 -0
  183. package/runtime/claude/egregore_runtime/adapters/qmd.py +1701 -0
  184. package/runtime/claude/egregore_runtime/adapters/qmd_cli.py +210 -0
  185. package/runtime/claude/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
  186. package/runtime/claude/egregore_runtime/admin_gate.py +102 -0
  187. package/runtime/claude/egregore_runtime/artifacts.py +523 -0
  188. package/runtime/claude/egregore_runtime/contracts.py +499 -0
  189. package/runtime/claude/egregore_runtime/entitlements.py +85 -0
  190. package/runtime/claude/egregore_runtime/errors.py +10 -0
  191. package/runtime/claude/egregore_runtime/graph.py +356 -0
  192. package/runtime/claude/egregore_runtime/handoff_lookup.py +196 -0
  193. package/runtime/claude/egregore_runtime/harness_cli.py +1221 -0
  194. package/runtime/claude/egregore_runtime/identity.py +734 -0
  195. package/runtime/claude/egregore_runtime/identity_cli.py +91 -0
  196. package/runtime/claude/egregore_runtime/ingest.py +575 -0
  197. package/runtime/claude/egregore_runtime/ingest_cli.py +254 -0
  198. package/runtime/claude/egregore_runtime/ingest_index_cli.py +147 -0
  199. package/runtime/claude/egregore_runtime/invitation_cli.py +51 -0
  200. package/runtime/claude/egregore_runtime/invitations.py +116 -0
  201. package/runtime/claude/egregore_runtime/issue_cli.py +305 -0
  202. package/runtime/claude/egregore_runtime/issues.py +359 -0
  203. package/runtime/claude/egregore_runtime/knowledge.py +376 -0
  204. package/runtime/claude/egregore_runtime/knowledge_cli.py +173 -0
  205. package/runtime/claude/egregore_runtime/lifecycle.py +888 -0
  206. package/runtime/claude/egregore_runtime/lifecycle_cli.py +269 -0
  207. package/runtime/claude/egregore_runtime/notification_cli.py +125 -0
  208. package/runtime/claude/egregore_runtime/notifications.py +230 -0
  209. package/runtime/claude/egregore_runtime/observe.py +342 -0
  210. package/runtime/claude/egregore_runtime/onboarding.py +290 -0
  211. package/runtime/claude/egregore_runtime/onboarding_cli.py +110 -0
  212. package/runtime/claude/egregore_runtime/policy.py +144 -0
  213. package/runtime/claude/egregore_runtime/profile.py +193 -0
  214. package/runtime/claude/egregore_runtime/quest_cli.py +216 -0
  215. package/runtime/claude/egregore_runtime/question_cli.py +180 -0
  216. package/runtime/claude/egregore_runtime/questions.py +304 -0
  217. package/runtime/claude/egregore_runtime/quests.py +419 -0
  218. package/runtime/claude/egregore_runtime/readiness.py +379 -0
  219. package/runtime/claude/egregore_runtime/research_ingest.py +256 -0
  220. package/runtime/claude/egregore_runtime/research_ingest_cli.py +44 -0
  221. package/runtime/claude/egregore_runtime/runtime.py +464 -0
  222. package/runtime/claude/egregore_runtime/scroll_cli.py +185 -0
  223. package/runtime/claude/egregore_runtime/scrolls.py +726 -0
  224. package/runtime/claude/egregore_runtime/status.py +412 -0
  225. package/runtime/claude/egregore_runtime/status_cli.py +174 -0
  226. package/runtime/claude/egregore_runtime/sync.py +218 -0
  227. package/runtime/claude/egregore_runtime/teamsync.py +139 -0
  228. package/runtime/claude/egregore_runtime/telemetry.py +640 -0
  229. package/runtime/claude/egregore_runtime/thread_cli.py +173 -0
  230. package/runtime/claude/egregore_runtime/threads.py +517 -0
  231. package/runtime/claude/egregore_runtime/todo_cli.py +142 -0
  232. package/runtime/claude/egregore_runtime/todos.py +244 -0
  233. package/runtime/claude/egregore_runtime/upgrade.py +770 -0
  234. package/runtime/claude/egregore_runtime/upgrade_worker.py +53 -0
  235. package/runtime/claude/egregore_runtime/writeback.py +391 -0
  236. package/runtime/claude/egregore_runtime/writeback_cli.py +114 -0
  237. package/runtime/claude/manifest.json +284 -0
  238. package/runtime/codex/.codex/hooks/search-hint.js +21 -52
  239. package/runtime/codex/.codex/hooks.json +3 -3
  240. package/runtime/codex/.codex/skills/activity/SKILL.md +23 -66
  241. package/runtime/codex/.codex/skills/announce/SKILL.md +27 -50
  242. package/runtime/codex/.codex/skills/archive/SKILL.md +21 -36
  243. package/runtime/codex/.codex/skills/checkup/SKILL.md +77 -36
  244. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +1 -1
  245. package/runtime/codex/.codex/skills/dashboard/SKILL.md +17 -47
  246. package/runtime/codex/.codex/skills/handoff/SKILL.md +84 -133
  247. package/runtime/codex/.codex/skills/harvest/SKILL.md +32 -117
  248. package/runtime/codex/.codex/skills/ingest/SKILL.md +47 -25
  249. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +18 -36
  250. package/runtime/codex/.codex/skills/ingest-user-interview/references/analysis-contract.md +42 -0
  251. package/runtime/codex/.codex/skills/invite/SKILL.md +21 -35
  252. package/runtime/codex/.codex/skills/issue/SKILL.md +60 -41
  253. package/runtime/codex/.codex/skills/me/SKILL.md +1 -1
  254. package/runtime/codex/.codex/skills/meeting/SKILL.md +16 -36
  255. package/runtime/codex/.codex/skills/meeting/references/analysis-contract.md +48 -0
  256. package/runtime/codex/.codex/skills/note/SKILL.md +30 -25
  257. package/runtime/codex/.codex/skills/onboarding/SKILL.md +36 -41
  258. package/runtime/codex/.codex/skills/pull/SKILL.md +1 -1
  259. package/runtime/codex/.codex/skills/quest/SKILL.md +27 -66
  260. package/runtime/codex/.codex/skills/reflect/SKILL.md +22 -36
  261. package/runtime/codex/.codex/skills/save/SKILL.md +2 -2
  262. package/runtime/codex/.codex/skills/telemetry/SKILL.md +1 -1
  263. package/runtime/codex/.codex/skills/test/SKILL.md +1 -1
  264. package/runtime/codex/.codex/skills/tutorial/SKILL.md +40 -41
  265. package/runtime/codex/.codex/skills/view/SKILL.md +115 -111
  266. package/runtime/codex/.codex/skills/wrap/SKILL.md +45 -41
  267. package/runtime/codex/.codex/spec-manifest.json +9 -9
  268. package/runtime/codex/AGENTS.md +62 -58
  269. package/runtime/codex/bin/activity-action.sh +92 -0
  270. package/runtime/codex/bin/activity-data.sh +12 -233
  271. package/runtime/codex/bin/agent.sh +84 -100
  272. package/runtime/codex/bin/artifact-register.sh +164 -0
  273. package/runtime/codex/bin/artifact-writeback.sh +53 -0
  274. package/runtime/codex/bin/artifacts.sh +288 -0
  275. package/runtime/codex/bin/build-sealed-dataroom.mjs +722 -0
  276. package/runtime/codex/bin/capture-run.sh +18 -2
  277. package/runtime/codex/bin/codex-render-spec.mjs +41 -16
  278. package/runtime/codex/bin/codex-session-start.sh +14 -4
  279. package/runtime/codex/bin/codex-skill-render.mjs +9 -7
  280. package/runtime/codex/bin/corpus_map.py +806 -0
  281. package/runtime/codex/bin/corpus_survey.py +304 -0
  282. package/runtime/codex/bin/dashboard-data.sh +11 -171
  283. package/runtime/codex/bin/github-auth.sh +121 -0
  284. package/runtime/codex/bin/graph-batch.sh +7 -0
  285. package/runtime/codex/bin/graph-op.sh +4 -0
  286. package/runtime/codex/bin/graph-projection.sh +54 -0
  287. package/runtime/codex/bin/graph-wal.sh +13 -0
  288. package/runtime/codex/bin/graph.sh +44 -0
  289. package/runtime/codex/bin/handoff-lifecycle-job.sh +146 -0
  290. package/runtime/codex/bin/handoff-lifecycle.sh +280 -0
  291. package/runtime/codex/bin/handoff-preview.sh +169 -0
  292. package/runtime/codex/bin/handoff-run.sh +42 -26
  293. package/runtime/codex/bin/ingest-graph.sh +6 -0
  294. package/runtime/codex/bin/ingest-item.sh +10 -0
  295. package/runtime/codex/bin/ingest.sh +59 -30
  296. package/runtime/codex/bin/invite.sh +6 -0
  297. package/runtime/codex/bin/issue.sh +10 -0
  298. package/runtime/codex/bin/knowledge.sh +10 -0
  299. package/runtime/codex/bin/lib/config.sh +20 -0
  300. package/runtime/codex/bin/lib/context.sh +46 -25
  301. package/runtime/codex/bin/lib/git-sync.sh +70 -6
  302. package/runtime/codex/bin/lib/greeting.sh +34 -5
  303. package/runtime/codex/bin/lib/handoff-meta.sh +97 -0
  304. package/runtime/codex/bin/lib/worktree-links.sh +6 -0
  305. package/runtime/codex/bin/node-run.sh +42 -0
  306. package/runtime/codex/bin/notification.sh +6 -0
  307. package/runtime/codex/bin/observe-context.sh +14 -0
  308. package/runtime/codex/bin/onboarding.sh +6 -0
  309. package/runtime/codex/bin/person.py +4 -0
  310. package/runtime/codex/bin/person.sh +43 -10
  311. package/runtime/codex/bin/pi-render-spec.mjs +140 -0
  312. package/runtime/codex/bin/post-update.sh +37 -0
  313. package/runtime/codex/bin/prime-render-spec.mjs +147 -0
  314. package/runtime/codex/bin/publish-artifact.sh +55 -2
  315. package/runtime/codex/bin/quest.sh +9 -0
  316. package/runtime/codex/bin/question.sh +8 -0
  317. package/runtime/codex/bin/research-ingest.sh +7 -0
  318. package/runtime/codex/bin/restore-owned-skills.sh +67 -0
  319. package/runtime/codex/bin/retrieval_eval.py +385 -0
  320. package/runtime/codex/bin/runtime-identity.sh +70 -0
  321. package/runtime/codex/bin/runtime-upgrade.sh +43 -0
  322. package/runtime/codex/bin/scroll.sh +9 -0
  323. package/runtime/codex/bin/search.sh +231 -195
  324. package/runtime/codex/bin/settings.sh +174 -5
  325. package/runtime/codex/bin/startup-check.sh +136 -0
  326. package/runtime/codex/bin/telemetry.sh +522 -247
  327. package/runtime/codex/bin/test-changes.sh +344 -0
  328. package/runtime/codex/bin/thread.sh +9 -0
  329. package/runtime/codex/bin/todo.sh +9 -0
  330. package/runtime/codex/bin/view-render.sh +126 -0
  331. package/runtime/codex/bin/worktree-create.sh +19 -5
  332. package/runtime/codex/egregore_runtime/__init__.py +228 -0
  333. package/runtime/codex/egregore_runtime/adapters/__init__.py +5 -0
  334. package/runtime/codex/egregore_runtime/adapters/invitation.py +237 -0
  335. package/runtime/codex/egregore_runtime/adapters/notification.py +59 -0
  336. package/runtime/codex/egregore_runtime/adapters/qmd.py +1701 -0
  337. package/runtime/codex/egregore_runtime/adapters/qmd_cli.py +210 -0
  338. package/runtime/codex/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
  339. package/runtime/codex/egregore_runtime/admin_gate.py +102 -0
  340. package/runtime/codex/egregore_runtime/artifacts.py +523 -0
  341. package/runtime/codex/egregore_runtime/contracts.py +499 -0
  342. package/runtime/codex/egregore_runtime/entitlements.py +85 -0
  343. package/runtime/codex/egregore_runtime/errors.py +10 -0
  344. package/runtime/codex/egregore_runtime/graph.py +356 -0
  345. package/runtime/codex/egregore_runtime/handoff_lookup.py +196 -0
  346. package/runtime/codex/egregore_runtime/harness_cli.py +1221 -0
  347. package/runtime/codex/egregore_runtime/identity.py +734 -0
  348. package/runtime/codex/egregore_runtime/identity_cli.py +91 -0
  349. package/runtime/codex/egregore_runtime/ingest.py +575 -0
  350. package/runtime/codex/egregore_runtime/ingest_cli.py +254 -0
  351. package/runtime/codex/egregore_runtime/ingest_index_cli.py +147 -0
  352. package/runtime/codex/egregore_runtime/invitation_cli.py +51 -0
  353. package/runtime/codex/egregore_runtime/invitations.py +116 -0
  354. package/runtime/codex/egregore_runtime/issue_cli.py +305 -0
  355. package/runtime/codex/egregore_runtime/issues.py +359 -0
  356. package/runtime/codex/egregore_runtime/knowledge.py +376 -0
  357. package/runtime/codex/egregore_runtime/knowledge_cli.py +173 -0
  358. package/runtime/codex/egregore_runtime/lifecycle.py +888 -0
  359. package/runtime/codex/egregore_runtime/lifecycle_cli.py +269 -0
  360. package/runtime/codex/egregore_runtime/notification_cli.py +125 -0
  361. package/runtime/codex/egregore_runtime/notifications.py +230 -0
  362. package/runtime/codex/egregore_runtime/observe.py +342 -0
  363. package/runtime/codex/egregore_runtime/onboarding.py +290 -0
  364. package/runtime/codex/egregore_runtime/onboarding_cli.py +110 -0
  365. package/runtime/codex/egregore_runtime/policy.py +144 -0
  366. package/runtime/codex/egregore_runtime/profile.py +193 -0
  367. package/runtime/codex/egregore_runtime/quest_cli.py +216 -0
  368. package/runtime/codex/egregore_runtime/question_cli.py +180 -0
  369. package/runtime/codex/egregore_runtime/questions.py +304 -0
  370. package/runtime/codex/egregore_runtime/quests.py +419 -0
  371. package/runtime/codex/egregore_runtime/readiness.py +379 -0
  372. package/runtime/codex/egregore_runtime/research_ingest.py +256 -0
  373. package/runtime/codex/egregore_runtime/research_ingest_cli.py +44 -0
  374. package/runtime/codex/egregore_runtime/runtime.py +464 -0
  375. package/runtime/codex/egregore_runtime/scroll_cli.py +185 -0
  376. package/runtime/codex/egregore_runtime/scrolls.py +726 -0
  377. package/runtime/codex/egregore_runtime/status.py +412 -0
  378. package/runtime/codex/egregore_runtime/status_cli.py +174 -0
  379. package/runtime/codex/egregore_runtime/sync.py +218 -0
  380. package/runtime/codex/egregore_runtime/teamsync.py +139 -0
  381. package/runtime/codex/egregore_runtime/telemetry.py +640 -0
  382. package/runtime/codex/egregore_runtime/thread_cli.py +173 -0
  383. package/runtime/codex/egregore_runtime/threads.py +517 -0
  384. package/runtime/codex/egregore_runtime/todo_cli.py +142 -0
  385. package/runtime/codex/egregore_runtime/todos.py +244 -0
  386. package/runtime/codex/egregore_runtime/upgrade.py +770 -0
  387. package/runtime/codex/egregore_runtime/upgrade_worker.py +53 -0
  388. package/runtime/codex/egregore_runtime/writeback.py +391 -0
  389. package/runtime/codex/egregore_runtime/writeback_cli.py +114 -0
  390. package/runtime/codex/manifest.json +98 -1
  391. package/runtime/pi/.codex/hooks/search-hint.js +21 -52
  392. package/runtime/pi/.codex/hooks.json +3 -3
  393. package/runtime/pi/.codex/skills/activity/SKILL.md +23 -66
  394. package/runtime/pi/.codex/skills/announce/SKILL.md +27 -50
  395. package/runtime/pi/.codex/skills/archive/SKILL.md +21 -36
  396. package/runtime/pi/.codex/skills/checkup/SKILL.md +77 -36
  397. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +1 -1
  398. package/runtime/pi/.codex/skills/dashboard/SKILL.md +17 -47
  399. package/runtime/pi/.codex/skills/handoff/SKILL.md +84 -133
  400. package/runtime/pi/.codex/skills/harvest/SKILL.md +32 -117
  401. package/runtime/pi/.codex/skills/ingest/SKILL.md +47 -25
  402. package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +18 -36
  403. package/runtime/pi/.codex/skills/ingest-user-interview/references/analysis-contract.md +42 -0
  404. package/runtime/pi/.codex/skills/invite/SKILL.md +21 -35
  405. package/runtime/pi/.codex/skills/issue/SKILL.md +60 -41
  406. package/runtime/pi/.codex/skills/me/SKILL.md +1 -1
  407. package/runtime/pi/.codex/skills/meeting/SKILL.md +16 -36
  408. package/runtime/pi/.codex/skills/meeting/references/analysis-contract.md +48 -0
  409. package/runtime/pi/.codex/skills/note/SKILL.md +30 -25
  410. package/runtime/pi/.codex/skills/onboarding/SKILL.md +36 -41
  411. package/runtime/pi/.codex/skills/pull/SKILL.md +1 -1
  412. package/runtime/pi/.codex/skills/quest/SKILL.md +27 -66
  413. package/runtime/pi/.codex/skills/reflect/SKILL.md +22 -36
  414. package/runtime/pi/.codex/skills/save/SKILL.md +2 -2
  415. package/runtime/pi/.codex/skills/telemetry/SKILL.md +1 -1
  416. package/runtime/pi/.codex/skills/test/SKILL.md +1 -1
  417. package/runtime/pi/.codex/skills/tutorial/SKILL.md +40 -41
  418. package/runtime/pi/.codex/skills/view/SKILL.md +115 -111
  419. package/runtime/pi/.codex/skills/wrap/SKILL.md +45 -41
  420. package/runtime/pi/.codex/spec-manifest.json +9 -9
  421. package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -48
  422. package/runtime/pi/.pi/extensions/egregore.ts +7 -0
  423. package/runtime/pi/.pi/spec-manifest.json +2 -2
  424. package/runtime/pi/AGENTS.md +62 -58
  425. package/runtime/pi/bin/activity-action.sh +92 -0
  426. package/runtime/pi/bin/activity-data.sh +12 -233
  427. package/runtime/pi/bin/agent.sh +84 -100
  428. package/runtime/pi/bin/artifact-register.sh +164 -0
  429. package/runtime/pi/bin/artifact-writeback.sh +53 -0
  430. package/runtime/pi/bin/artifacts.sh +288 -0
  431. package/runtime/pi/bin/build-sealed-dataroom.mjs +722 -0
  432. package/runtime/pi/bin/capture-run.sh +18 -2
  433. package/runtime/pi/bin/codex-render-spec.mjs +41 -16
  434. package/runtime/pi/bin/codex-session-start.sh +14 -4
  435. package/runtime/pi/bin/codex-skill-render.mjs +9 -7
  436. package/runtime/pi/bin/corpus_map.py +806 -0
  437. package/runtime/pi/bin/corpus_survey.py +304 -0
  438. package/runtime/pi/bin/dashboard-data.sh +11 -171
  439. package/runtime/pi/bin/github-auth.sh +121 -0
  440. package/runtime/pi/bin/graph-batch.sh +7 -0
  441. package/runtime/pi/bin/graph-op.sh +4 -0
  442. package/runtime/pi/bin/graph-projection.sh +54 -0
  443. package/runtime/pi/bin/graph-wal.sh +13 -0
  444. package/runtime/pi/bin/graph.sh +44 -0
  445. package/runtime/pi/bin/handoff-lifecycle-job.sh +146 -0
  446. package/runtime/pi/bin/handoff-lifecycle.sh +280 -0
  447. package/runtime/pi/bin/handoff-preview.sh +169 -0
  448. package/runtime/pi/bin/handoff-run.sh +42 -26
  449. package/runtime/pi/bin/ingest-graph.sh +6 -0
  450. package/runtime/pi/bin/ingest-item.sh +10 -0
  451. package/runtime/pi/bin/ingest.sh +59 -30
  452. package/runtime/pi/bin/invite.sh +6 -0
  453. package/runtime/pi/bin/issue.sh +10 -0
  454. package/runtime/pi/bin/knowledge.sh +10 -0
  455. package/runtime/pi/bin/lib/config.sh +20 -0
  456. package/runtime/pi/bin/lib/context.sh +46 -25
  457. package/runtime/pi/bin/lib/git-sync.sh +70 -6
  458. package/runtime/pi/bin/lib/greeting.sh +34 -5
  459. package/runtime/pi/bin/lib/handoff-meta.sh +97 -0
  460. package/runtime/pi/bin/lib/worktree-links.sh +6 -0
  461. package/runtime/pi/bin/node-run.sh +42 -0
  462. package/runtime/pi/bin/notification.sh +6 -0
  463. package/runtime/pi/bin/observe-context.sh +14 -0
  464. package/runtime/pi/bin/onboarding.sh +6 -0
  465. package/runtime/pi/bin/person.py +4 -0
  466. package/runtime/pi/bin/person.sh +43 -10
  467. package/runtime/pi/bin/pi-observe-context.mjs +93 -0
  468. package/runtime/pi/bin/pi-product-workflows.mjs +1 -1
  469. package/runtime/pi/bin/pi-render-spec.mjs +1 -1
  470. package/runtime/pi/bin/post-update.sh +37 -0
  471. package/runtime/pi/bin/prime-render-spec.mjs +147 -0
  472. package/runtime/pi/bin/publish-artifact.sh +55 -2
  473. package/runtime/pi/bin/quest.sh +9 -0
  474. package/runtime/pi/bin/question.sh +8 -0
  475. package/runtime/pi/bin/research-ingest.sh +7 -0
  476. package/runtime/pi/bin/restore-owned-skills.sh +67 -0
  477. package/runtime/pi/bin/retrieval_eval.py +385 -0
  478. package/runtime/pi/bin/runtime-identity.sh +70 -0
  479. package/runtime/pi/bin/runtime-upgrade.sh +43 -0
  480. package/runtime/pi/bin/scroll.sh +9 -0
  481. package/runtime/pi/bin/search.sh +231 -195
  482. package/runtime/pi/bin/settings.sh +174 -5
  483. package/runtime/pi/bin/startup-check.sh +136 -0
  484. package/runtime/pi/bin/telemetry.sh +522 -247
  485. package/runtime/pi/bin/test-changes.sh +344 -0
  486. package/runtime/pi/bin/thread.sh +9 -0
  487. package/runtime/pi/bin/todo.sh +9 -0
  488. package/runtime/pi/bin/view-render.sh +126 -0
  489. package/runtime/pi/bin/worktree-create.sh +19 -5
  490. package/runtime/pi/egregore_runtime/__init__.py +228 -0
  491. package/runtime/pi/egregore_runtime/adapters/__init__.py +5 -0
  492. package/runtime/pi/egregore_runtime/adapters/invitation.py +237 -0
  493. package/runtime/pi/egregore_runtime/adapters/notification.py +59 -0
  494. package/runtime/pi/egregore_runtime/adapters/qmd.py +1701 -0
  495. package/runtime/pi/egregore_runtime/adapters/qmd_cli.py +210 -0
  496. package/runtime/pi/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
  497. package/runtime/pi/egregore_runtime/admin_gate.py +102 -0
  498. package/runtime/pi/egregore_runtime/artifacts.py +523 -0
  499. package/runtime/pi/egregore_runtime/contracts.py +499 -0
  500. package/runtime/pi/egregore_runtime/entitlements.py +85 -0
  501. package/runtime/pi/egregore_runtime/errors.py +10 -0
  502. package/runtime/pi/egregore_runtime/graph.py +356 -0
  503. package/runtime/pi/egregore_runtime/handoff_lookup.py +196 -0
  504. package/runtime/pi/egregore_runtime/harness_cli.py +1221 -0
  505. package/runtime/pi/egregore_runtime/identity.py +734 -0
  506. package/runtime/pi/egregore_runtime/identity_cli.py +91 -0
  507. package/runtime/pi/egregore_runtime/ingest.py +575 -0
  508. package/runtime/pi/egregore_runtime/ingest_cli.py +254 -0
  509. package/runtime/pi/egregore_runtime/ingest_index_cli.py +147 -0
  510. package/runtime/pi/egregore_runtime/invitation_cli.py +51 -0
  511. package/runtime/pi/egregore_runtime/invitations.py +116 -0
  512. package/runtime/pi/egregore_runtime/issue_cli.py +305 -0
  513. package/runtime/pi/egregore_runtime/issues.py +359 -0
  514. package/runtime/pi/egregore_runtime/knowledge.py +376 -0
  515. package/runtime/pi/egregore_runtime/knowledge_cli.py +173 -0
  516. package/runtime/pi/egregore_runtime/lifecycle.py +888 -0
  517. package/runtime/pi/egregore_runtime/lifecycle_cli.py +269 -0
  518. package/runtime/pi/egregore_runtime/notification_cli.py +125 -0
  519. package/runtime/pi/egregore_runtime/notifications.py +230 -0
  520. package/runtime/pi/egregore_runtime/observe.py +342 -0
  521. package/runtime/pi/egregore_runtime/onboarding.py +290 -0
  522. package/runtime/pi/egregore_runtime/onboarding_cli.py +110 -0
  523. package/runtime/pi/egregore_runtime/policy.py +144 -0
  524. package/runtime/pi/egregore_runtime/profile.py +193 -0
  525. package/runtime/pi/egregore_runtime/quest_cli.py +216 -0
  526. package/runtime/pi/egregore_runtime/question_cli.py +180 -0
  527. package/runtime/pi/egregore_runtime/questions.py +304 -0
  528. package/runtime/pi/egregore_runtime/quests.py +419 -0
  529. package/runtime/pi/egregore_runtime/readiness.py +379 -0
  530. package/runtime/pi/egregore_runtime/research_ingest.py +256 -0
  531. package/runtime/pi/egregore_runtime/research_ingest_cli.py +44 -0
  532. package/runtime/pi/egregore_runtime/runtime.py +464 -0
  533. package/runtime/pi/egregore_runtime/scroll_cli.py +185 -0
  534. package/runtime/pi/egregore_runtime/scrolls.py +726 -0
  535. package/runtime/pi/egregore_runtime/status.py +412 -0
  536. package/runtime/pi/egregore_runtime/status_cli.py +174 -0
  537. package/runtime/pi/egregore_runtime/sync.py +218 -0
  538. package/runtime/pi/egregore_runtime/teamsync.py +139 -0
  539. package/runtime/pi/egregore_runtime/telemetry.py +640 -0
  540. package/runtime/pi/egregore_runtime/thread_cli.py +173 -0
  541. package/runtime/pi/egregore_runtime/threads.py +517 -0
  542. package/runtime/pi/egregore_runtime/todo_cli.py +142 -0
  543. package/runtime/pi/egregore_runtime/todos.py +244 -0
  544. package/runtime/pi/egregore_runtime/upgrade.py +770 -0
  545. package/runtime/pi/egregore_runtime/upgrade_worker.py +53 -0
  546. package/runtime/pi/egregore_runtime/writeback.py +391 -0
  547. package/runtime/pi/egregore_runtime/writeback_cli.py +114 -0
  548. package/runtime/pi/manifest.json +98 -1
  549. package/runtime/prime/.codex/hooks/search-hint.js +21 -52
  550. package/runtime/prime/.codex/hooks.json +3 -3
  551. package/runtime/prime/.codex/skills/activity/SKILL.md +23 -66
  552. package/runtime/prime/.codex/skills/announce/SKILL.md +27 -50
  553. package/runtime/prime/.codex/skills/archive/SKILL.md +21 -36
  554. package/runtime/prime/.codex/skills/checkup/SKILL.md +77 -36
  555. package/runtime/prime/.codex/skills/dark-mode/SKILL.md +1 -1
  556. package/runtime/prime/.codex/skills/dashboard/SKILL.md +17 -47
  557. package/runtime/prime/.codex/skills/handoff/SKILL.md +84 -133
  558. package/runtime/prime/.codex/skills/harvest/SKILL.md +32 -117
  559. package/runtime/prime/.codex/skills/ingest/SKILL.md +47 -25
  560. package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +18 -36
  561. package/runtime/prime/.codex/skills/ingest-user-interview/references/analysis-contract.md +42 -0
  562. package/runtime/prime/.codex/skills/invite/SKILL.md +21 -35
  563. package/runtime/prime/.codex/skills/issue/SKILL.md +60 -41
  564. package/runtime/prime/.codex/skills/me/SKILL.md +1 -1
  565. package/runtime/prime/.codex/skills/meeting/SKILL.md +16 -36
  566. package/runtime/prime/.codex/skills/meeting/references/analysis-contract.md +48 -0
  567. package/runtime/prime/.codex/skills/note/SKILL.md +30 -25
  568. package/runtime/prime/.codex/skills/onboarding/SKILL.md +36 -41
  569. package/runtime/prime/.codex/skills/pull/SKILL.md +1 -1
  570. package/runtime/prime/.codex/skills/quest/SKILL.md +27 -66
  571. package/runtime/prime/.codex/skills/reflect/SKILL.md +22 -36
  572. package/runtime/prime/.codex/skills/save/SKILL.md +2 -2
  573. package/runtime/prime/.codex/skills/telemetry/SKILL.md +1 -1
  574. package/runtime/prime/.codex/skills/test/SKILL.md +1 -1
  575. package/runtime/prime/.codex/skills/tutorial/SKILL.md +40 -41
  576. package/runtime/prime/.codex/skills/view/SKILL.md +115 -111
  577. package/runtime/prime/.codex/skills/wrap/SKILL.md +45 -41
  578. package/runtime/prime/.codex/spec-manifest.json +9 -9
  579. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +57 -48
  580. package/runtime/prime/.prime/agent/extensions/egregore.ts +7 -0
  581. package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
  582. package/runtime/prime/AGENTS.md +62 -58
  583. package/runtime/prime/bin/activity-action.sh +92 -0
  584. package/runtime/prime/bin/activity-data.sh +12 -233
  585. package/runtime/prime/bin/agent.sh +84 -100
  586. package/runtime/prime/bin/artifact-register.sh +164 -0
  587. package/runtime/prime/bin/artifact-writeback.sh +53 -0
  588. package/runtime/prime/bin/artifacts.sh +288 -0
  589. package/runtime/prime/bin/build-sealed-dataroom.mjs +722 -0
  590. package/runtime/prime/bin/capture-run.sh +18 -2
  591. package/runtime/prime/bin/codex-render-spec.mjs +41 -16
  592. package/runtime/prime/bin/codex-session-start.sh +14 -4
  593. package/runtime/prime/bin/codex-skill-render.mjs +9 -7
  594. package/runtime/prime/bin/corpus_map.py +806 -0
  595. package/runtime/prime/bin/corpus_survey.py +304 -0
  596. package/runtime/prime/bin/dashboard-data.sh +11 -171
  597. package/runtime/prime/bin/github-auth.sh +121 -0
  598. package/runtime/prime/bin/graph-batch.sh +7 -0
  599. package/runtime/prime/bin/graph-op.sh +4 -0
  600. package/runtime/prime/bin/graph-projection.sh +54 -0
  601. package/runtime/prime/bin/graph-wal.sh +13 -0
  602. package/runtime/prime/bin/graph.sh +44 -0
  603. package/runtime/prime/bin/handoff-lifecycle-job.sh +146 -0
  604. package/runtime/prime/bin/handoff-lifecycle.sh +280 -0
  605. package/runtime/prime/bin/handoff-preview.sh +169 -0
  606. package/runtime/prime/bin/handoff-run.sh +42 -26
  607. package/runtime/prime/bin/ingest-graph.sh +6 -0
  608. package/runtime/prime/bin/ingest-item.sh +10 -0
  609. package/runtime/prime/bin/ingest.sh +59 -30
  610. package/runtime/prime/bin/invite.sh +6 -0
  611. package/runtime/prime/bin/issue.sh +10 -0
  612. package/runtime/prime/bin/knowledge.sh +10 -0
  613. package/runtime/prime/bin/lib/config.sh +20 -0
  614. package/runtime/prime/bin/lib/context.sh +46 -25
  615. package/runtime/prime/bin/lib/git-sync.sh +70 -6
  616. package/runtime/prime/bin/lib/greeting.sh +34 -5
  617. package/runtime/prime/bin/lib/handoff-meta.sh +97 -0
  618. package/runtime/prime/bin/lib/worktree-links.sh +6 -0
  619. package/runtime/prime/bin/node-run.sh +42 -0
  620. package/runtime/prime/bin/notification.sh +6 -0
  621. package/runtime/prime/bin/observe-context.sh +14 -0
  622. package/runtime/prime/bin/onboarding.sh +6 -0
  623. package/runtime/prime/bin/person.py +4 -0
  624. package/runtime/prime/bin/person.sh +43 -10
  625. package/runtime/prime/bin/pi-observe-context.mjs +93 -0
  626. package/runtime/prime/bin/pi-product-workflows.mjs +1 -1
  627. package/runtime/prime/bin/pi-render-spec.mjs +140 -0
  628. package/runtime/prime/bin/post-update.sh +37 -0
  629. package/runtime/prime/bin/prime-render-spec.mjs +1 -1
  630. package/runtime/prime/bin/publish-artifact.sh +55 -2
  631. package/runtime/prime/bin/quest.sh +9 -0
  632. package/runtime/prime/bin/question.sh +8 -0
  633. package/runtime/prime/bin/research-ingest.sh +7 -0
  634. package/runtime/prime/bin/restore-owned-skills.sh +67 -0
  635. package/runtime/prime/bin/retrieval_eval.py +385 -0
  636. package/runtime/prime/bin/runtime-identity.sh +70 -0
  637. package/runtime/prime/bin/runtime-upgrade.sh +43 -0
  638. package/runtime/prime/bin/scroll.sh +9 -0
  639. package/runtime/prime/bin/search.sh +231 -195
  640. package/runtime/prime/bin/settings.sh +174 -5
  641. package/runtime/prime/bin/startup-check.sh +136 -0
  642. package/runtime/prime/bin/telemetry.sh +522 -247
  643. package/runtime/prime/bin/test-changes.sh +344 -0
  644. package/runtime/prime/bin/thread.sh +9 -0
  645. package/runtime/prime/bin/todo.sh +9 -0
  646. package/runtime/prime/bin/view-render.sh +126 -0
  647. package/runtime/prime/bin/worktree-create.sh +19 -5
  648. package/runtime/prime/egregore_runtime/__init__.py +228 -0
  649. package/runtime/prime/egregore_runtime/adapters/__init__.py +5 -0
  650. package/runtime/prime/egregore_runtime/adapters/invitation.py +237 -0
  651. package/runtime/prime/egregore_runtime/adapters/notification.py +59 -0
  652. package/runtime/prime/egregore_runtime/adapters/qmd.py +1701 -0
  653. package/runtime/prime/egregore_runtime/adapters/qmd_cli.py +210 -0
  654. package/runtime/prime/egregore_runtime/adapters/qmd_embed_worker.py +59 -0
  655. package/runtime/prime/egregore_runtime/admin_gate.py +102 -0
  656. package/runtime/prime/egregore_runtime/artifacts.py +523 -0
  657. package/runtime/prime/egregore_runtime/contracts.py +499 -0
  658. package/runtime/prime/egregore_runtime/entitlements.py +85 -0
  659. package/runtime/prime/egregore_runtime/errors.py +10 -0
  660. package/runtime/prime/egregore_runtime/graph.py +356 -0
  661. package/runtime/prime/egregore_runtime/handoff_lookup.py +196 -0
  662. package/runtime/prime/egregore_runtime/harness_cli.py +1221 -0
  663. package/runtime/prime/egregore_runtime/identity.py +734 -0
  664. package/runtime/prime/egregore_runtime/identity_cli.py +91 -0
  665. package/runtime/prime/egregore_runtime/ingest.py +575 -0
  666. package/runtime/prime/egregore_runtime/ingest_cli.py +254 -0
  667. package/runtime/prime/egregore_runtime/ingest_index_cli.py +147 -0
  668. package/runtime/prime/egregore_runtime/invitation_cli.py +51 -0
  669. package/runtime/prime/egregore_runtime/invitations.py +116 -0
  670. package/runtime/prime/egregore_runtime/issue_cli.py +305 -0
  671. package/runtime/prime/egregore_runtime/issues.py +359 -0
  672. package/runtime/prime/egregore_runtime/knowledge.py +376 -0
  673. package/runtime/prime/egregore_runtime/knowledge_cli.py +173 -0
  674. package/runtime/prime/egregore_runtime/lifecycle.py +888 -0
  675. package/runtime/prime/egregore_runtime/lifecycle_cli.py +269 -0
  676. package/runtime/prime/egregore_runtime/notification_cli.py +125 -0
  677. package/runtime/prime/egregore_runtime/notifications.py +230 -0
  678. package/runtime/prime/egregore_runtime/observe.py +342 -0
  679. package/runtime/prime/egregore_runtime/onboarding.py +290 -0
  680. package/runtime/prime/egregore_runtime/onboarding_cli.py +110 -0
  681. package/runtime/prime/egregore_runtime/policy.py +144 -0
  682. package/runtime/prime/egregore_runtime/profile.py +193 -0
  683. package/runtime/prime/egregore_runtime/quest_cli.py +216 -0
  684. package/runtime/prime/egregore_runtime/question_cli.py +180 -0
  685. package/runtime/prime/egregore_runtime/questions.py +304 -0
  686. package/runtime/prime/egregore_runtime/quests.py +419 -0
  687. package/runtime/prime/egregore_runtime/readiness.py +379 -0
  688. package/runtime/prime/egregore_runtime/research_ingest.py +256 -0
  689. package/runtime/prime/egregore_runtime/research_ingest_cli.py +44 -0
  690. package/runtime/prime/egregore_runtime/runtime.py +464 -0
  691. package/runtime/prime/egregore_runtime/scroll_cli.py +185 -0
  692. package/runtime/prime/egregore_runtime/scrolls.py +726 -0
  693. package/runtime/prime/egregore_runtime/status.py +412 -0
  694. package/runtime/prime/egregore_runtime/status_cli.py +174 -0
  695. package/runtime/prime/egregore_runtime/sync.py +218 -0
  696. package/runtime/prime/egregore_runtime/teamsync.py +139 -0
  697. package/runtime/prime/egregore_runtime/telemetry.py +640 -0
  698. package/runtime/prime/egregore_runtime/thread_cli.py +173 -0
  699. package/runtime/prime/egregore_runtime/threads.py +517 -0
  700. package/runtime/prime/egregore_runtime/todo_cli.py +142 -0
  701. package/runtime/prime/egregore_runtime/todos.py +244 -0
  702. package/runtime/prime/egregore_runtime/upgrade.py +770 -0
  703. package/runtime/prime/egregore_runtime/upgrade_worker.py +53 -0
  704. package/runtime/prime/egregore_runtime/writeback.py +391 -0
  705. package/runtime/prime/egregore_runtime/writeback_cli.py +114 -0
  706. package/runtime/prime/manifest.json +98 -1
@@ -0,0 +1,806 @@
1
+ #!/usr/bin/env python3
2
+ """The corpus map: every document listed, none understood before it must be.
3
+
4
+ Intake makes documents retrievable. It does not make them knowledge, and the
5
+ distance between the two is where a corpus can become expensive: downstream
6
+ statement refinement may run a model or consume expert attention over passages
7
+ whether or not anyone will ever ask about them. On an archive of thousands of
8
+ documents, doing that wholesale spends effort on material that may never be
9
+ retrieved.
10
+
11
+ This module holds the line between the two. It maintains a *map* — one card
12
+ per document, built from evidence intake already recorded, at no model cost —
13
+ and a deterministic *promotion* step that scans only the chunks a real question
14
+ needs. Every promotion is written to a ledger with its trigger and exact local
15
+ scan size. This module makes no model call; downstream model or expert work has
16
+ to record its own actual cost rather than infer spend from promoted text.
17
+
18
+ The map is markdown and JSONL in the memory repo. Files are authoritative;
19
+ everything here can be rebuilt from the intake manifest and the state file,
20
+ and a rebuild never loses a promotion. Agents read the index in one pass and
21
+ open cards on need — nothing is ever re-ingested to answer a question.
22
+
23
+ Nothing in this module knows what an olive or a zone is. Vocabulary, grammar,
24
+ genres and boundaries come from the instance's publisher profile.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import argparse
30
+ import json
31
+ import re
32
+ import sys
33
+ from datetime import datetime, timezone
34
+ from pathlib import Path
35
+
36
+ sys.path.append(str(Path(__file__).resolve().parent))
37
+ import claim_candidates # noqa: E402
38
+ import doc_genre # noqa: E402
39
+ import doc_segment # noqa: E402
40
+ import ingest # noqa: E402
41
+ import lac_record # noqa: E402
42
+ import retrieval_eval # noqa: E402
43
+
44
+ CHUNK_MARK = re.compile(r"<!-- ingest-chunk: ([0-9a-f-]+-c\d{4}-[0-9a-f]+) -->")
45
+ CHUNK_HEAD = re.compile(r"^## Chunk \d{4}$", re.M)
46
+ # How many documents a single index file may list before it is split by
47
+ # publisher class. One file an agent reads in a gulp is the point of the map;
48
+ # past this size the gulp stops being one.
49
+ SHARD_AT = 400
50
+ ABSTRACT_LIMIT = 1800
51
+ STATE_FILE = "state.json"
52
+ ROWS_FILE = "map.jsonl"
53
+ STATEMENTS_FILE = "statements.jsonl"
54
+
55
+
56
+ def now_iso() -> str:
57
+ return datetime.now(timezone.utc).isoformat(timespec="seconds")
58
+
59
+
60
+ def map_dir_for(args) -> Path:
61
+ if getattr(args, "map_dir", None):
62
+ return Path(args.map_dir).resolve()
63
+ return (ingest.ROOT / "memory" / "corpora" / ingest.slug(args.source)).resolve()
64
+
65
+
66
+ def load_state(map_dir: Path) -> dict:
67
+ path = map_dir / STATE_FILE
68
+ if path.is_file():
69
+ return json.loads(path.read_text(encoding="utf-8"))
70
+ return {"documents": {}}
71
+
72
+
73
+ def save_state(map_dir: Path, state: dict) -> None:
74
+ ingest.atomic_json(map_dir / STATE_FILE, state)
75
+
76
+
77
+ def load_rows(map_dir: Path) -> list[dict]:
78
+ path = map_dir / ROWS_FILE
79
+ if not path.is_file():
80
+ return []
81
+ return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
82
+
83
+
84
+ def manifest_for(source_id: str) -> dict:
85
+ directory = ingest.source_dir(ingest.slug(source_id))
86
+ manifest = ingest.load_manifest(directory)
87
+ if not manifest.get("documents"):
88
+ raise SystemExit(f"no manifest for source '{source_id}' under {directory} — run ingest first")
89
+ return manifest
90
+
91
+
92
+ def doc_body(path: Path) -> str:
93
+ """The document's text with intake scaffolding removed.
94
+
95
+ The normalized file interleaves frontmatter, chunk headings and chunk
96
+ markers with the extracted text. Genre markers and abstracts live in the
97
+ text, not the scaffolding.
98
+ """
99
+ try:
100
+ raw = path.read_text(encoding="utf-8")
101
+ except OSError:
102
+ return ""
103
+ if raw.startswith("---"):
104
+ close = raw.find("\n---", 3)
105
+ if close != -1:
106
+ raw = raw[close + 4:]
107
+ raw = CHUNK_MARK.sub("", raw)
108
+ return CHUNK_HEAD.sub("", raw)
109
+
110
+
111
+ def doc_chunks(path: Path) -> list[tuple[str, str]]:
112
+ """(chunk_id, text) pairs in document order."""
113
+ try:
114
+ raw = path.read_text(encoding="utf-8")
115
+ except OSError:
116
+ return []
117
+ parts = CHUNK_MARK.split(raw)
118
+ # parts = [preamble, id1, text1, id2, text2, ...]
119
+ out = []
120
+ for i in range(1, len(parts) - 1, 2):
121
+ out.append((parts[i], parts[i + 1].split("## Chunk")[0].strip()))
122
+ return out
123
+
124
+
125
+ def extract_abstract(text: str) -> str | None:
126
+ """The document's own abstract, verbatim, when it declares one.
127
+
128
+ The map never paraphrases: a card's position statement is the author's
129
+ abstract or nothing. Where no abstract exists the card shows an opening
130
+ excerpt labelled as such — an honest sample, not a summary.
131
+ """
132
+ profile = doc_segment.PROFILES[doc_segment.DEFAULT_PROFILE]
133
+ match = profile["abstract_open"].search(text[:20000])
134
+ if not match:
135
+ return None
136
+ window = text[match.end(): match.end() + 3000]
137
+ keywords = profile["keywords"].search(window)
138
+ body = window[: keywords.start()] if keywords else window[:ABSTRACT_LIMIT]
139
+ body = body.strip()
140
+ if len(body) > ABSTRACT_LIMIT:
141
+ cut = body.rfind(".", 0, ABSTRACT_LIMIT)
142
+ body = body[: cut + 1] if cut > 200 else body[:ABSTRACT_LIMIT]
143
+ return body or None
144
+
145
+
146
+ def resolve_doc(manifest: dict, needle: str) -> dict:
147
+ """One document record from an id prefix, a source-path fragment, or a title."""
148
+ docs = manifest["documents"]
149
+ hits = [d for d in docs if d["id"].startswith(needle)]
150
+ if not hits:
151
+ lowered = needle.lower()
152
+ hits = [d for d in docs
153
+ if lowered in d.get("source_path", "").lower()
154
+ or lowered in d.get("title", "").lower()]
155
+ if not hits:
156
+ raise SystemExit(f"no document matches '{needle}'")
157
+ if len(hits) > 1:
158
+ listing = "\n".join(f" {d['id'][:12]} {d.get('source_path')}" for d in hits[:10])
159
+ raise SystemExit(f"'{needle}' is ambiguous — {len(hits)} matches:\n{listing}")
160
+ return hits[0]
161
+
162
+
163
+ def ledger_append(map_dir: Path, action: str, target: str, trigger: str,
164
+ cost: str, outcome: str) -> None:
165
+ """One row per act that spent something, appended, never rewritten.
166
+
167
+ The ledger is the corpus's account of itself: what was done, at whose
168
+ request, at what cost, to what effect. It is the difference between
169
+ claiming the pipeline is economical and being able to show it.
170
+ """
171
+ path = map_dir / "ledger.md"
172
+ if not path.is_file():
173
+ path.write_text(
174
+ "# Ledger\n\n"
175
+ "Every act that spent extraction, local compute, model attention, or expert time. "
176
+ "`bin/corpus_map.py` records deterministic compute only; model or expert work "
177
+ "must add its own actual cost. Appended by `bin/corpus_map.py`, never edited.\n\n"
178
+ "| when | action | target | trigger | cost | outcome |\n"
179
+ "|---|---|---|---|---|---|\n",
180
+ encoding="utf-8",
181
+ )
182
+ def cell(value: str) -> str:
183
+ return (value or "—").replace("|", "\\|").replace("\n", " ")
184
+ with path.open("a", encoding="utf-8") as handle:
185
+ handle.write(f"| {now_iso()} | {cell(action)} | {cell(target)} | "
186
+ f"{cell(trigger)} | {cell(cost)} | {cell(outcome)} |\n")
187
+
188
+
189
+ def gap_append(map_dir: Path, entry: dict) -> None:
190
+ path = map_dir / "gaps.md"
191
+ if not path.is_file():
192
+ path.write_text(
193
+ "# Gaps\n\nQuestions the corpus could not answer, with the reason. "
194
+ "This is the acquisition and review queue — each entry is work for "
195
+ "whoever curates the corpus, not an apology.\n",
196
+ encoding="utf-8",
197
+ )
198
+ with path.open("a", encoding="utf-8") as handle:
199
+ handle.write(f"\n## {entry.get('asked_at', now_iso())} — {entry.get('reason', 'gap')}\n\n"
200
+ f"> {entry.get('question', '')}\n\n"
201
+ f"- zone: `{entry.get('zone', '')}`\n")
202
+ for follow_up in entry.get("follow_ups") or []:
203
+ handle.write(f"- follow-up: {follow_up}\n")
204
+
205
+
206
+ # ---------------------------------------------------------------- build
207
+
208
+
209
+ def build_rows(manifest: dict, state: dict, profile: dict | None) -> list[dict]:
210
+ genres = doc_genre.load_genres(profile or {})
211
+ data_root = ingest.data_root()
212
+ rows = []
213
+ for doc in manifest["documents"]:
214
+ doc_state = state["documents"].get(doc["id"], {})
215
+ row = {
216
+ "id": doc["id"],
217
+ "title": doc.get("title", ""),
218
+ "source_path": doc.get("source_path", ""),
219
+ "revision": doc.get("revision", ""),
220
+ "status": doc.get("status", ""),
221
+ "chunks": len(doc.get("chunks") or []),
222
+ "boundaries": doc.get("boundaries") or {},
223
+ "tier": (doc.get("provenance") or {}).get("tier", ""),
224
+ "publisher_class": (doc.get("provenance") or {}).get("publisher_class", ""),
225
+ "zone_derived": bool((doc.get("provenance") or {}).get("derived")),
226
+ "year": (doc.get("structure") or {}).get("publication_year"),
227
+ "year_source": (doc.get("structure") or {}).get("year_source", ""),
228
+ "extractor": (doc.get("extraction") or {}).get("extractor", ""),
229
+ "uses": doc_state.get("uses", 0),
230
+ "last_used": doc_state.get("last_used"),
231
+ "promoted_chunks": len(doc_state.get("promoted", {})),
232
+ "card": bool(doc_state.get("card")),
233
+ }
234
+ file_path = doc.get("file_path")
235
+ text = doc_body(data_root / file_path) if file_path else ""
236
+ row["text_chars"] = len(text)
237
+ row["has_abstract"] = bool(text) and bool(
238
+ doc_segment.PROFILES[doc_segment.DEFAULT_PROFILE]["abstract_open"].search(text[:20000]))
239
+ if text and genres:
240
+ genre = doc_genre.classify(text[:8000], genres)
241
+ row["genre"] = genre.name
242
+ row["genre_confident"] = genre.confident
243
+ else:
244
+ row["genre"] = ""
245
+ row["genre_confident"] = False
246
+ rows.append(row)
247
+ return rows
248
+
249
+
250
+ def state_marker(row: dict) -> str:
251
+ if row["status"] == "extraction_failed":
252
+ return "✕"
253
+ if row["promoted_chunks"]:
254
+ return "◆"
255
+ if row["uses"]:
256
+ return "●"
257
+ return "○"
258
+
259
+
260
+ def index_table(rows: list[dict]) -> str:
261
+ lines = ["| st | id | title | year | zone | tier | genre | chunks | uses |",
262
+ "|---|---|---|---|---|---|---|---|---|"]
263
+ for row in sorted(rows, key=lambda r: (-(r["uses"]), r.get("source_path", ""))):
264
+ zone = row["boundaries"].get("zone", "")
265
+ title = row["title"][:64].replace("|", "\\|")
266
+ genre = row["genre"] or "—"
267
+ if row["genre"] and not row["genre_confident"]:
268
+ genre += "?"
269
+ lines.append(
270
+ f"| {state_marker(row)} | `{row['id'][:12]}` | {title} "
271
+ f"| {row['year'] or '—'} | {zone or '—'}{'*' if row['zone_derived'] else ''} "
272
+ f"| {row['tier'] or '—'} | {genre} | {row['chunks']} | {row['uses']} |")
273
+ return "\n".join(lines)
274
+
275
+
276
+ def write_index(map_dir: Path, source_id: str, rows: list[dict], manifest: dict) -> None:
277
+ failed = [r for r in rows if r["status"] == "extraction_failed"]
278
+ ok = [r for r in rows if r["status"] != "extraction_failed"]
279
+ zoneless = [r for r in ok if not r["boundaries"].get("zone")]
280
+ yearless = [r for r in ok if not r["year"]]
281
+ promoted = [r for r in ok if r["promoted_chunks"]]
282
+ total_chunks = sum(r["chunks"] for r in ok)
283
+ promoted_chunks = sum(r["promoted_chunks"] for r in ok)
284
+ total_chars = sum(r["text_chars"] for r in ok)
285
+
286
+ head = [
287
+ f"# Corpus map — `{source_id}`",
288
+ "",
289
+ f"Rebuilt {now_iso()} by `bin/corpus_map.py build`. One card per document; "
290
+ "nothing here required a model. Position statements are the authors' own "
291
+ "abstracts, verbatim — the map enriches, it never summarises.",
292
+ "",
293
+ "## How to use this map",
294
+ "",
295
+ "1. **Read this index** (or grep `map.jsonl`) — never re-ingest to find a document.",
296
+ "2. **Retrieve** for a question: `python3 bin/corpus_map.py retrieve --source "
297
+ f"{source_id} --question \"…\" --zone <zone>`.",
298
+ "3. **Touch** documents a question used: `… touch <id> --source "
299
+ f"{source_id} --question \"…\"` — usage is the curation signal.",
300
+ "4. **Promote on need, never wholesale**: `… promote <id> --reason \"<the question>\"` "
301
+ "extracts advice candidates from just that document, at a ledger-recorded cost.",
302
+ "5. **Validate** filled statements (`… validate <id>`), then **answer** "
303
+ "(`… answer --question \"…\"`) — refusals are recorded in `gaps.md`, not improvised around.",
304
+ "",
305
+ "States: ○ mapped · ● used · ◆ promoted · ✕ extraction failed. "
306
+ "`*` marks a zone derived from provenance rather than stated.",
307
+ "",
308
+ "## Coverage — honest counts",
309
+ "",
310
+ f"- documents: **{len(rows)}** ({len(ok)} readable, {len(failed)} failed extraction)",
311
+ f"- chunks: **{total_chunks}** · extracted text: ~{total_chars:,} chars",
312
+ f"- without a zone (unservable until placed): **{len(zoneless)}**",
313
+ f"- without a year (no freshness weighting): **{len(yearless)}**",
314
+ f"- promoted: **{len(promoted)}** documents, {promoted_chunks} chunks "
315
+ f"({(100 * promoted_chunks / total_chunks):.1f}% of the corpus)" if total_chunks else
316
+ "- promoted: 0",
317
+ ]
318
+ if failed:
319
+ head.append("")
320
+ head.append("Failed extraction (reported, never silently indexed):")
321
+ for row in failed:
322
+ head.append(f"- `{row['id'][:12]}` {row['source_path']}")
323
+ head.append("")
324
+ head.append("## Spend")
325
+ head.append("")
326
+ head.append(f"- map build: deterministic, model cost **0** (read ~{total_chars:,} chars locally)")
327
+ if total_chunks:
328
+ head.append(
329
+ f"- promotion so far: **{promoted_chunks} of {total_chunks} chunks** scanned "
330
+ f"deterministically — `corpus_map.py` made 0 model calls; see `ledger.md`")
331
+ head.append("")
332
+
333
+ if len(ok) + len(failed) <= SHARD_AT:
334
+ body = ["## Documents", "", index_table(rows)]
335
+ else:
336
+ shard_dir = map_dir / "index"
337
+ shard_dir.mkdir(exist_ok=True)
338
+ groups: dict[str, list[dict]] = {}
339
+ for row in rows:
340
+ groups.setdefault(row["publisher_class"] or "unclassified", []).append(row)
341
+ body = ["## Documents", "",
342
+ f"{len(rows)} documents, sharded by publisher class:", ""]
343
+ for name in sorted(groups):
344
+ shard = shard_dir / f"{ingest.slug(name)}.md"
345
+ shard.write_text(f"# {name} — {len(groups[name])} documents\n\n"
346
+ + index_table(groups[name]) + "\n", encoding="utf-8")
347
+ body.append(f"- [`{name}`](index/{ingest.slug(name)}.md) — {len(groups[name])}")
348
+ (map_dir / "index.md").write_text("\n".join(head + body) + "\n", encoding="utf-8")
349
+
350
+
351
+ def cmd_build(args) -> int:
352
+ manifest = manifest_for(args.source)
353
+ map_dir = map_dir_for(args)
354
+ map_dir.mkdir(parents=True, exist_ok=True)
355
+ state = load_state(map_dir)
356
+ profile = ingest.publisher_profile()
357
+ rows = build_rows(manifest, state, profile)
358
+ with (map_dir / ROWS_FILE).open("w", encoding="utf-8") as handle:
359
+ for row in rows:
360
+ handle.write(json.dumps(row, ensure_ascii=False) + "\n")
361
+ write_index(map_dir, ingest.slug(args.source), rows, manifest)
362
+ save_state(map_dir, state)
363
+ total_chars = sum(r["text_chars"] for r in rows)
364
+ ledger_append(map_dir, "map build", f"{len(rows)} documents", "operator",
365
+ f"~{total_chars:,} chars read locally · model 0", "index.md + map.jsonl rebuilt")
366
+ print(json.dumps({"map_dir": str(map_dir), "documents": len(rows),
367
+ "failed": sum(1 for r in rows if r["status"] == "extraction_failed"),
368
+ "model_cost": 0}, ensure_ascii=False, indent=2))
369
+ return 0
370
+
371
+
372
+ # ---------------------------------------------------------------- card
373
+
374
+
375
+ def materialize_card(map_dir: Path, doc: dict, row: dict | None) -> Path:
376
+ data_root = ingest.data_root()
377
+ text = doc_body(data_root / doc["file_path"]) if doc.get("file_path") else ""
378
+ abstract = extract_abstract(text) if text else None
379
+ provenance = doc.get("provenance") or {}
380
+ structure = doc.get("structure") or {}
381
+ lines = [
382
+ f"# {doc.get('title', doc['id'][:12])}",
383
+ "",
384
+ f"- id: `{doc['id']}` · revision `{doc.get('revision')}`",
385
+ f"- source: `{doc.get('source_path')}`",
386
+ f"- boundaries: `{json.dumps(doc.get('boundaries') or {}, ensure_ascii=False)}`",
387
+ f"- publisher: {provenance.get('publisher_class') or 'unknown'} · tier {provenance.get('tier') or '—'}"
388
+ + (" · zone derived from provenance" if provenance.get("derived") else ""),
389
+ f"- year: {structure.get('publication_year') or 'unknown'}"
390
+ + (f" ({structure.get('year_source')})" if structure.get("year_source") else ""),
391
+ f"- genre: {(row or {}).get('genre') or 'unclassified'}"
392
+ + ("" if (row or {}).get("genre_confident") else " (unconfident)" if (row or {}).get("genre") else ""),
393
+ f"- chunks: {len(doc.get('chunks') or [])} · extracted ~{len(text):,} chars",
394
+ "",
395
+ ]
396
+ if abstract:
397
+ lines += ["## Abstract — verbatim from the source", "", "> " + abstract.replace("\n", "\n> "), ""]
398
+ elif text:
399
+ opening = " ".join(text.split()[:80])
400
+ lines += ["## Opening excerpt — no abstract in source; this is a sample, not a summary",
401
+ "", "> " + opening, ""]
402
+ digest = map_dir / "digests" / f"{doc['id'][:12]}.md"
403
+ if digest.is_file():
404
+ lines += [f"Digest: [`digests/{digest.name}`](digests/{digest.name})", ""]
405
+ cards = map_dir / "papers"
406
+ cards.mkdir(parents=True, exist_ok=True)
407
+ path = cards / f"{doc['id'][:12]}.md"
408
+ path.write_text("\n".join(lines), encoding="utf-8")
409
+ return path
410
+
411
+
412
+ def cmd_card(args) -> int:
413
+ manifest = manifest_for(args.source)
414
+ map_dir = map_dir_for(args)
415
+ state = load_state(map_dir)
416
+ doc = resolve_doc(manifest, args.document)
417
+ rows = {r["id"]: r for r in load_rows(map_dir)}
418
+ path = materialize_card(map_dir, doc, rows.get(doc["id"]))
419
+ state["documents"].setdefault(doc["id"], {})["card"] = True
420
+ save_state(map_dir, state)
421
+ print(path)
422
+ return 0
423
+
424
+
425
+ # ---------------------------------------------------------------- touch
426
+
427
+
428
+ def cmd_touch(args) -> int:
429
+ manifest = manifest_for(args.source)
430
+ map_dir = map_dir_for(args)
431
+ state = load_state(map_dir)
432
+ touched = []
433
+ for needle in args.documents:
434
+ doc = resolve_doc(manifest, needle)
435
+ entry = state["documents"].setdefault(doc["id"], {})
436
+ entry["uses"] = entry.get("uses", 0) + 1
437
+ entry["last_used"] = now_iso()
438
+ touched.append(doc["id"][:12])
439
+ save_state(map_dir, state)
440
+ ledger_append(map_dir, "touch", ", ".join(touched), args.question or "—",
441
+ "0", "usage recorded")
442
+ print(json.dumps({"touched": touched}))
443
+ return 0
444
+
445
+
446
+ # ---------------------------------------------------------------- promote
447
+
448
+
449
+ def promotion_context(doc: dict) -> dict:
450
+ provenance = doc.get("provenance") or {}
451
+ structure = doc.get("structure") or {}
452
+ boundaries = doc.get("boundaries") or {}
453
+ return {
454
+ "document": doc["id"],
455
+ "revision": doc.get("revision", ""),
456
+ "zone": boundaries.get("zone", ""),
457
+ "crop": boundaries.get("crop", ""),
458
+ "tier": provenance.get("tier", ""),
459
+ "zone_confidence": provenance.get("confidence"),
460
+ "year": structure.get("publication_year"),
461
+ }
462
+
463
+
464
+ def cmd_promote(args) -> int:
465
+ manifest = manifest_for(args.source)
466
+ map_dir = map_dir_for(args)
467
+ state = load_state(map_dir)
468
+ profile = ingest.publisher_profile() or {}
469
+ grammar = claim_candidates.load_grammar(profile)
470
+ if not grammar.get("obligation"):
471
+ raise SystemExit("the publisher profile declares no claim grammar — "
472
+ "promotion would find nothing; add claim_grammar first")
473
+ doc = resolve_doc(manifest, args.document)
474
+ if not doc.get("file_path"):
475
+ raise SystemExit(f"{doc['id'][:12]} has no extracted text to promote")
476
+ pairs = doc_chunks(ingest.data_root() / doc["file_path"])
477
+ wanted = None
478
+ if args.chunks and args.chunks != "all":
479
+ indexes = {int(i) for i in args.chunks.split(",")}
480
+ pairs = [(cid, text) for n, (cid, text) in enumerate(pairs, 1) if n in indexes]
481
+ wanted = sorted(indexes)
482
+ if not pairs:
483
+ raise SystemExit("no chunks matched")
484
+
485
+ digest_dir = map_dir / "digests"
486
+ digest_dir.mkdir(parents=True, exist_ok=True)
487
+ context = promotion_context(doc)
488
+ chars_in = sum(len(t) for _, t in pairs)
489
+ candidates_out = []
490
+ lines = [
491
+ f"# Digest — {doc.get('title')}",
492
+ "",
493
+ f"- source: `{doc.get('source_path')}` · revision `{doc.get('revision')}`",
494
+ f"- zone `{context['zone'] or '—'}` · tier {context['tier'] or '—'} · year {context['year'] or '—'}",
495
+ f"- promoted {now_iso()} · reason: {args.reason}",
496
+ f"- chunks: {wanted or 'all'} ({len(pairs)} of {len(doc.get('chunks') or [])})",
497
+ "",
498
+ "Candidates below are the source's own sentences, found deterministically, "
499
+ "with the features whose loss would invert or unground them. To refine one, "
500
+ f"write slots to `digests/{doc['id'][:12]}.slots.json` "
501
+ '(`{"<candidate id>": {"claim": …, "growth_stage": …, "season_window": …}}`), '
502
+ "then run `validate`. Unrefined candidates are recorded verbatim — the "
503
+ "deterministic path rewrites nothing and cannot misquote.",
504
+ "",
505
+ ]
506
+ for chunk_id, text in pairs:
507
+ found = claim_candidates.find(text, grammar)
508
+ if not found:
509
+ continue
510
+ lines.append(f"## Chunk `{chunk_id}`")
511
+ lines.append("")
512
+ for n, candidate in enumerate(found, 1):
513
+ cand_id = f"{chunk_id}-s{n:02d}"
514
+ candidates_out.append({"id": cand_id, "chunk": chunk_id, **candidate.as_dict()})
515
+ risks = ", ".join(candidate.risks) or "none"
516
+ lines.append(f"### `{cand_id}` — {candidate.polarity} · hazards: {risks}")
517
+ lines.append("")
518
+ lines.append(f"> {candidate.text}")
519
+ lines.append("")
520
+ (digest_dir / f"{doc['id'][:12]}.md").write_text("\n".join(lines), encoding="utf-8")
521
+ ingest.atomic_json(digest_dir / f"{doc['id'][:12]}.candidates.json",
522
+ {"document": doc["id"], "context": context, "reason": args.reason,
523
+ "promoted_at": now_iso(), "candidates": candidates_out})
524
+
525
+ entry = state["documents"].setdefault(doc["id"], {})
526
+ promoted = entry.setdefault("promoted", {})
527
+ for chunk_id, _ in pairs:
528
+ promoted[chunk_id] = {"reason": args.reason, "at": now_iso()}
529
+ save_state(map_dir, state)
530
+ materialize_card(map_dir, doc, None)
531
+ entry["card"] = True
532
+ save_state(map_dir, state)
533
+
534
+ ledger_append(
535
+ map_dir, "promote", f"{doc['id'][:12]} ({len(pairs)} chunks)", args.reason,
536
+ f"{chars_in:,} source chars scanned deterministically; 0 model calls",
537
+ f"{len(candidates_out)} advice candidates → digests/{doc['id'][:12]}.md")
538
+ print(json.dumps({"document": doc["id"][:12], "chunks": len(pairs),
539
+ "candidates": len(candidates_out),
540
+ "digest": str(digest_dir / (doc['id'][:12] + '.md'))},
541
+ ensure_ascii=False, indent=2))
542
+ return 0
543
+
544
+
545
+ # ---------------------------------------------------------------- validate
546
+
547
+
548
+ def cmd_validate(args) -> int:
549
+ manifest = manifest_for(args.source)
550
+ map_dir = map_dir_for(args)
551
+ profile = ingest.publisher_profile() or {}
552
+ grammar = claim_candidates.load_grammar(profile)
553
+ doc = resolve_doc(manifest, args.document)
554
+ digest_dir = map_dir / "digests"
555
+ saved_path = digest_dir / f"{doc['id'][:12]}.candidates.json"
556
+ if not saved_path.is_file():
557
+ raise SystemExit(f"{doc['id'][:12]} has no promotion — run promote first")
558
+ saved = json.loads(saved_path.read_text(encoding="utf-8"))
559
+ slots_path = digest_dir / f"{doc['id'][:12]}.slots.json"
560
+ slots_all = json.loads(slots_path.read_text(encoding="utf-8")) if slots_path.is_file() else {}
561
+
562
+ built = []
563
+ statements = []
564
+ for entry in saved["candidates"]:
565
+ candidate = claim_candidates.Candidate(**{
566
+ k: v for k, v in entry.items() if k not in ("id", "chunk")})
567
+ context = dict(saved["context"])
568
+ context["chunk"] = entry["chunk"]
569
+ statement = lac_record.build(candidate, context, slots_all.get(entry["id"]), grammar)
570
+ built.append(statement)
571
+ row = statement.as_dict()
572
+ row["id"] = entry["id"]
573
+ row["document_title"] = doc.get("title", "")
574
+ row["reason"] = saved.get("reason", "")
575
+ row["validated_at"] = now_iso()
576
+ statements.append(row)
577
+
578
+ # Rows for this document replace its previous rows; other documents' rows
579
+ # are kept as they are. Statement identity is the candidate id, which is
580
+ # anchored to the chunk hash — a re-promotion after content change gets new
581
+ # ids rather than silently overwriting old evidence.
582
+ path = map_dir / STATEMENTS_FILE
583
+ kept = []
584
+ if path.is_file():
585
+ kept = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()
586
+ if line.strip() and not json.loads(line)["source"]["document"] == doc["id"]]
587
+ with path.open("w", encoding="utf-8") as handle:
588
+ for row in kept + statements:
589
+ handle.write(json.dumps(row, ensure_ascii=False) + "\n")
590
+
591
+ summary = lac_record.summarise(built)
592
+ ledger_append(map_dir, "validate", doc["id"][:12], saved.get("reason", "—"),
593
+ f"{len(statements)} statements checked against source",
594
+ json.dumps(summary, ensure_ascii=False) if isinstance(summary, dict) else str(summary))
595
+ print(json.dumps({"document": doc["id"][:12], "statements": len(statements),
596
+ "servable": sum(1 for s in statements if s.get("servable")),
597
+ "needs_review": sum(1 for s in statements if s.get("status") == "needs_review"),
598
+ "rejected": sum(1 for s in statements if s.get("status") == "rejected")},
599
+ ensure_ascii=False, indent=2))
600
+ return 0
601
+
602
+
603
+ # ---------------------------------------------------------------- retrieve / answer
604
+
605
+
606
+ def load_vocabulary(paths: list[str]):
607
+ if not paths:
608
+ return None
609
+ import term_expand
610
+ return term_expand.load_vocabulary([Path(p) for p in paths])
611
+
612
+
613
+ def hierarchy_from(args, profile: dict | None) -> dict:
614
+ if getattr(args, "fixture", None):
615
+ fixture = json.loads(Path(args.fixture).read_text(encoding="utf-8"))
616
+ return fixture.get("zone_hierarchy") or {}
617
+ return (profile or {}).get("zone_hierarchy") or {}
618
+
619
+
620
+ def cmd_retrieve(args) -> int:
621
+ directory = ingest.source_dir(ingest.slug(args.source))
622
+ passages = retrieval_eval.load_passages(directory / "manifest.json", ingest.data_root())
623
+ index = retrieval_eval.Index(passages)
624
+ profile = ingest.publisher_profile()
625
+ hierarchy = hierarchy_from(args, profile)
626
+ allowed = retrieval_eval.zones_in_scope(args.zone, hierarchy)
627
+ vocabulary = load_vocabulary(args.vocabulary)
628
+ results = index.search(args.question, allowed, limit=args.limit, vocabulary=vocabulary)
629
+ out = []
630
+ for score, passage in results:
631
+ out.append({"score": round(score, 2), "chunk": passage.id,
632
+ "document": passage.document, "zone": passage.zone,
633
+ "tier": passage.tier,
634
+ "snippet": " ".join(passage.text.split())[:220]})
635
+ print(json.dumps({"question": args.question, "zone": args.zone,
636
+ "passages_in_corpus": len(passages), "results": out},
637
+ ensure_ascii=False, indent=2))
638
+ return 0
639
+
640
+
641
+ def rehydrate(row: dict) -> lac_record.Statement:
642
+ statement = lac_record.Statement(
643
+ claim=row["claim"], polarity=row.get("polarity", ""),
644
+ zone=row.get("zone", ""), crop=row.get("crop", ""),
645
+ growth_stage=row.get("growth_stage", ""),
646
+ season_window=row.get("season_window", ""),
647
+ source=row.get("source") or {}, trust_tier=row.get("trust_tier", ""),
648
+ freshness_ts=row.get("freshness_ts"),
649
+ violations=[lac_record.Violation(v["kind"], v["severity"], v["detail"])
650
+ for v in row.get("violations") or []])
651
+ statement.integrity_score = row.get("integrity_score")
652
+ return statement
653
+
654
+
655
+ def cmd_answer(args) -> int:
656
+ import answer as answer_module
657
+ map_dir = map_dir_for(args)
658
+ profile = ingest.publisher_profile() or {}
659
+ path = map_dir / STATEMENTS_FILE
660
+ rows = ([json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
661
+ if path.is_file() else [])
662
+ statements = [rehydrate(row) for row in rows]
663
+ vocabulary = load_vocabulary(args.vocabulary)
664
+ hierarchy = hierarchy_from(args, profile)
665
+ manifest = manifest_for(args.source)
666
+ revisions = {d["id"]: d.get("revision") for d in manifest["documents"]}
667
+ genre_of = {r["id"]: r.get("genre") for r in load_rows(map_dir) if r.get("genre")}
668
+ relevance = answer_module.score_statements(args.question, statements, vocabulary=vocabulary)
669
+ result = answer_module.assemble(
670
+ args.question, args.zone, statements, hierarchy,
671
+ follow_up_rules=answer_module.load_follow_up_rules(profile),
672
+ current_revisions=revisions, vocabulary=vocabulary, relevance=relevance,
673
+ genre_of=genre_of, genre_preferences=doc_genre.load_preferences(profile))
674
+ print(answer_module.render(result))
675
+ if result.gap:
676
+ gap_append(map_dir, answer_module.gap_entry(result, now_iso()))
677
+ ledger_append(map_dir, "answer (refused)", "—", args.question,
678
+ f"{len(statements)} statements considered", f"gap recorded: {result.gap}")
679
+ else:
680
+ ledger_append(map_dir, "answer", f"{len(result.served)} statements served",
681
+ args.question, f"{len(statements)} statements considered",
682
+ "served with provenance and tier")
683
+ return 0
684
+
685
+
686
+ # ---------------------------------------------------------------- status
687
+
688
+
689
+ def cmd_status(args) -> int:
690
+ map_dir = map_dir_for(args)
691
+ rows = load_rows(map_dir)
692
+ if not rows:
693
+ raise SystemExit(f"no map at {map_dir} — run build first")
694
+ # The rows snapshot promotion state as of the last build; promotions since
695
+ # then live in the state file. Status must answer for now, not for then.
696
+ state = load_state(map_dir)
697
+ for row in rows:
698
+ doc_state = state["documents"].get(row["id"], {})
699
+ row["promoted_chunks"] = len(doc_state.get("promoted", {}))
700
+ row["uses"] = doc_state.get("uses", row.get("uses", 0))
701
+ ok = [r for r in rows if r["status"] != "extraction_failed"]
702
+ total_chunks = sum(r["chunks"] for r in ok)
703
+ promoted_chunks = sum(r["promoted_chunks"] for r in ok)
704
+ total_chars = sum(r["text_chars"] for r in ok)
705
+ promoted_docs = [r for r in ok if r["promoted_chunks"]]
706
+ statements_path = map_dir / STATEMENTS_FILE
707
+ statement_rows = ([json.loads(line) for line in
708
+ statements_path.read_text(encoding="utf-8").splitlines() if line.strip()]
709
+ if statements_path.is_file() else [])
710
+ print(json.dumps({
711
+ "map_dir": str(map_dir),
712
+ "documents": len(rows),
713
+ "extraction_failed": len(rows) - len(ok),
714
+ "chunks": total_chunks,
715
+ "corpus_text_chars": total_chars,
716
+ "promoted": {
717
+ "documents": len(promoted_docs),
718
+ "chunks": promoted_chunks,
719
+ "share_of_corpus": f"{(100 * promoted_chunks / total_chunks):.1f}%" if total_chunks else "0%",
720
+ },
721
+ "statements": {
722
+ "total": len(statement_rows),
723
+ "servable": sum(1 for s in statement_rows if s.get("servable")),
724
+ "needs_review": sum(1 for s in statement_rows if s.get("status") == "needs_review"),
725
+ "rejected": sum(1 for s in statement_rows if s.get("status") == "rejected"),
726
+ },
727
+ "counterfactual": {
728
+ "wholesale_deterministic_scan_chars": total_chars,
729
+ "demand_driven_deterministic_scan_chars_estimate": sum(
730
+ r["text_chars"] * (r["promoted_chunks"] / r["chunks"])
731
+ for r in promoted_docs if r["chunks"]),
732
+ "corpus_map_model_calls": 0,
733
+ "corpus_map_model_input_chars": 0,
734
+ },
735
+ }, ensure_ascii=False, indent=2))
736
+ return 0
737
+
738
+
739
+ # ---------------------------------------------------------------- entry
740
+
741
+
742
+ def parser() -> argparse.ArgumentParser:
743
+ top = argparse.ArgumentParser(description=__doc__.splitlines()[0])
744
+ sub = top.add_subparsers(dest="command", required=True)
745
+
746
+ def common(p):
747
+ p.add_argument("--source", required=True, help="ingest source id")
748
+ p.add_argument("--map-dir", help="map directory (default memory/corpora/<source>)")
749
+
750
+ build = sub.add_parser("build", help="(re)build the map from the intake manifest — deterministic")
751
+ common(build)
752
+ build.set_defaults(func=cmd_build)
753
+
754
+ card = sub.add_parser("card", help="materialize one document's card with its verbatim abstract")
755
+ common(card)
756
+ card.add_argument("document")
757
+ card.set_defaults(func=cmd_card)
758
+
759
+ touch = sub.add_parser("touch", help="record that a question used these documents")
760
+ common(touch)
761
+ touch.add_argument("documents", nargs="+")
762
+ touch.add_argument("--question", default="")
763
+ touch.set_defaults(func=cmd_touch)
764
+
765
+ promote = sub.add_parser("promote", help="extract advice candidates from one document, on demand")
766
+ common(promote)
767
+ promote.add_argument("document")
768
+ promote.add_argument("--reason", required=True, help="the question or need that triggered this")
769
+ promote.add_argument("--chunks", default="all", help="comma-separated chunk numbers, or 'all'")
770
+ promote.set_defaults(func=cmd_promote)
771
+
772
+ validate = sub.add_parser("validate", help="check filled statements against their source sentences")
773
+ common(validate)
774
+ validate.add_argument("document")
775
+ validate.set_defaults(func=cmd_validate)
776
+
777
+ retrieve = sub.add_parser("retrieve", help="BM25 over the corpus, zone-bounded")
778
+ common(retrieve)
779
+ retrieve.add_argument("--question", required=True)
780
+ retrieve.add_argument("--zone", required=True)
781
+ retrieve.add_argument("--limit", type=int, default=5)
782
+ retrieve.add_argument("--vocabulary", nargs="*", default=[])
783
+ retrieve.add_argument("--fixture", help="fixture JSON supplying zone_hierarchy")
784
+ retrieve.set_defaults(func=cmd_retrieve)
785
+
786
+ ans = sub.add_parser("answer", help="assemble an answer from validated statements")
787
+ common(ans)
788
+ ans.add_argument("--question", required=True)
789
+ ans.add_argument("--zone", required=True)
790
+ ans.add_argument("--vocabulary", nargs="*", default=[])
791
+ ans.add_argument("--fixture", help="fixture JSON supplying zone_hierarchy")
792
+ ans.set_defaults(func=cmd_answer)
793
+
794
+ status = sub.add_parser("status", help="map, promotion and spend summary")
795
+ common(status)
796
+ status.set_defaults(func=cmd_status)
797
+ return top
798
+
799
+
800
+ def main() -> int:
801
+ args = parser().parse_args()
802
+ return args.func(args)
803
+
804
+
805
+ if __name__ == "__main__":
806
+ sys.exit(main())