create-egregore 0.19.33 → 0.20.1

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 (250) hide show
  1. package/assets/egregore-launcher.js +141 -14
  2. package/bin/cli.js +19 -3
  3. package/lib/connect-runtime.js +4 -2
  4. package/lib/local.js +15 -1
  5. package/lib/prime-runtime.js +208 -0
  6. package/lib/setup.js +66 -5
  7. package/package.json +4 -3
  8. package/runtime/codex/.codex/config.toml +3 -0
  9. package/runtime/codex/.codex/skills/archive/SKILL.md +1 -1
  10. package/runtime/codex/.codex/skills/branch/SKILL.md +1 -1
  11. package/runtime/codex/.codex/skills/checkup/SKILL.md +1 -1
  12. package/runtime/codex/.codex/skills/commit/SKILL.md +1 -1
  13. package/runtime/codex/.codex/skills/contribute/SKILL.md +1 -1
  14. package/runtime/codex/.codex/skills/delete-user/SKILL.md +1 -1
  15. package/runtime/codex/.codex/skills/ingest/SKILL.md +1 -1
  16. package/runtime/codex/.codex/skills/ingest-corpus/SKILL.md +29 -0
  17. package/runtime/codex/.codex/skills/ingest-notion/SKILL.md +29 -0
  18. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +1 -1
  19. package/runtime/codex/.codex/skills/issue/SKILL.md +1 -1
  20. package/runtime/codex/.codex/skills/me/SKILL.md +1 -1
  21. package/runtime/codex/.codex/skills/meeting/SKILL.md +1 -1
  22. package/runtime/codex/.codex/skills/note/SKILL.md +1 -1
  23. package/runtime/codex/.codex/skills/notion-connect/SKILL.md +29 -0
  24. package/runtime/codex/.codex/skills/onboarding/SKILL.md +1 -1
  25. package/runtime/codex/.codex/skills/pr/SKILL.md +1 -1
  26. package/runtime/codex/.codex/skills/pull/SKILL.md +1 -1
  27. package/runtime/codex/.codex/skills/push/SKILL.md +1 -1
  28. package/runtime/codex/.codex/skills/reflect/SKILL.md +1 -1
  29. package/runtime/codex/.codex/skills/review-pr/SKILL.md +1 -1
  30. package/runtime/codex/.codex/skills/save/SKILL.md +3 -1
  31. package/runtime/codex/.codex/skills/slack-connect/SKILL.md +1 -1
  32. package/runtime/codex/.codex/skills/summon/SKILL.md +1 -1
  33. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +1 -1
  34. package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
  35. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +1 -1
  36. package/runtime/codex/.codex/skills/telemetry/SKILL.md +1 -1
  37. package/runtime/codex/.codex/skills/test/SKILL.md +1 -1
  38. package/runtime/codex/.codex/skills/tutorial/SKILL.md +1 -1
  39. package/runtime/codex/.codex/skills/update/SKILL.md +1 -1
  40. package/runtime/codex/.codex/spec-manifest.json +5 -5
  41. package/runtime/codex/AGENTS.md +41 -35
  42. package/runtime/codex/bin/activity-data.sh +4 -8
  43. package/runtime/codex/bin/agent.sh +17 -5
  44. package/runtime/codex/bin/attendant.sh +13 -0
  45. package/runtime/codex/bin/autosave.sh +27 -1
  46. package/runtime/codex/bin/codex-render-spec.mjs +33 -32
  47. package/runtime/codex/bin/codex-session-start.sh +3 -1
  48. package/runtime/codex/bin/codex-sync-skills.sh +6 -1
  49. package/runtime/codex/bin/graph-batch.sh +25 -6
  50. package/runtime/codex/bin/graph-op.sh +5 -4
  51. package/runtime/codex/bin/graph.sh +31 -5
  52. package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
  53. package/runtime/codex/bin/handoff-run.sh +8 -3
  54. package/runtime/codex/bin/handoff-save-egregore.sh +19 -7
  55. package/runtime/codex/bin/ingest.py +341 -10
  56. package/runtime/codex/bin/lib/context.sh +26 -1
  57. package/runtime/codex/bin/lib/git-message.sh +37 -0
  58. package/runtime/codex/bin/lib/git-sync.sh +10 -2
  59. package/runtime/codex/bin/lib/greeting.sh +4 -1
  60. package/runtime/codex/bin/lib/worktree-links.sh +25 -0
  61. package/runtime/codex/bin/session-autosave.sh +7 -1
  62. package/runtime/codex/bin/settings.sh +1 -1
  63. package/runtime/codex/manifest.json +10 -1
  64. package/runtime/pi/.codex/config.toml +3 -0
  65. package/runtime/pi/.codex/skills/archive/SKILL.md +1 -1
  66. package/runtime/pi/.codex/skills/branch/SKILL.md +1 -1
  67. package/runtime/pi/.codex/skills/checkup/SKILL.md +1 -1
  68. package/runtime/pi/.codex/skills/commit/SKILL.md +1 -1
  69. package/runtime/pi/.codex/skills/contribute/SKILL.md +1 -1
  70. package/runtime/pi/.codex/skills/delete-user/SKILL.md +1 -1
  71. package/runtime/pi/.codex/skills/ingest/SKILL.md +1 -1
  72. package/runtime/pi/.codex/skills/ingest-corpus/SKILL.md +29 -0
  73. package/runtime/pi/.codex/skills/ingest-notion/SKILL.md +29 -0
  74. package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +1 -1
  75. package/runtime/pi/.codex/skills/issue/SKILL.md +1 -1
  76. package/runtime/pi/.codex/skills/me/SKILL.md +1 -1
  77. package/runtime/pi/.codex/skills/meeting/SKILL.md +1 -1
  78. package/runtime/pi/.codex/skills/note/SKILL.md +1 -1
  79. package/runtime/pi/.codex/skills/notion-connect/SKILL.md +29 -0
  80. package/runtime/pi/.codex/skills/onboarding/SKILL.md +1 -1
  81. package/runtime/pi/.codex/skills/pr/SKILL.md +1 -1
  82. package/runtime/pi/.codex/skills/pull/SKILL.md +1 -1
  83. package/runtime/pi/.codex/skills/push/SKILL.md +1 -1
  84. package/runtime/pi/.codex/skills/reflect/SKILL.md +1 -1
  85. package/runtime/pi/.codex/skills/review-pr/SKILL.md +1 -1
  86. package/runtime/pi/.codex/skills/save/SKILL.md +3 -1
  87. package/runtime/pi/.codex/skills/slack-connect/SKILL.md +1 -1
  88. package/runtime/pi/.codex/skills/summon/SKILL.md +1 -1
  89. package/runtime/pi/.codex/skills/sync-repos/SKILL.md +1 -1
  90. package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
  91. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +1 -1
  92. package/runtime/pi/.codex/skills/telemetry/SKILL.md +1 -1
  93. package/runtime/pi/.codex/skills/test/SKILL.md +1 -1
  94. package/runtime/pi/.codex/skills/tutorial/SKILL.md +1 -1
  95. package/runtime/pi/.codex/skills/update/SKILL.md +1 -1
  96. package/runtime/pi/.codex/spec-manifest.json +5 -5
  97. package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -33
  98. package/runtime/pi/.pi/extensions/egregore.ts +17 -6
  99. package/runtime/pi/.pi/spec-manifest.json +2 -2
  100. package/runtime/pi/AGENTS.md +41 -35
  101. package/runtime/pi/bin/activity-data.sh +4 -8
  102. package/runtime/pi/bin/agent.sh +17 -5
  103. package/runtime/pi/bin/attendant.sh +13 -0
  104. package/runtime/pi/bin/autosave.sh +27 -1
  105. package/runtime/pi/bin/codex-render-spec.mjs +33 -32
  106. package/runtime/pi/bin/codex-session-start.sh +3 -1
  107. package/runtime/pi/bin/codex-sync-skills.sh +6 -1
  108. package/runtime/pi/bin/graph-batch.sh +25 -6
  109. package/runtime/pi/bin/graph-op.sh +5 -4
  110. package/runtime/pi/bin/graph.sh +31 -5
  111. package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
  112. package/runtime/pi/bin/handoff-run.sh +8 -3
  113. package/runtime/pi/bin/handoff-save-egregore.sh +19 -7
  114. package/runtime/pi/bin/ingest.py +341 -10
  115. package/runtime/pi/bin/lib/context.sh +26 -1
  116. package/runtime/pi/bin/lib/git-message.sh +37 -0
  117. package/runtime/pi/bin/lib/git-sync.sh +10 -2
  118. package/runtime/pi/bin/lib/greeting.sh +4 -1
  119. package/runtime/pi/bin/lib/worktree-links.sh +25 -0
  120. package/runtime/pi/bin/pi-branch-policy.mjs +138 -1
  121. package/runtime/pi/bin/pi-product-workflows.mjs +35 -7
  122. package/runtime/pi/bin/pi-render-spec.mjs +52 -26
  123. package/runtime/pi/bin/session-autosave.sh +7 -1
  124. package/runtime/pi/bin/settings.sh +1 -1
  125. package/runtime/pi/manifest.json +10 -1
  126. package/runtime/prime/.claude/context/notification-consent.md +66 -0
  127. package/runtime/prime/.codex/config.toml +20 -0
  128. package/runtime/prime/.codex/hooks/branch-guard.js +461 -0
  129. package/runtime/prime/.codex/hooks/search-hint.js +67 -0
  130. package/runtime/prime/.codex/hooks.json +30 -0
  131. package/runtime/prime/.codex/skills/activity/SKILL.md +82 -0
  132. package/runtime/prime/.codex/skills/announce/SKILL.md +67 -0
  133. package/runtime/prime/.codex/skills/archive/SKILL.md +45 -0
  134. package/runtime/prime/.codex/skills/branch/SKILL.md +29 -0
  135. package/runtime/prime/.codex/skills/checkup/SKILL.md +45 -0
  136. package/runtime/prime/.codex/skills/commit/SKILL.md +29 -0
  137. package/runtime/prime/.codex/skills/contribute/SKILL.md +29 -0
  138. package/runtime/prime/.codex/skills/create-skill/SKILL.md +29 -0
  139. package/runtime/prime/.codex/skills/dark-mode/SKILL.md +29 -0
  140. package/runtime/prime/.codex/skills/dashboard/SKILL.md +59 -0
  141. package/runtime/prime/.codex/skills/deep-reflect/SKILL.md +109 -0
  142. package/runtime/prime/.codex/skills/delete-user/SKILL.md +29 -0
  143. package/runtime/prime/.codex/skills/emissary/SKILL.md +45 -0
  144. package/runtime/prime/.codex/skills/handoff/SKILL.md +166 -0
  145. package/runtime/prime/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
  146. package/runtime/prime/.codex/skills/harvest/SKILL.md +131 -0
  147. package/runtime/prime/.codex/skills/ingest/SKILL.md +29 -0
  148. package/runtime/prime/.codex/skills/ingest-corpus/SKILL.md +29 -0
  149. package/runtime/prime/.codex/skills/ingest-notion/SKILL.md +29 -0
  150. package/runtime/prime/.codex/skills/ingest-user-interview/SKILL.md +45 -0
  151. package/runtime/prime/.codex/skills/invite/SKILL.md +44 -0
  152. package/runtime/prime/.codex/skills/issue/SKILL.md +45 -0
  153. package/runtime/prime/.codex/skills/me/SKILL.md +29 -0
  154. package/runtime/prime/.codex/skills/meeting/SKILL.md +45 -0
  155. package/runtime/prime/.codex/skills/note/SKILL.md +29 -0
  156. package/runtime/prime/.codex/skills/notion-connect/SKILL.md +29 -0
  157. package/runtime/prime/.codex/skills/onboarding/SKILL.md +45 -0
  158. package/runtime/prime/.codex/skills/pr/SKILL.md +29 -0
  159. package/runtime/prime/.codex/skills/pull/SKILL.md +29 -0
  160. package/runtime/prime/.codex/skills/push/SKILL.md +29 -0
  161. package/runtime/prime/.codex/skills/quest/SKILL.md +80 -0
  162. package/runtime/prime/.codex/skills/reflect/SKILL.md +45 -0
  163. package/runtime/prime/.codex/skills/review-pr/SKILL.md +45 -0
  164. package/runtime/prime/.codex/skills/save/SKILL.md +127 -0
  165. package/runtime/prime/.codex/skills/slack-connect/SKILL.md +29 -0
  166. package/runtime/prime/.codex/skills/summon/SKILL.md +45 -0
  167. package/runtime/prime/.codex/skills/sync-repos/SKILL.md +29 -0
  168. package/runtime/prime/.codex/skills/teams-connect/SKILL.md +29 -0
  169. package/runtime/prime/.codex/skills/telegram-connect/SKILL.md +29 -0
  170. package/runtime/prime/.codex/skills/telemetry/SKILL.md +29 -0
  171. package/runtime/prime/.codex/skills/test/SKILL.md +45 -0
  172. package/runtime/prime/.codex/skills/the-spiral/SKILL.md +84 -0
  173. package/runtime/prime/.codex/skills/tui-design/SKILL.md +45 -0
  174. package/runtime/prime/.codex/skills/tutorial/SKILL.md +45 -0
  175. package/runtime/prime/.codex/skills/update/SKILL.md +29 -0
  176. package/runtime/prime/.codex/skills/view/SKILL.md +156 -0
  177. package/runtime/prime/.codex/skills/view/agents/openai.yaml +4 -0
  178. package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +221 -0
  179. package/runtime/prime/.codex/skills/visual-explain/SKILL.md +45 -0
  180. package/runtime/prime/.codex/skills/wrap/SKILL.md +58 -0
  181. package/runtime/prime/.codex/spec-manifest.json +132 -0
  182. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +416 -0
  183. package/runtime/prime/.prime/agent/extensions/egregore.ts +460 -0
  184. package/runtime/prime/.prime/agent/settings.json +4 -0
  185. package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +34 -0
  186. package/runtime/prime/.prime/agent/skills/egregore/pyproject.toml +13 -0
  187. package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +156 -0
  188. package/runtime/prime/.prime/agent/spec-manifest.json +12 -0
  189. package/runtime/prime/AGENTS.md +488 -0
  190. package/runtime/prime/bin/activity-data.sh +238 -0
  191. package/runtime/prime/bin/agent.sh +787 -0
  192. package/runtime/prime/bin/attendant.sh +707 -0
  193. package/runtime/prime/bin/autosave.sh +112 -0
  194. package/runtime/prime/bin/capture-reconcile.sh +69 -0
  195. package/runtime/prime/bin/capture-run.sh +341 -0
  196. package/runtime/prime/bin/codex-install-skills.sh +78 -0
  197. package/runtime/prime/bin/codex-render-spec.mjs +435 -0
  198. package/runtime/prime/bin/codex-session-start.sh +265 -0
  199. package/runtime/prime/bin/codex-skill-render.mjs +347 -0
  200. package/runtime/prime/bin/codex-sync-skills.sh +237 -0
  201. package/runtime/prime/bin/connect-refresh.sh +33 -0
  202. package/runtime/prime/bin/dashboard-data.sh +178 -0
  203. package/runtime/prime/bin/graph-batch.sh +79 -0
  204. package/runtime/prime/bin/graph-op.sh +1102 -0
  205. package/runtime/prime/bin/graph-wal.sh +229 -0
  206. package/runtime/prime/bin/graph.sh +223 -0
  207. package/runtime/prime/bin/handoff-pr-backfill.sh +86 -0
  208. package/runtime/prime/bin/handoff-run.sh +676 -0
  209. package/runtime/prime/bin/handoff-save-egregore.sh +224 -0
  210. package/runtime/prime/bin/index-handoff.sh +322 -0
  211. package/runtime/prime/bin/ingest-graph.sh +80 -0
  212. package/runtime/prime/bin/ingest-surface.html +495 -0
  213. package/runtime/prime/bin/ingest.py +1011 -0
  214. package/runtime/prime/bin/ingest.sh +279 -0
  215. package/runtime/prime/bin/ingest_graph.py +856 -0
  216. package/runtime/prime/bin/ingest_surface.py +544 -0
  217. package/runtime/prime/bin/lib/artifact-id.sh +46 -0
  218. package/runtime/prime/bin/lib/config.sh +116 -0
  219. package/runtime/prime/bin/lib/context.sh +621 -0
  220. package/runtime/prime/bin/lib/dashboard-artifact.sh +242 -0
  221. package/runtime/prime/bin/lib/ensure-develop.sh +183 -0
  222. package/runtime/prime/bin/lib/git-message.sh +37 -0
  223. package/runtime/prime/bin/lib/git-sync.sh +414 -0
  224. package/runtime/prime/bin/lib/greeting.sh +519 -0
  225. package/runtime/prime/bin/lib/hash.sh +13 -0
  226. package/runtime/prime/bin/lib/identity.sh +213 -0
  227. package/runtime/prime/bin/lib/metrics.sh +374 -0
  228. package/runtime/prime/bin/lib/noncode.sh +31 -0
  229. package/runtime/prime/bin/lib/permissions.sh +106 -0
  230. package/runtime/prime/bin/lib/time.sh +56 -0
  231. package/runtime/prime/bin/lib/worktree-links.sh +79 -0
  232. package/runtime/prime/bin/notify.sh +636 -0
  233. package/runtime/prime/bin/person.py +630 -0
  234. package/runtime/prime/bin/person.sh +448 -0
  235. package/runtime/prime/bin/pi-branch-policy.mjs +312 -0
  236. package/runtime/prime/bin/pi-product-workflows.mjs +394 -0
  237. package/runtime/prime/bin/prime-render-spec.mjs +147 -0
  238. package/runtime/prime/bin/prime-session-start.sh +24 -0
  239. package/runtime/prime/bin/publish-artifact.sh +271 -0
  240. package/runtime/prime/bin/publish-references.sh +144 -0
  241. package/runtime/prime/bin/repo-state.sh +96 -0
  242. package/runtime/prime/bin/search.sh +240 -0
  243. package/runtime/prime/bin/session-autosave.sh +215 -0
  244. package/runtime/prime/bin/session-log.sh +199 -0
  245. package/runtime/prime/bin/settings.sh +304 -0
  246. package/runtime/prime/bin/telemetry.sh +391 -0
  247. package/runtime/prime/bin/worktree-create.sh +128 -0
  248. package/runtime/prime/bin/worktree-remove.sh +50 -0
  249. package/runtime/prime/bin/worktree.sh +198 -0
  250. package/runtime/prime/manifest.json +181 -0
@@ -1,7 +1,11 @@
1
1
  import { isAbsolute, relative, resolve } from "node:path";
2
2
 
3
3
  const MUTATING_COMMANDS = /(^|[;&|]\s*)(?:sudo\s+)?(rm|mv|cp|mkdir|rmdir|touch|chmod|chown|install|tee)\b/;
4
- const MUTATING_GIT = /(^|[;&|]\s*)(?:sudo\s+)?git\s+(add|commit|push|merge|rebase|cherry-pick|tag|rm|mv)\b/;
4
+ // Global options (-C <path>, -c k=v, --git-dir=…) may sit between `git` and
5
+ // the subcommand. The repo convention actively teaches `git -C <abs-path>`,
6
+ // so a classifier that only matches bare `git commit` misses the shape the
7
+ // model is most likely to emit.
8
+ const MUTATING_GIT = /(^|[;&|]\s*)(?:sudo\s+)?git\s+(?:(?:-C|-c|--git-dir|--work-tree)(?:=\S+|\s+\S+)\s+)*(add|commit|push|merge|rebase|cherry-pick|tag|rm|mv|reset|restore|clean)\b/;
5
9
  const MUTATING_PACKAGES = /(^|[;&|]\s*)(?:sudo\s+)?(npm|pnpm|yarn|bun)\s+(install|add|remove|uninstall|update)\b/;
6
10
 
7
11
  // A `>` only acts as a shell redirect when it sits OUTSIDE quotes. Quoted
@@ -112,6 +116,7 @@ export function isRuntimeStatePath(target, projectDir) {
112
116
  rel === ".claude" || rel.startsWith(".claude/") ||
113
117
  rel === ".codex" || rel.startsWith(".codex/") ||
114
118
  rel === ".pi" || rel.startsWith(".pi/") ||
119
+ rel === ".prime" || rel.startsWith(".prime/") ||
115
120
  rel === ".egregore-state.json" ||
116
121
  rel === ".egregore-branch-consent" ||
117
122
  rel === ".env";
@@ -162,6 +167,20 @@ export function mutatesOnlyRuntimeState(command, projectDir) {
162
167
  return foundTarget;
163
168
  }
164
169
 
170
+ // The documented escape hatch must itself be runnable, but ONLY as the whole
171
+ // command: an unanchored match lets `git push origin develop && echo 'develop'
172
+ // > .egregore-branch-consent` pass the gate in one call, and the block message
173
+ // teaches that exact echo string. Canonical form mirrors
174
+ // .codex/hooks/branch-guard.js isConsentCommand — anchored, branch escaped,
175
+ // optional ./ prefix — and is shared so the three runtimes cannot drift.
176
+ export function isConsentCommand(command, branch) {
177
+ if (!branch) return false;
178
+ const escaped = String(branch).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
179
+ return new RegExp(
180
+ `^\\s*echo\\s+['"]?${escaped}['"]?\\s*>\\s*(?:\\./)?\\.egregore-branch-consent\\s*$`
181
+ ).test(String(command || ""));
182
+ }
183
+
165
184
  export function isMutatingBash(command) {
166
185
  if (!command) return false;
167
186
  const meaningfulRedirects = stripQuotedSegments(String(command))
@@ -173,3 +192,121 @@ export function isMutatingBash(command) {
173
192
  /(^|[^<])>>?\s*[^&]/.test(meaningfulRedirects) ||
174
193
  /\bsed\s+[^\n]*\s-i(?:\s|$)/.test(command);
175
194
  }
195
+
196
+ // ---- IPython code classification (Prime Agent) -----------------------------
197
+ // Prime Agent's default tool surface is a persistent IPython kernel: file
198
+ // writes and shell escapes arrive as Python code, not bash tool calls. This
199
+ // classifier is deliberately static and therefore porous (exec/eval and
200
+ // string-built commands pass); the generated runtime spec carries the
201
+ // standing-instruction discipline as the backstop, and the adversarial test
202
+ // fixtures pin what this actually catches.
203
+
204
+ // Strip Python string literals so call-name detection ignores docstrings and
205
+ // string content. Triple quotes first, then single-line quotes; f-string
206
+ // prefixes fall out naturally because only the quoted part is replaced.
207
+ function stripPythonStrings(code) {
208
+ return String(code)
209
+ .replace(/[rbuf]{0,2}("""|''')[\s\S]*?\1/gi, '""')
210
+ .replace(/[rbuf]{0,2}"(?:\\.|[^"\\\n])*"/gi, '""')
211
+ .replace(/[rbuf]{0,2}'(?:\\.|[^'\\\n])*'/gi, "''");
212
+ }
213
+
214
+ function pythonShellCommands(code) {
215
+ const shell = [];
216
+ const lines = String(code).split("\n");
217
+ const first = (lines[0] || "").trim();
218
+ if (/^%%(bash|sh|script\s+(ba)?sh)\b/.test(first)) {
219
+ shell.push(lines.slice(1).join("\n"));
220
+ return { shell, rest: "" };
221
+ }
222
+ if (/^%%writefile\b/.test(first)) {
223
+ const target = first.replace(/^%%writefile\s+(-a\s+)?/, "").trim();
224
+ return { shell: [`tee ${target || "unknown-target"}`], rest: "" };
225
+ }
226
+ const rest = [];
227
+ for (const line of lines) {
228
+ const bang = line.match(/^\s*!(.+)$/);
229
+ const sysMagic = line.match(/^\s*%(?:system|sx)\s+(.+)$/);
230
+ if (bang) shell.push(bang[1]);
231
+ else if (sysMagic) shell.push(sysMagic[1]);
232
+ else rest.push(line);
233
+ }
234
+ return { shell, rest: rest.join("\n") };
235
+ }
236
+
237
+ const PY_MUTATING_CALLS =
238
+ /\b(?:os\.(?:remove|unlink|rename|replace|rmdir|removedirs|makedirs|mkdir|symlink|link|chmod|chown|truncate)|shutil\.(?:copy|copy2|copyfile|copytree|move|rmtree|make_archive|unpack_archive))\s*\(/;
239
+ const PY_WRITE_METHODS = /\.\s*(?:write_text|write_bytes|unlink|touch|mkdir|rmdir|hardlink_to|symlink_to)\s*\(/;
240
+ const PY_OPEN_WRITE =
241
+ /\bopen\s*\(\s*[^,]+,\s*(?:mode\s*=\s*)?['"](?:[wax]|r\+)[rwxab+]*['"]/;
242
+ const PY_OPEN_MODE_KWARG = /\bopen\s*\([^)]*mode\s*=\s*['"](?:[wax]|r\+)[rwxab+]*['"]/;
243
+
244
+ function pythonSubprocessArgv(code) {
245
+ const argvs = [];
246
+ const calls =
247
+ /\b(?:subprocess\.(?:run|call|check_call|check_output|Popen)|os\.system|os\.popen)\s*\(\s*(\[[^\]]*\]|[rbuf]{0,2}["'](?:\\.|[^"'\\\n])*["'])/g;
248
+ let match;
249
+ while ((match = calls.exec(String(code)))) {
250
+ const arg = match[1];
251
+ if (arg.startsWith("[")) {
252
+ const words = [...arg.matchAll(/["']((?:\\.|[^"'\\])*)["']/g)].map((m) => m[1]);
253
+ if (words.length) argvs.push(words.join(" "));
254
+ } else {
255
+ argvs.push(arg.replace(/^[rbuf]{0,2}["']/i, "").replace(/["']$/, ""));
256
+ }
257
+ }
258
+ return argvs;
259
+ }
260
+
261
+ // Classify IPython cell code. Returns true when the code statically performs a
262
+ // repo mutation that should meet the protected-branch gate. Mutations that
263
+ // target only Egregore runtime state (memory/, dot-dirs, consent files) are
264
+ // exempt when the target is statically extractable, mirroring
265
+ // mutatesOnlyRuntimeState for bash.
266
+ export function isMutatingIpython(code, projectDir) {
267
+ if (!code) return false;
268
+ const { shell, rest } = pythonShellCommands(code);
269
+ for (const cmd of shell) {
270
+ if (isMutatingBash(cmd) && !(projectDir && mutatesOnlyRuntimeState(cmd, projectDir))) return true;
271
+ }
272
+ if (!rest) return false;
273
+
274
+ for (const argv of pythonSubprocessArgv(rest)) {
275
+ if (isMutatingBash(argv) && !(projectDir && mutatesOnlyRuntimeState(argv, projectDir))) return true;
276
+ }
277
+
278
+ const stripped = stripPythonStrings(rest);
279
+ if (PY_MUTATING_CALLS.test(stripped) || PY_WRITE_METHODS.test(stripped)) {
280
+ // Exempt when every extractable string target is runtime state. The
281
+ // mutated location differs by call: copy/move/rename mutate their LAST
282
+ // argument (the destination); remove/rmtree/mkdir mutate their first.
283
+ if (projectDir) {
284
+ const DEST_LAST = /^(?:copy|copy2|copyfile|copytree|move|rename|replace|symlink|link)$/;
285
+ const targets = [...String(rest).matchAll(
286
+ /\b(?:os|shutil)\.(\w+)\s*\(([^)]*)\)/g
287
+ )].flatMap((m) => {
288
+ const args = [...m[2].matchAll(/["']((?:\\.|[^"'\\])*)["']/g)].map((s) => s[1]);
289
+ if (!args.length) return [];
290
+ return [DEST_LAST.test(m[1]) ? args[args.length - 1] : args[0]];
291
+ });
292
+ const pathTargets = [...String(rest).matchAll(
293
+ /Path\s*\(\s*[rbuf]{0,2}["']((?:\\.|[^"'\\\n])*)["']\s*\)\s*\.\s*(?:write_text|write_bytes|unlink|touch|mkdir|rmdir)/g
294
+ )].map((m) => m[1]);
295
+ const all = [...targets, ...pathTargets];
296
+ if (all.length && all.every((t) => isRuntimeStatePath(t, projectDir))) return false;
297
+ }
298
+ return true;
299
+ }
300
+
301
+ if (PY_OPEN_WRITE.test(rest) || PY_OPEN_MODE_KWARG.test(rest)) {
302
+ if (projectDir) {
303
+ const openTargets = [...String(rest).matchAll(
304
+ /\bopen\s*\(\s*[rbuf]{0,2}["']((?:\\.|[^"'\\\n])*)["']\s*,\s*(?:mode\s*=\s*)?["'](?:[wax]|r\+)/g
305
+ )].map((m) => m[1]);
306
+ if (openTargets.length && openTargets.every((t) => isRuntimeStatePath(t, projectDir))) return false;
307
+ }
308
+ return true;
309
+ }
310
+
311
+ return false;
312
+ }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {
4
4
  existsSync,
5
+ mkdirSync,
5
6
  mkdtempSync,
6
7
  readFileSync,
7
8
  readdirSync,
@@ -141,12 +142,37 @@ export async function renderView(pi, root, request, signal) {
141
142
  return { text, file, type: request.type };
142
143
  }
143
144
 
144
- export async function renderGreeting(pi, root, signal) {
145
- const script = join(root, "bin", "pi-session-start.sh");
146
- if (!existsSync(script)) throw new Error("Pi startup renderer is missing");
145
+ export async function renderGreeting(pi, root, signal, startScript = "pi-session-start.sh") {
146
+ const script = join(root, "bin", startScript);
147
+ if (!existsSync(script)) throw new Error("Egregore startup renderer is missing");
147
148
  return checkedExec(pi, "bash", [script, "--card"], { signal, timeout: 45000 }, "Egregore startup");
148
149
  }
149
150
 
151
+ // Raw JSON from the most recent activity-data.sh run, so follow-up focus
152
+ // turns can receive the data inline instead of re-running the script inside
153
+ // a harness surface that may cap command time (Prime's kernel kills execs at
154
+ // ~5s, truncating the 10-20s data script to a partial fragment).
155
+ let lastActivityData = "";
156
+ export function lastActivityJson() {
157
+ return lastActivityData;
158
+ }
159
+
160
+ // Stable on-disk copy of the same data. Focus turns point the model at this
161
+ // path instead of an inline excerpt: a file read is one fast kernel call,
162
+ // never truncated, and removes any reason to re-run the data script.
163
+ export function activityDataPath(root) {
164
+ return join(root, ".egregore", "activity-data.json");
165
+ }
166
+
167
+ function persistActivityData(root, json) {
168
+ try {
169
+ mkdirSync(join(root, ".egregore"), { recursive: true });
170
+ writeFileSync(activityDataPath(root), json);
171
+ } catch {
172
+ // Cache only — rendering proceeds from memory when the disk write fails.
173
+ }
174
+ }
175
+
150
176
  export async function renderActivity(pi, root, signal, options = {}) {
151
177
  if (options.sync !== false) {
152
178
  await checkedExec(
@@ -177,6 +203,8 @@ export async function renderActivity(pi, root, signal, options = {}) {
177
203
  { signal, timeout: 120000 },
178
204
  "Activity refresh",
179
205
  );
206
+ lastActivityData = String(json || "");
207
+ persistActivityData(root, lastActivityData);
180
208
  const temp = mkdtempSync(join(tmpdir(), "egregore-pi-activity-"));
181
209
  const input = join(temp, "activity.json");
182
210
  try {
@@ -276,7 +304,7 @@ export async function createHandoff(pi, root, params, signal) {
276
304
  }
277
305
  }
278
306
 
279
- export async function captureSessionEnd(pi, root, sessionFile, signal) {
307
+ export async function captureSessionEnd(pi, root, sessionFile, signal, runtime = "pi") {
280
308
  const sessionIdFile = join(root, ".egregore-session-id");
281
309
  if (!existsSync(sessionIdFile)) return false;
282
310
  const sessionId = cleanArgs(readFileSync(sessionIdFile, "utf8"));
@@ -287,7 +315,7 @@ export async function captureSessionEnd(pi, root, sessionFile, signal) {
287
315
  writeFileSync(input, JSON.stringify({
288
316
  session_id: sessionId,
289
317
  transcript_path: cleanArgs(sessionFile),
290
- runtime: "pi",
318
+ runtime,
291
319
  }));
292
320
 
293
321
  try {
@@ -353,11 +381,11 @@ export async function publishScroll(pi, root, params, signal) {
353
381
  };
354
382
  }
355
383
 
356
- export function workflowPrompt(name, args, extra = "") {
384
+ export function workflowPrompt(name, args, extra = "", spec = { runtimeName: "Pi", specPath: ".pi/APPEND_SYSTEM.md" }) {
357
385
  const userArgs = cleanArgs(args);
358
386
  return [
359
387
  `Run the Egregore /${name} workflow now.`,
360
- `Read .codex/skills/${name}/SKILL.md completely and adapt Codex-specific wording to Pi using .pi/APPEND_SYSTEM.md.`,
388
+ `Read .codex/skills/${name}/SKILL.md completely and adapt Codex-specific wording to ${spec.runtimeName} using ${spec.specPath}.`,
361
389
  "Work quietly: do not narrate progress, expose raw command output, or repeat tool results.",
362
390
  "Use the fewest targeted reads and commands that preserve the workflow contract.",
363
391
  userArgs ? `User arguments: ${userArgs}` : "",
@@ -40,24 +40,57 @@ function replaceSection(markdown, heading, body) {
40
40
  return `${markdown.slice(0, start)}${marker}\n\n${body}${markdown.slice(end)}`;
41
41
  }
42
42
 
43
+ // Keep an inherited section and put runtime-specific framing in front of it.
44
+ // Command Awareness is the case that matters: the canonical category rows and
45
+ // disambiguation map live in CLAUDE.md, and a full replace here silently
46
+ // shipped a truncated copy that drifted for months. The Codex spec opens the
47
+ // section with its own skills-not-slash-commands paragraph, which is wrong for
48
+ // a runtime that does have slash commands, so that one paragraph is dropped.
49
+ function prependToSection(markdown, heading, body, dropLeadParagraphStartingWith) {
50
+ const marker = `## ${heading}`;
51
+ const start = markdown.indexOf(marker);
52
+ if (start < 0) throw new Error(`missing section in AGENTS.md: ${heading}`);
53
+ const rest = markdown.slice(start + marker.length);
54
+ const boundary = rest.search(/\n\n---\n\n## /);
55
+ const end = boundary < 0 ? markdown.length : start + marker.length + boundary;
56
+ let inherited = markdown.slice(start + marker.length, end).trim();
57
+ if (dropLeadParagraphStartingWith) {
58
+ if (!inherited.startsWith(dropLeadParagraphStartingWith)) {
59
+ throw new Error(`pi/prime-render-spec: "${heading}" no longer opens with "${dropLeadParagraphStartingWith}" — the Codex wording moved; update this adaptation`);
60
+ }
61
+ inherited = inherited.slice(inherited.indexOf("\n\n") + 2).trim();
62
+ }
63
+ return replaceSection(markdown, heading, `${body}\n\n${inherited}`);
64
+ }
65
+
66
+ const CODEX_COMMAND_LEAD = "Codex reserves leading";
67
+
43
68
  function adapt(sourceSpec) {
44
- let text = sourceSpec
45
- .replaceAll("Egregore on Codex", "Egregore on Pi")
46
- .replaceAll("Codex-native", "Pi-native")
47
- .replaceAll("Codex sessions", "Pi sessions")
48
- .replaceAll("On Codex", "On Pi")
49
- .replaceAll("from Codex", "from Pi")
50
- .replaceAll("inside Codex", "inside Pi")
51
- .replaceAll("Codex has no", "Pi has no built-in")
52
- .replaceAll("normal sandboxed Codex shell", "the Pi shell")
53
- .replaceAll("Codex graph network access", "Pi graph network access")
54
- .replaceAll("structured Codex question tooling", "Pi UI question tooling")
55
- .replaceAll("structured Codex", "structured Pi")
56
- .replaceAll("bin/codex-session-start.sh", "bin/pi-session-start.sh")
57
- .replaceAll(".codex/hooks/branch-guard.js", ".pi/extensions/egregore.ts")
58
- .replaceAll("PreToolUse hook (enabled by the launcher via `--enable hooks`)", "`tool_call` gate (loaded after Pi project trust)")
59
- .replaceAll("If this Codex build does not support hooks", "If Pi project resources are disabled")
60
- .replaceAll("change permissions in `.claude/settings.json` anytime", "review or disable project resources in `.pi/` anytime")
69
+ // Anchored replacements: every `from` string MUST exist in the generated
70
+ // Codex spec. A missing anchor once no-opped silently (the tool_call gate
71
+ // wording vanished from this file for a whole PR cycle before a test caught
72
+ // it) — now it fails the render loudly so the anchor gets updated alongside
73
+ // the Codex text that moved.
74
+ const ANCHORED = [
75
+ ["Egregore on Codex", "Egregore on Pi"],
76
+ ["Codex-native", "Pi-native"],
77
+ ["Codex sessions", "Pi sessions"],
78
+ ["On Codex", "On Pi"],
79
+ ["Codex has no", "Pi has no built-in"],
80
+ ["bin/codex-session-start.sh", "bin/pi-session-start.sh"],
81
+ [".codex/hooks/branch-guard.js", ".pi/extensions/egregore.ts"],
82
+ ["PreToolUse hook (launcher `--enable hooks`)", "`tool_call` gate (loaded after Pi project trust)"],
83
+ ["If this Codex build does not support hooks", "If Pi project resources are disabled"],
84
+ ["change permissions in `.claude/settings.json` anytime", "review or disable project resources in `.pi/` anytime"],
85
+ ];
86
+ let text = sourceSpec;
87
+ for (const [from, to] of ANCHORED) {
88
+ if (!text.includes(from)) {
89
+ throw new Error(`pi-render-spec: anchor not found in generated Codex spec: "${from}" — the Codex wording moved; update this replacement`);
90
+ }
91
+ text = text.replaceAll(from, to);
92
+ }
93
+ text = text
61
94
  .replace(/`\$([a-z][a-z0-9-]*)/g, "`/$1")
62
95
  .replace(/\$([a-z][a-z0-9-]*)/g, "/$1");
63
96
 
@@ -67,16 +100,9 @@ This appendix is generated from the reviewed shell-runtime translation of \`CLAU
67
100
 
68
101
  text = replaceSection(text, "On Launch — MANDATORY FIRST ACTION", `The project-local Pi extension renders the Egregore startup card (identity, handoffs, team activity) inside Pi via \`bin/pi-session-start.sh\`. This works for direct \`pi\` launches and launcher-managed sessions. Do not rerun startup checks and do not narrate startup. The card ends with **"What are you working on?"** — that question is already on screen; treat the user's first message as the answer to it. To re-show the card outside Pi, run \`bash bin/pi-session-start.sh --card\`.`);
69
102
 
70
- text = replaceSection(text, "Command Awareness", `Egregore workflows are available as Pi slash commands such as \`/activity\`, \`/handoff\`, \`/save\`, and \`/view\`. They are registered from the shared skill inventory by \`.pi/extensions/egregore.ts\`. Pi also exposes the underlying Agent Skills as \`/skill:name\`, and natural-language requests work normally. \`/save\` is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory — never make users manage the git workflow by hand.
71
-
72
- Invoke commands from user intent — don't wait for the slash. Read the selected skill's \`SKILL.md\` completely before acting. Codex wording inside the shared portable skills means the shell-capable runtime adapter; in Pi, run referenced \`bin/\` scripts directly, use ordinary Pi shell/network access, ignore Codex approval-channel wording, and render compact numbered questions when no Pi UI question surface is available.
103
+ text = prependToSection(text, "Command Awareness", `Egregore workflows are available as Pi slash commands such as \`/activity\`, \`/handoff\`, \`/save\`, and \`/view\`. They are registered from the shared skill inventory by \`.pi/extensions/egregore.ts\`. Pi also exposes the underlying Agent Skills as \`/skill:name\`, and natural-language requests work normally. \`/save\` is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory — never make users manage the git workflow by hand.
73
104
 
74
- **Core loop** — \`/activity\` \`/dashboard\` \`/handoff\` \`/wrap\` \`/save\` \`/reflect\` \`/todo\`
75
- **Knowledge** — \`/search\` \`/deep-reflect\` \`/archive\` \`/note\` \`/add\` \`/meeting\` \`/ingest\` \`/scroll\`
76
- **Identity** — \`/me\`
77
- **Coordination** — \`/ask\` \`/quest\` \`/issue\` \`/invite\` \`/delete-user\` \`/announce\`
78
- **Git** — \`/branch\` \`/commit\` \`/push\` \`/pr\` \`/save\` \`/review-pr\` \`/contribute\`
79
- **Infra** — \`/setup\` \`/update\` \`/pull\` \`/env\` \`/infra\` \`/sync-repos\` \`/release\` \`/checkup\``);
105
+ Read the selected skill's \`SKILL.md\` completely before acting. Codex wording inside the shared portable skills means the shell-capable runtime adapter; in Pi, run referenced \`bin/\` scripts directly, use ordinary Pi shell/network access, ignore Codex approval-channel wording, and render compact numbered questions when no Pi UI question surface is available.`, CODEX_COMMAND_LEAD);
80
106
 
81
107
  return `# Egregore on Pi\n\nThis file is generated by \`bin/pi-render-spec.mjs\`; do not edit it by hand.\n\n${text}\n`;
82
108
  }
@@ -29,6 +29,12 @@
29
29
 
30
30
  set -uo pipefail
31
31
 
32
+ # shellcheck source=bin/lib/git-message.sh
33
+ . "$(cd "$(dirname "$0")" && pwd)/lib/git-message.sh" 2>/dev/null || true
34
+ type egregore_commit >/dev/null 2>&1 || egregore_commit() {
35
+ local gd="$1" m="$3"; shift 3; git -C "$gd" commit -m "$m" "$@"
36
+ }
37
+
32
38
  DIR=""
33
39
  AUTHOR=""
34
40
  DEBOUNCE=0
@@ -118,7 +124,7 @@ fi
118
124
  if git -C "$DIR/memory" rev-parse --git-dir >/dev/null 2>&1; then
119
125
  if [ -n "$(git -C "$DIR/memory" status --porcelain 2>/dev/null)" ]; then
120
126
  git -C "$DIR/memory" add -A >/dev/null 2>&1 || true
121
- git -C "$DIR/memory" commit -m "Auto-save: session content" --quiet 2>/dev/null || true
127
+ egregore_commit "$DIR/memory" "$DIR" "chore(autosave): save session work" --quiet 2>/dev/null || true
122
128
  fi
123
129
  if [ -n "$(git -C "$DIR/memory" log origin/main..HEAD --oneline 2>/dev/null)" ]; then
124
130
  for _try in 1 2 3; do
@@ -232,7 +232,7 @@ _people_file() {
232
232
  rm -f "$f"
233
233
  fi
234
234
  ( cd "$SCRIPT_DIR/memory" 2>/dev/null && git add -A people/ 2>/dev/null \
235
- && git commit -q -m "settings: people $op $user" 2>/dev/null \
235
+ && git commit -q -m "chore(settings): $op $user in people" 2>/dev/null \
236
236
  && git push -q 2>/dev/null ) || true
237
237
  }
238
238
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "source": "egregore-pi-meta-harness",
3
3
  "policy": "Pi-native lifecycle layer over the available OSS Codex skill corpus",
4
- "skillCount": 43,
4
+ "skillCount": 47,
5
5
  "skills": [
6
6
  "activity",
7
7
  "announce",
@@ -19,12 +19,15 @@
19
19
  "handoff",
20
20
  "harvest",
21
21
  "ingest",
22
+ "ingest-corpus",
23
+ "ingest-notion",
22
24
  "ingest-user-interview",
23
25
  "invite",
24
26
  "issue",
25
27
  "me",
26
28
  "meeting",
27
29
  "note",
30
+ "notion-connect",
28
31
  "onboarding",
29
32
  "pr",
30
33
  "pull",
@@ -36,6 +39,7 @@
36
39
  "slack-connect",
37
40
  "summon",
38
41
  "sync-repos",
42
+ "teams-connect",
39
43
  "telegram-connect",
40
44
  "telemetry",
41
45
  "test",
@@ -70,6 +74,8 @@
70
74
  ".codex/skills/handoff/SKILL.md",
71
75
  ".codex/skills/harvest/QUESTION_PALETTE.md",
72
76
  ".codex/skills/harvest/SKILL.md",
77
+ ".codex/skills/ingest-corpus/SKILL.md",
78
+ ".codex/skills/ingest-notion/SKILL.md",
73
79
  ".codex/skills/ingest-user-interview/SKILL.md",
74
80
  ".codex/skills/ingest/SKILL.md",
75
81
  ".codex/skills/invite/SKILL.md",
@@ -77,6 +83,7 @@
77
83
  ".codex/skills/me/SKILL.md",
78
84
  ".codex/skills/meeting/SKILL.md",
79
85
  ".codex/skills/note/SKILL.md",
86
+ ".codex/skills/notion-connect/SKILL.md",
80
87
  ".codex/skills/onboarding/SKILL.md",
81
88
  ".codex/skills/pr/SKILL.md",
82
89
  ".codex/skills/pull/SKILL.md",
@@ -88,6 +95,7 @@
88
95
  ".codex/skills/slack-connect/SKILL.md",
89
96
  ".codex/skills/summon/SKILL.md",
90
97
  ".codex/skills/sync-repos/SKILL.md",
98
+ ".codex/skills/teams-connect/SKILL.md",
91
99
  ".codex/skills/telegram-connect/SKILL.md",
92
100
  ".codex/skills/telemetry/SKILL.md",
93
101
  ".codex/skills/test/SKILL.md",
@@ -138,6 +146,7 @@
138
146
  "bin/lib/context.sh",
139
147
  "bin/lib/dashboard-artifact.sh",
140
148
  "bin/lib/ensure-develop.sh",
149
+ "bin/lib/git-message.sh",
141
150
  "bin/lib/git-sync.sh",
142
151
  "bin/lib/greeting.sh",
143
152
  "bin/lib/hash.sh",
@@ -0,0 +1,66 @@
1
+ # External notification consent
2
+
3
+ This protocol applies to every Telegram, Teams, or other external notification
4
+ in every harness. It applies even when the user asked to announce, notify,
5
+ handoff, invite, or message someone, and even when the harness is running with
6
+ broad tool permissions.
7
+
8
+ ## Invariant
9
+
10
+ Never dispatch an external notification until the human has approved that one
11
+ exact delivery in a dedicated checkpoint. The checkpoint must show:
12
+
13
+ - the organization;
14
+ - the final recipient or group;
15
+ - every channel that will receive it;
16
+ - the exact final message, including links.
17
+
18
+ A workflow request, plan approval, batch approval, prior approval, broad
19
+ permission mode, or approval of a related action is not notification consent.
20
+ There is no standing "always allow" choice. One approval authorizes one attempt
21
+ to send one immutable message.
22
+
23
+ ## Required flow
24
+
25
+ 1. Finish the message first. Publish any artifact and insert its real URL
26
+ before planning; never change the message after preview.
27
+ 2. Resolve the exact delivery without sending:
28
+
29
+ ```bash
30
+ PLAN_JSON=$(bash bin/notify.sh plan send "$RECIPIENT" "$MESSAGE")
31
+ # or
32
+ PLAN_JSON=$(bash bin/notify.sh plan group "$MESSAGE")
33
+ ```
34
+
35
+ 3. Read `plan_id` and `digest` from `PLAN_JSON`. Show the organization,
36
+ recipient, all `channels`/`deliveries`, and exact `message` in a dedicated
37
+ Send / Edit / Cancel question. Use the harness's structured question UI
38
+ when available. Otherwise show the same fields in plain text and stop for
39
+ the user's answer.
40
+ 4. Only if the user selects Send for that exact preview:
41
+
42
+ ```bash
43
+ APPROVAL_JSON=$(bash bin/notify.sh approve \
44
+ "$PLAN_ID" "$DIGEST" APPROVE_EXACT_NOTIFICATION)
45
+ APPROVAL_TOKEN=$(printf '%s' "$APPROVAL_JSON" | jq -r '.approval_token')
46
+ bash bin/notify.sh dispatch "$PLAN_ID" "$APPROVAL_TOKEN"
47
+ ```
48
+
49
+ 5. If the user chooses Edit, cancel the old plan, edit, create a new plan, and
50
+ preview again. If they choose Cancel, cancel it and do not send. If the plan
51
+ expires or dispatch fails, create a new plan and obtain new consent; never
52
+ retry from the old approval.
53
+
54
+ ## Hard rules
55
+
56
+ - Never call notification API endpoints directly. Use `bin/notify.sh`.
57
+ - Never use legacy `notify.sh send` or `notify.sh group`; they only create a
58
+ proposal and intentionally cannot dispatch.
59
+ - Never substitute a group when a direct message cannot be delivered.
60
+ - Never add a channel after preview.
61
+ - Never approve or dispatch from a detached/background process, hook, cron
62
+ job, batch execution, or unattended agent. Those paths may create a plan and
63
+ report that human approval is pending.
64
+ - Multiple direct recipients require a separate preview and approval for each
65
+ exact message. A group fanout may use one approval only when every receiving
66
+ channel is listed in the preview.
@@ -0,0 +1,20 @@
1
+ [features]
2
+ hooks = true
3
+
4
+ [mcp_servers.notion]
5
+ url = "https://mcp.notion.com/mcp"
6
+
7
+ [tui]
8
+ # Codex currently accepts built-in footer item IDs only. Egregore's dynamic
9
+ # ◆ CONNECTED / ◇ LOCAL identity is rendered by bin/codex-session-start.sh.
10
+ status_line = [
11
+ "model-with-reasoning",
12
+ "git-branch",
13
+ "current-dir",
14
+ "context-remaining",
15
+ "five-hour-limit",
16
+ "weekly-limit",
17
+ "permissions",
18
+ "approval-mode",
19
+ ]
20
+ status_line_use_colors = true