create-egregore 0.19.8 → 0.19.12

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 (92) hide show
  1. package/assets/egregore-launcher.js +266 -4
  2. package/lib/local.js +11 -1
  3. package/lib/setup.js +33 -0
  4. package/package.json +2 -2
  5. package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
  6. package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
  7. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
  8. package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
  9. package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
  10. package/runtime/codex/.codex/skills/save/SKILL.md +29 -5
  11. package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
  12. package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
  13. package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
  14. package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
  15. package/runtime/codex/.codex/spec-manifest.json +19 -7
  16. package/runtime/codex/AGENTS.md +63 -19
  17. package/runtime/codex/bin/activity-data.sh +1 -1
  18. package/runtime/codex/bin/agent.sh +88 -25
  19. package/runtime/codex/bin/attendant.sh +525 -0
  20. package/runtime/codex/bin/capture-reconcile.sh +67 -0
  21. package/runtime/codex/bin/capture-run.sh +341 -0
  22. package/runtime/codex/bin/codex-render-spec.mjs +29 -14
  23. package/runtime/codex/bin/codex-session-start.sh +49 -4
  24. package/runtime/codex/bin/codex-skill-render.mjs +23 -4
  25. package/runtime/codex/bin/graph-op.sh +719 -49
  26. package/runtime/codex/bin/graph-wal.sh +3 -2
  27. package/runtime/codex/bin/handoff-run.sh +72 -0
  28. package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
  29. package/runtime/codex/bin/index-handoff.sh +26 -17
  30. package/runtime/codex/bin/ingest-graph.sh +80 -0
  31. package/runtime/codex/bin/ingest-surface.html +429 -0
  32. package/runtime/codex/bin/ingest.py +614 -0
  33. package/runtime/codex/bin/ingest.sh +231 -0
  34. package/runtime/codex/bin/ingest_graph.py +776 -0
  35. package/runtime/codex/bin/ingest_surface.py +464 -0
  36. package/runtime/codex/bin/lib/git-sync.sh +2 -6
  37. package/runtime/codex/bin/lib/greeting.sh +157 -22
  38. package/runtime/codex/bin/lib/identity.sh +50 -29
  39. package/runtime/codex/bin/lib/noncode.sh +31 -0
  40. package/runtime/codex/bin/person.py +610 -0
  41. package/runtime/codex/bin/person.sh +448 -0
  42. package/runtime/codex/bin/session-autosave.sh +184 -0
  43. package/runtime/codex/bin/session-log.sh +199 -0
  44. package/runtime/codex/bin/settings.sh +1 -1
  45. package/runtime/codex/manifest.json +19 -1
  46. package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
  47. package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
  48. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
  49. package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
  50. package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
  51. package/runtime/pi/.codex/skills/save/SKILL.md +29 -5
  52. package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
  53. package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
  54. package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
  55. package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
  56. package/runtime/pi/.codex/spec-manifest.json +19 -7
  57. package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -14
  58. package/runtime/pi/.pi/extensions/egregore.ts +98 -8
  59. package/runtime/pi/.pi/spec-manifest.json +2 -2
  60. package/runtime/pi/AGENTS.md +63 -19
  61. package/runtime/pi/bin/activity-data.sh +1 -1
  62. package/runtime/pi/bin/agent.sh +88 -25
  63. package/runtime/pi/bin/attendant.sh +525 -0
  64. package/runtime/pi/bin/capture-reconcile.sh +67 -0
  65. package/runtime/pi/bin/capture-run.sh +341 -0
  66. package/runtime/pi/bin/codex-render-spec.mjs +29 -14
  67. package/runtime/pi/bin/codex-session-start.sh +49 -4
  68. package/runtime/pi/bin/codex-skill-render.mjs +23 -4
  69. package/runtime/pi/bin/graph-op.sh +719 -49
  70. package/runtime/pi/bin/graph-wal.sh +3 -2
  71. package/runtime/pi/bin/handoff-run.sh +72 -0
  72. package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
  73. package/runtime/pi/bin/index-handoff.sh +26 -17
  74. package/runtime/pi/bin/ingest-graph.sh +80 -0
  75. package/runtime/pi/bin/ingest-surface.html +429 -0
  76. package/runtime/pi/bin/ingest.py +614 -0
  77. package/runtime/pi/bin/ingest.sh +231 -0
  78. package/runtime/pi/bin/ingest_graph.py +776 -0
  79. package/runtime/pi/bin/ingest_surface.py +464 -0
  80. package/runtime/pi/bin/lib/git-sync.sh +2 -6
  81. package/runtime/pi/bin/lib/greeting.sh +157 -22
  82. package/runtime/pi/bin/lib/identity.sh +50 -29
  83. package/runtime/pi/bin/lib/noncode.sh +31 -0
  84. package/runtime/pi/bin/person.py +610 -0
  85. package/runtime/pi/bin/person.sh +448 -0
  86. package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
  87. package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
  88. package/runtime/pi/bin/publish-artifact.sh +14 -0
  89. package/runtime/pi/bin/session-autosave.sh +184 -0
  90. package/runtime/pi/bin/session-log.sh +199 -0
  91. package/runtime/pi/bin/settings.sh +274 -0
  92. package/runtime/pi/manifest.json +20 -1
@@ -23,6 +23,24 @@ When a user reports broken or missing framework behavior, the first question is
23
23
 
24
24
  ---
25
25
 
26
+ ## Cross-Runtime Compatibility
27
+
28
+ Framework behavior must work across Claude Code, Codex, and Pi Egregores. Put
29
+ shared mechanics in runtime-neutral `bin/` scripts; keep `.claude/`, `.codex/`,
30
+ and `.pi/` as thin adapters to those mechanics. When changing startup, session
31
+ lifecycle, capture, skills, or commands, trace all three runtime entry points
32
+ and add or update a parity test. Do not call a framework change complete based
33
+ on one runtime alone. Regenerate derived Codex and Pi specifications or runtime
34
+ bundles whenever their sources change.
35
+
36
+ ---
37
+
38
+ ## Voice
39
+
40
+ Egregore's voice is governed by `.claude/rules/voice-bedrock.md` (always loaded). Register-specific skills: `egregore-voice` (external), `product-voice` (internal UX), `character-v4` (encounters), `alpha-openers` (outreach). See voice-bedrock for the full register map.
41
+
42
+ ---
43
+
26
44
  ## On Launch — MANDATORY FIRST ACTION
27
45
 
28
46
  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`.
@@ -73,19 +91,22 @@ If on develop after two messages, create a branch immediately from whatever cont
73
91
 
74
92
  ### Branch-guard protocol
75
93
 
76
- The `.pi/extensions/egregore.ts` `tool_call` gate (loaded after Pi project trust) denies writes, patches, and commits on `develop`/`main`/`master`. When it fires, choose between two paths based on how clear the topic is:
94
+ The `.pi/extensions/egregore.ts` `tool_call` gate (loaded after Pi project trust) protects project writes on `develop`/`main`/`master`. Its block message is operational guidance, not a reason to interrupt the user with routine Git choices:
77
95
 
78
- - **Topic is obvious from the last turn** (user said "fix X", "add Y feature") → auto-branch silently with `bin/agent.sh branch --topic "<topic>"` (or the git checkout fallback), then continue. Say one sentence ("Creating `dev/{author}/{slug}`.") so the user isn't confused about why a new branch appeared.
79
- - **Topic is ambiguous** (open-ended exploration, user didn't specify what we're building yet) → ask **before** branching, in plain text with numbered options (Pi has no built-in structured question tool):
96
+ - **Topic is clear** (the user said "fix X", "add Y feature") → run `bin/agent.sh branch --topic "<topic>"` automatically, continue in the printed worktree, and say one short sentence so the branch change is visible. Do not ask the user to approve routine branching.
97
+ - **Topic is genuinely ambiguous** → ask only for the topic, using compact numbered options:
80
98
 
81
99
  ```text
82
- Need a working branch first. Topic?
100
+ What should I call this work?
83
101
  1. <suggested slug from context>
84
102
  2. <alternative slug>
85
103
  3. Other: (name it)
86
104
  ```
87
105
 
88
106
  Wait for the reply, then branch.
107
+ - **The user explicitly requested the protected branch** → that request is consent. Record it with `echo '{branch}' > .egregore-branch-consent`, then retry. Never create the marker merely to silence the hook.
108
+
109
+ Memory, managed-repo, and runtime-state writes should bypass the project guard. If one triggers it, correct the operation target/context instead of asking for protected-branch consent.
89
110
 
90
111
  If Pi project resources are disabled, follow the same discipline as a standing instruction: never write or commit on develop/main/master.
91
112
 
@@ -97,7 +118,7 @@ If the startup card output contains `onboarding_needed`, invoke the `/onboarding
97
118
 
98
119
  ## Config Files
99
120
 
100
- - **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. **Never put secrets here.**
121
+ - **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. Optional `boundary { posture, read[], locked }` sets the org's isolation posture (see Environment Isolation). **Never put secrets here.**
101
122
  - **`.env`** — gitignored. Personal secrets. Local mode: `GITHUB_TOKEN` only. Connected mode: `GITHUB_TOKEN` + `EGREGORE_API_KEY`. **Never use `source .env`** — use `grep '^KEY=' .env | cut -d'=' -f2-`.
102
123
 
103
124
  In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API server only — `bin/graph.sh` and `bin/notify.sh` route through the API gateway.
@@ -106,7 +127,22 @@ In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API
106
127
 
107
128
  ## Knowledge Graph
108
129
 
109
- **Connected mode only.** Always use `bin/graph.sh` for Neo4j queries — never construct curl calls directly. See DEVELOPMENT.md §1 for usage examples and current schema. In local mode there is no graph; read `memory/` directly.
130
+ **Connected mode only.** For the following intents, the named read must be the
131
+ first retrieval action. Run the exact command with its safe default limit before
132
+ opening files, running broad grep, inspecting `graph-op.sh`, or writing Cypher:
133
+
134
+ - current work addressed to someone → `bash bin/graph-op.sh open-handoffs "<user>"`
135
+ - unanswered questions for someone → `bash bin/graph-op.sh pending-questions "<user>"`
136
+ - handoff → implementation → branch/PR/direction → `bash bin/graph-op.sh lineage "<topic>"`
137
+ - company/person/topic across meetings → `bash bin/graph-op.sh meeting-history "<entity-or-topic>"`
138
+
139
+ A question about calls, meetings, transcripts, demos, or how a conversation
140
+ evolved is always `meeting-history` intent when it names a company, person, or
141
+ topic—even when it does not ask for "current" or "live" state. Pass the shortest
142
+ discriminative entity or topic (for example, `meeting-history "42CAP"`), not the
143
+ whole question.
144
+
145
+ Run `catalog` only when the route is unclear. Named reads return bounded stable fields and exact canonical `evidencePath` pointers; open only the returned files needed to answer. When a read reports partial coverage, use `unprojectedPaths` rather than claiming those files are represented in the graph. Use `bin/graph.sh` for unsupported Neo4j queries and never construct curl calls directly. See DEVELOPMENT.md §1 for the schema. In local mode there is no graph; read `memory/` directly.
110
146
 
111
147
  ---
112
148
 
@@ -171,6 +207,10 @@ main ← stable (/release)
171
207
  - **Memory repo**: stays on main (separate repo, auto-merge).
172
208
  - **Never push directly to main or develop.** All changes flow through PRs.
173
209
 
210
+ ### Pull request format (all harnesses)
211
+
212
+ Every PR body follows `.claude/context/pr-format.md`, enforced by the `pr-format` CI check regardless of which harness opened it: `## What` (1–4 bullets) + `## Why` (1–3 sentences) always; `## Verification` when the diff touches non-markdown files (how it was checked, or an honest `Not verified — <reason>`); `## Risk`/`## Links` when real; title `type(scope): imperative summary` (advisory). **Never create a PR with an empty body or `--fill`** — write the body and pass it explicitly (`gh pr create --body`, or `bin/agent.sh save --pr-body` for shell agents; the bridge auto-generates a compliant skeleton only as a last resort).
213
+
174
214
  ### Managed Repos
175
215
 
176
216
  Repos in `egregore.json` → `repos[]` are cloned as siblings (`../{repo}/`). Each entry can be a string or `{"name": "...", "description": "..."}`. Match user intent to the right repo using `description`. Same branching strategy. Use `git -C` with absolute paths — never `cd` into repos. `/save` scans all managed repos for uncommitted changes.
@@ -239,17 +279,20 @@ Egregore runs in one of two configurations, set by `mode` in `egregore.json`. De
239
279
 
240
280
  ## Environment Isolation
241
281
 
242
- Sessions are confined to this project + memory + managed repos. On Pi this boundary is a standing instruction, not an enforced hook — hold it yourself.
282
+ Sessions are confined to this project + memory + managed repos, with a **two-tier boundary** — a hard wall between Egregore instances, a consent gate for everything else. On Pi the boundary is a standing instruction, not an enforced hook — hold it yourself.
243
283
 
244
- - **Never modify `~/.egregore/instances.json`** — managed by session-start.sh
245
- - **Never access another instance's files** — refuse even if asked
246
- - When a request points outside the boundary, do not improvise a workaround. Ask in plain text with numbered options:
284
+ - **Hard tier — other Egregore instances.** Denied for every tool, always. There is no consent path. Never access another instance's files — refuse even if asked. Never modify `~/.egregore/instances.json` (managed by session-start.sh). When a request points at another instance's files there is nothing to ask — refuse and explain.
285
+ - **Soft tier — paths outside the boundary.** Consent-gated. Inbox dirs (`~/Downloads`, `~/Desktop`) are readable without consent under the default posture; writes outside the project always need consent. Posture (`strict | standard | open`) and extra read roots come from `egregore.json` -> `boundary { posture, read[], locked }` (org, committed) merged with `.egregore-boundary.local.json` (personal, gitignored). `locked: true` removes the consent path entirely.
286
+ - When a request needs soft-tier consent, do not improvise a workaround. Ask in plain text with numbered options:
247
287
 
248
288
  ```text
249
289
  That path is outside this Egregore's boundary. How should we proceed?
250
- 1. Paste the contents inline
251
- 2. Move the file into the repo and point me at the new path
252
- 3. Cancel
290
+ 1. Allow its directory for this session (recorded in .egregore-boundary-consent)
291
+ 2. Always allow on this instance (added to read[] in .egregore-boundary.local.json)
292
+ 3. Paste the contents inline
293
+ 4. Cancel
253
294
  ```
254
295
 
255
- See DEVELOPMENT.md §3 for boundary details.
296
+ Never record a consent grant without the user's explicit choice of option 1 or 2 in that exchange. Session grants are cleared on session start; `locked: true` removes options 1 and 2.
297
+
298
+ See DEVELOPMENT.md §3 for boundary details and `memory/knowledge/decisions/2026-07-08-boundary-hook-consent-design.md` for the design decisions.
@@ -1,9 +1,10 @@
1
- import { existsSync, readFileSync, readdirSync } from "node:fs";
2
- import { join, relative } from "node:path";
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { Text } from "@earendil-works/pi-tui";
4
4
  import { Type } from "typebox";
5
- import { isMutatingBash } from "../../bin/pi-branch-policy.mjs";
5
+ import { isMutatingBash, isRuntimeStatePath, mutatesOnlyRuntimeState } from "../../bin/pi-branch-policy.mjs";
6
6
  import {
7
+ captureSessionEnd,
7
8
  createHandoff,
8
9
  parseViewRequest,
9
10
  publishScroll,
@@ -47,6 +48,32 @@ function errorMessage(error) {
47
48
  return error instanceof Error ? error.message : String(error);
48
49
  }
49
50
 
51
+ function nearestExistingDirectory(candidate) {
52
+ let cursor = resolve(candidate);
53
+ try {
54
+ if (existsSync(cursor) && !statSync(cursor).isDirectory()) cursor = dirname(cursor);
55
+ } catch {}
56
+ while (!existsSync(cursor)) {
57
+ const parent = dirname(cursor);
58
+ if (parent === cursor) break;
59
+ cursor = parent;
60
+ }
61
+ return cursor;
62
+ }
63
+
64
+ async function gitContext(pi, candidate) {
65
+ const probe = nearestExistingDirectory(candidate);
66
+ const topResult = await pi.exec("git", ["-C", probe, "rev-parse", "--show-toplevel"], { timeout: 5000 });
67
+ const top = String(topResult.stdout || "").trim();
68
+ if (!top) return null;
69
+ const commonResult = await pi.exec("git", ["-C", top, "rev-parse", "--git-common-dir"], { timeout: 5000 });
70
+ const commonRaw = String(commonResult.stdout || "").trim();
71
+ return {
72
+ top: resolve(top),
73
+ common: resolve(top, commonRaw || ".git"),
74
+ };
75
+ }
76
+
50
77
  export default function egregore(pi) {
51
78
  const root = process.cwd();
52
79
  const skillsDir = join(root, ".codex", "skills");
@@ -297,6 +324,18 @@ export default function egregore(pi) {
297
324
  }
298
325
  });
299
326
 
327
+ pi.on("session_shutdown", async (event, ctx) => {
328
+ // Reload keeps the same logical session alive. Every other graceful
329
+ // shutdown is Pi's equivalent of Claude Code's SessionEnd hook.
330
+ if (event?.reason === "reload") return;
331
+ try {
332
+ await captureSessionEnd(pi, root, ctx.sessionManager.getSessionFile(), undefined);
333
+ } catch {
334
+ // Loss-prevention capture must never prevent Pi from exiting. The
335
+ // attendant remains the fallback for interrupted or failed shutdowns.
336
+ }
337
+ });
338
+
300
339
  pi.on("agent_settled", async (_event, ctx) => {
301
340
  if (!activeWorkflow || !ctx.hasUI) return;
302
341
  activeWorkflow = "";
@@ -305,20 +344,71 @@ export default function egregore(pi) {
305
344
  });
306
345
 
307
346
  pi.on("tool_call", async (event, ctx) => {
308
- const mutates = event.toolName === "write" || event.toolName === "edit" ||
309
- (event.toolName === "bash" && isMutatingBash(String(event.input?.command || "")));
347
+ const isFileWrite = event.toolName === "write" || event.toolName === "edit";
348
+ const command = String(event.input?.command || "");
349
+ const mutates = isFileWrite || (event.toolName === "bash" && isMutatingBash(command));
310
350
  if (!mutates) return;
311
351
 
312
- const result = await pi.exec("git", ["-C", ctx.cwd, "branch", "--show-current"], { timeout: 5000 });
352
+ // Resolve the branch in the git context the write actually lands in.
353
+ // Task worktrees from `bin/agent.sh branch` live outside ctx.cwd but sit
354
+ // on a task branch, so checking ctx.cwd alone blocks legitimate worktree
355
+ // edits whenever the session checkout itself is on develop. Memory and
356
+ // managed repos have a different Git common dir and are outside this guard.
357
+ const hub = await gitContext(pi, ctx.cwd);
358
+ if (event.toolName === "bash" && mutatesOnlyRuntimeState(command, hub?.top || ctx.cwd)) return;
359
+ let operation = hub;
360
+ if (isFileWrite) {
361
+ const targetPath = String(event.input?.path || "");
362
+ const abs = isAbsolute(targetPath) ? targetPath : join(ctx.cwd, targetPath);
363
+ if (hub && isRuntimeStatePath(abs, hub.top)) return;
364
+ operation = await gitContext(pi, abs);
365
+ if (!operation && hub) {
366
+ const rel = relative(hub.top, abs);
367
+ if (rel.startsWith("..") || isAbsolute(rel)) return;
368
+ }
369
+ } else {
370
+ // bash: `git -C PATH` or a leading `cd DIR &&` retargets the command
371
+ // away from ctx.cwd — resolve the branch where it actually runs so
372
+ // worktree commits (git -C $WT commit) aren't misjudged by the session
373
+ // checkout's branch.
374
+ const gitC = command.match(/(?:^|[;&|]\s*)git\s+-C\s+("[^"]*"|'[^']*'|\S+)/);
375
+ const cdAnd = command.match(/^\s*cd\s+("[^"]*"|'[^']*'|\S+)\s*&&/);
376
+ const raw = (gitC && gitC[1]) || (cdAnd && cdAnd[1]);
377
+ if (raw) {
378
+ const target = raw.replace(/^['"]|['"]$/g, "");
379
+ const abs = isAbsolute(target) ? target : join(ctx.cwd, target);
380
+ operation = await gitContext(pi, abs);
381
+ }
382
+ }
383
+
384
+ if (hub && operation && operation.common !== hub.common) return;
385
+ const gitDir = operation?.top || hub?.top || ctx.cwd;
386
+ const result = await pi.exec("git", ["-C", gitDir, "branch", "--show-current"], { timeout: 5000 });
313
387
  const branch = String(result.stdout || "").trim();
314
388
  if (!PROTECTED_BRANCHES.has(branch)) return;
315
389
 
316
- const consentFile = join(ctx.cwd, ".egregore-branch-consent");
390
+ // The documented escape hatch must itself be runnable: recording consent
391
+ // (echo 'develop' into .egregore-branch-consent) contains a redirect and
392
+ // would otherwise be blocked by this very gate.
393
+ if (event.toolName === "bash") {
394
+ const consentCmd = new RegExp(
395
+ "^\\s*echo\\s+['\"]?" + branch + "['\"]?\\s*>\\s*\\.egregore-branch-consent\\s*$"
396
+ );
397
+ if (consentCmd.test(String(event.input?.command || "").trim())) return;
398
+ }
399
+
400
+ const consentFile = join(gitDir, ".egregore-branch-consent");
317
401
  if (existsSync(consentFile) && readFileSync(consentFile, "utf8").trim() === branch) return;
318
402
 
319
403
  return {
320
404
  block: true,
321
- reason: `Protected branch (${branch}). Create a task worktree first with: bin/agent.sh branch --topic "<work topic>". Only if the user explicitly authorizes writing on ${branch}, record it with: echo '${branch}' > .egregore-branch-consent`,
405
+ reason: [
406
+ `Protected branch (${branch}). Move project work to a task branch before writing.`,
407
+ `If the work topic is clear, run bin/agent.sh branch --topic "<work topic>" automatically and continue in the printed worktree; do not ask for routine Git permission.`,
408
+ `Ask only when the topic is genuinely ambiguous.`,
409
+ `Only when the user explicitly requested ${branch}, record consent with: echo '${branch}' > .egregore-branch-consent`,
410
+ "Memory, managed-repo, and runtime-state writes should bypass this project guard.",
411
+ ].join("\n"),
322
412
  };
323
413
  });
324
414
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "generatedBy": "bin/pi-render-spec.mjs",
3
3
  "source": "AGENTS.md generated Codex spec",
4
- "sourceHash": "4c268f3ebb13",
5
- "outputHash": "2d0d2ac65e04",
4
+ "sourceHash": "6ad94ccc38d8",
5
+ "outputHash": "7a3db92a611e",
6
6
  "adaptations": [
7
7
  "Pi project trust and APPEND_SYSTEM delivery",
8
8
  "Pi tool_call branch gate",
@@ -35,7 +35,8 @@ Use the runtime-neutral bridge instead of Claude Code slash commands:
35
35
 
36
36
  ```bash
37
37
  bin/agent.sh branch --topic "auth review"
38
- bin/agent.sh save --message "Save: auth review" --topic "auth review"
38
+ bin/agent.sh save --message "Save: auth review" --topic "auth review" \
39
+ --pr-body "$PR_BODY" # body per .claude/context/pr-format.md (auto-skeleton if omitted)
39
40
  bin/agent.sh wrap --from alice --topic "auth review" \
40
41
  --summary "Implemented the OAuth callback parser and documented follow-ups." \
41
42
  --body "Open threads: review error cases and browser redirects."
@@ -96,6 +97,24 @@ When a user reports broken or missing framework behavior, the first question is
96
97
 
97
98
  ---
98
99
 
100
+ ## Cross-Runtime Compatibility
101
+
102
+ Framework behavior must work across Claude Code, Codex, and Pi Egregores. Put
103
+ shared mechanics in runtime-neutral `bin/` scripts; keep `.claude/`, `.codex/`,
104
+ and `.pi/` as thin adapters to those mechanics. When changing startup, session
105
+ lifecycle, capture, skills, or commands, trace all three runtime entry points
106
+ and add or update a parity test. Do not call a framework change complete based
107
+ on one runtime alone. Regenerate derived Codex and Pi specifications or runtime
108
+ bundles whenever their sources change.
109
+
110
+ ---
111
+
112
+ ## Voice
113
+
114
+ Egregore's voice is governed by `.claude/rules/voice-bedrock.md` (always loaded). Register-specific skills: `egregore-voice` (external), `product-voice` (internal UX), `character-v4` (encounters), `alpha-openers` (outreach). See voice-bedrock for the full register map.
115
+
116
+ ---
117
+
99
118
  ## On Launch — MANDATORY FIRST ACTION
100
119
 
101
120
  The `egregore` launcher renders the Egregore startup card (identity, team momentum, and pending work) via `bin/codex-session-start.sh` before Codex starts, and installs project skills. 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, run `bash bin/codex-session-start.sh --card`.
@@ -146,19 +165,22 @@ If on develop after two messages, create a branch immediately from whatever cont
146
165
 
147
166
  ### Branch-guard protocol
148
167
 
149
- The `.codex/hooks/branch-guard.js` PreToolUse hook (enabled by the launcher via `--enable hooks`) denies writes, patches, and commits on `develop`/`main`/`master`. When it fires, choose between two paths based on how clear the topic is:
168
+ The `.codex/hooks/branch-guard.js` PreToolUse hook (enabled by the launcher via `--enable hooks`) protects project writes on `develop`/`main`/`master`. Its block message is operational guidance, not a reason to interrupt the user with routine Git choices:
150
169
 
151
- - **Topic is obvious from the last turn** (user said "fix X", "add Y feature") → auto-branch silently with `bin/agent.sh branch --topic "<topic>"` (or the git checkout fallback), then continue. Say one sentence ("Creating `dev/{author}/{slug}`.") so the user isn't confused about why a new branch appeared.
152
- - **Topic is ambiguous** (open-ended exploration, user didn't specify what we're building yet) → ask **before** branching, in plain text with numbered options (Codex has no structured question tool):
170
+ - **Topic is clear** (the user said "fix X", "add Y feature") → run `bin/agent.sh branch --topic "<topic>"` automatically, continue in the printed worktree, and say one short sentence so the branch change is visible. Do not ask the user to approve routine branching.
171
+ - **Topic is genuinely ambiguous** → ask only for the topic, using compact numbered options:
153
172
 
154
173
  ```text
155
- Need a working branch first. Topic?
174
+ What should I call this work?
156
175
  1. <suggested slug from context>
157
176
  2. <alternative slug>
158
177
  3. Other: (name it)
159
178
  ```
160
179
 
161
180
  Wait for the reply, then branch.
181
+ - **The user explicitly requested the protected branch** → that request is consent. Record it with `echo '{branch}' > .egregore-branch-consent`, then retry. Never create the marker merely to silence the hook.
182
+
183
+ Memory, managed-repo, and runtime-state writes should bypass the project guard. If one triggers it, correct the operation target/context instead of asking for protected-branch consent.
162
184
 
163
185
  If this Codex build does not support hooks, follow the same discipline as a standing instruction: never write or commit on develop/main/master.
164
186
 
@@ -170,7 +192,7 @@ If the startup card output contains `onboarding_needed`, invoke the `$onboarding
170
192
 
171
193
  ## Config Files
172
194
 
173
- - **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. **Never put secrets here.**
195
+ - **`egregore.json`** — committed. Non-secret org config: `org_name`, `github_org`, `memory_repo`, `slug`, `mode`. `api_url` is connected-mode only. Optional `boundary { posture, read[], locked }` sets the org's isolation posture (see Environment Isolation). **Never put secrets here.**
174
196
  - **`.env`** — gitignored. Personal secrets. Local mode: `GITHUB_TOKEN` only. Connected mode: `GITHUB_TOKEN` + `EGREGORE_API_KEY`. **Never use `source .env`** — use `grep '^KEY=' .env | cut -d'=' -f2-`.
175
197
 
176
198
  In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API server only — `bin/graph.sh` and `bin/notify.sh` route through the API gateway.
@@ -179,7 +201,22 @@ In connected mode, infrastructure credentials (Neo4j, Telegram) live on the API
179
201
 
180
202
  ## Knowledge Graph
181
203
 
182
- **Connected mode only.** Always use `bin/graph.sh` for Neo4j queries — never construct curl calls directly. See DEVELOPMENT.md §1 for usage examples and current schema. In local mode there is no graph; read `memory/` directly.
204
+ **Connected mode only.** For the following intents, the named read must be the
205
+ first retrieval action. Run the exact command with its safe default limit before
206
+ opening files, running broad grep, inspecting `graph-op.sh`, or writing Cypher:
207
+
208
+ - current work addressed to someone → `bash bin/graph-op.sh open-handoffs "<user>"`
209
+ - unanswered questions for someone → `bash bin/graph-op.sh pending-questions "<user>"`
210
+ - handoff → implementation → branch/PR/direction → `bash bin/graph-op.sh lineage "<topic>"`
211
+ - company/person/topic across meetings → `bash bin/graph-op.sh meeting-history "<entity-or-topic>"`
212
+
213
+ A question about calls, meetings, transcripts, demos, or how a conversation
214
+ evolved is always `meeting-history` intent when it names a company, person, or
215
+ topic—even when it does not ask for "current" or "live" state. Pass the shortest
216
+ discriminative entity or topic (for example, `meeting-history "42CAP"`), not the
217
+ whole question.
218
+
219
+ Run `catalog` only when the route is unclear. Named reads return bounded stable fields and exact canonical `evidencePath` pointers; open only the returned files needed to answer. When a read reports partial coverage, use `unprojectedPaths` rather than claiming those files are represented in the graph. Use `bin/graph.sh` for unsupported Neo4j queries and never construct curl calls directly. See DEVELOPMENT.md §1 for the schema. In local mode there is no graph; read `memory/` directly.
183
220
 
184
221
  ---
185
222
 
@@ -244,6 +281,10 @@ main ← stable (/release)
244
281
  - **Memory repo**: stays on main (separate repo, auto-merge).
245
282
  - **Never push directly to main or develop.** All changes flow through PRs.
246
283
 
284
+ ### Pull request format (all harnesses)
285
+
286
+ Every PR body follows `.claude/context/pr-format.md`, enforced by the `pr-format` CI check regardless of which harness opened it: `## What` (1–4 bullets) + `## Why` (1–3 sentences) always; `## Verification` when the diff touches non-markdown files (how it was checked, or an honest `Not verified — <reason>`); `## Risk`/`## Links` when real; title `type(scope): imperative summary` (advisory). **Never create a PR with an empty body or `--fill`** — write the body and pass it explicitly (`gh pr create --body`, or `bin/agent.sh save --pr-body` for shell agents; the bridge auto-generates a compliant skeleton only as a last resort).
287
+
247
288
  ### Managed Repos
248
289
 
249
290
  Repos in `egregore.json` → `repos[]` are cloned as siblings (`../{repo}/`). Each entry can be a string or `{"name": "...", "description": "..."}`. Match user intent to the right repo using `description`. Same branching strategy. Use `git -C` with absolute paths — never `cd` into repos. `$save` scans all managed repos for uncommitted changes.
@@ -268,13 +309,13 @@ Invoke commands from user intent — don't wait for the slash. Each command file
268
309
  **Knowledge** — `$search` `$deep-reflect` `$archive` `$note` `$add` `$meeting` `$ingest` `$scroll`
269
310
  **Identity** — `$me` (view profile or set display name)
270
311
  **Coordination** — `$ask` `$quest` `$issue` `$invite` `$delete-user` `$announce`
271
- **Connectors** — `$telegram-connect` (Telegram group setup)
312
+ **Connectors** — `$telegram-connect` (Telegram group setup) `$teams-connect` (Microsoft Teams channel setup)
272
313
  **Git** — `$branch` `$commit` `$push` `$pr` `$save` `$review-pr` `$contribute`
273
314
  **Spirits** — `$summon` (persistent agent processes)
274
315
  **Infra** — `$setup` `$update` `$pull` `$env` `$infra` `$sync-repos` `$release` `$checkup`
275
316
 
276
317
  **Disambiguation:**
277
- - Knowledge: `$reflect` (share-ready) · `$note` (half-baked) · `$deep-reflect` (cross-reference) · `$archive` (AI patterns)
318
+ - Knowledge: `$reflect` (share-ready) · `$note` (half-baked) · `$deep-reflect` (deep research over memory — questions AND cross-referencing) · `$archive` (AI patterns)
278
319
  - Finding things — the retrieval layers: **Grep/ls** (Claude Code / Codex file ops — always available, OSS) · **`bin/graph.sh`** (indexed relevance + relationships/status — paid). Use grep proactively; the graph deepens it when connected. (Semantic `$search` is dormant — evals showed it only pays off on massive unstructured data.)
279
320
  - Finding **something you generated** (a scroll, handoff, emissary, decision, any hosted egregore.xyz link): `bash bin/artifacts.sh find <query>`. Every generative surface already records into memory — hosted artifacts self-register into `memory/artifacts/` at publish (and the record is committed+pushed, so it's findable from *any* session, not just the one that made it); handoffs land in `memory/handoffs/`, emissaries in `memory/handoffs/outbound/`, decisions/findings/patterns in `memory/knowledge/`. The finder searches **all** of them, ranks title/topic matches above passing body mentions, tags each hit by type (`[document]`/`[handoff]`/`[emissary]`/`[decision]`), and surfaces the shareable URL. Findable by **content, not just name**, across all three layers — grep-first for OSS, folding in search + graph when available. *"bring me the artifact where I laid out our GTM plan"* → this.
280
321
  - Artifacts with questions: `$scroll` (living paper + embedded harvest, updates in place) · `$view` (static render) · `$harvest` (elicitation, no published face)
@@ -282,8 +323,8 @@ Invoke commands from user intent — don't wait for the slash. Each command file
282
323
  - Ending: `$wrap` (personal closure) · `$handoff` (notes for others) · `$save` (still working)
283
324
  - Tasks: `$todo` (personal) · `$quest` (team exploration) · `$issue` (something broken)
284
325
  - Questions: `$ask [person]` (async) · just ask (agent answers from context)
285
- - Ingestion: `$ingest meeting` · `$ingest user-interview` · `$ingest google` · ambiguous → ask which type
286
- - Connectors: `$telegram-connect` (Telegram group) · `$ingest` (bring content in)
326
+ - Ingestion: `$ingest <file-or-folder>` (org-scoped corpus intake) · `$ingest meeting` · `$ingest user-interview` · `$ingest google` · ambiguous → ask which type
327
+ - Connectors: `$telegram-connect` (Telegram group) · `$teams-connect` (MS Teams channel) · `$ingest` (bring content in)
287
328
  - Identity: `$me` — "who am I", "call me oz"
288
329
  - People: `$invite` (add) · `$delete-user` (remove)
289
330
  - PRs: `$pr` (create) · `$review-pr` (review)
@@ -332,19 +373,22 @@ Egregore runs in one of two configurations, set by `mode` in `egregore.json`. De
332
373
 
333
374
  ## Environment Isolation
334
375
 
335
- Sessions are confined to this project + memory + managed repos. On Codex this boundary is a standing instruction, not an enforced hook — hold it yourself.
376
+ Sessions are confined to this project + memory + managed repos, with a **two-tier boundary** — a hard wall between Egregore instances, a consent gate for everything else. On Codex the boundary is a standing instruction, not an enforced hook — hold it yourself.
336
377
 
337
- - **Never modify `~/.egregore/instances.json`** — managed by session-start.sh
338
- - **Never access another instance's files** — refuse even if asked
339
- - When a request points outside the boundary, do not improvise a workaround. Ask in plain text with numbered options:
378
+ - **Hard tier — other Egregore instances.** Denied for every tool, always. There is no consent path. Never access another instance's files — refuse even if asked. Never modify `~/.egregore/instances.json` (managed by session-start.sh). When a request points at another instance's files there is nothing to ask — refuse and explain.
379
+ - **Soft tier — paths outside the boundary.** Consent-gated. Inbox dirs (`~/Downloads`, `~/Desktop`) are readable without consent under the default posture; writes outside the project always need consent. Posture (`strict | standard | open`) and extra read roots come from `egregore.json` -> `boundary { posture, read[], locked }` (org, committed) merged with `.egregore-boundary.local.json` (personal, gitignored). `locked: true` removes the consent path entirely.
380
+ - When a request needs soft-tier consent, do not improvise a workaround. Ask in plain text with numbered options:
340
381
 
341
382
  ```text
342
383
  That path is outside this Egregore's boundary. How should we proceed?
343
- 1. Paste the contents inline
344
- 2. Move the file into the repo and point me at the new path
345
- 3. Cancel
384
+ 1. Allow its directory for this session (recorded in .egregore-boundary-consent)
385
+ 2. Always allow on this instance (added to read[] in .egregore-boundary.local.json)
386
+ 3. Paste the contents inline
387
+ 4. Cancel
346
388
  ```
347
389
 
348
- See DEVELOPMENT.md §3 for boundary details.
390
+ Never record a consent grant without the user's explicit choice of option 1 or 2 in that exchange. Session grants are cleared on session start; `locked: true` removes options 1 and 2.
391
+
392
+ See DEVELOPMENT.md §3 for boundary details and `memory/knowledge/decisions/2026-07-08-boundary-hook-consent-design.md` for the design decisions.
349
393
 
350
394
  <!-- END GENERATED EGREGORE CODEX SPEC -->
@@ -9,7 +9,7 @@ trap "rm -rf $TMPDIR" EXIT
9
9
 
10
10
  # --- Read config ---
11
11
  API_URL=$(jq -r '.api_url // empty' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
12
- API_KEY=$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2-)
12
+ API_KEY="${EGREGORE_API_KEY:-$(grep '^EGREGORE_API_KEY=' "$SCRIPT_DIR/.env" 2>/dev/null | cut -d'=' -f2-)}"
13
13
  ORG=$(jq -r '.org_name // "Egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "Egregore")
14
14
  DATE=$(date '+%b %d')
15
15