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.
- package/assets/egregore-launcher.js +266 -4
- package/lib/local.js +11 -1
- package/lib/setup.js +33 -0
- package/package.json +2 -2
- package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
- package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
- package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
- package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
- package/runtime/codex/.codex/skills/save/SKILL.md +29 -5
- package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
- package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
- package/runtime/codex/.codex/spec-manifest.json +19 -7
- package/runtime/codex/AGENTS.md +63 -19
- package/runtime/codex/bin/activity-data.sh +1 -1
- package/runtime/codex/bin/agent.sh +88 -25
- package/runtime/codex/bin/attendant.sh +525 -0
- package/runtime/codex/bin/capture-reconcile.sh +67 -0
- package/runtime/codex/bin/capture-run.sh +341 -0
- package/runtime/codex/bin/codex-render-spec.mjs +29 -14
- package/runtime/codex/bin/codex-session-start.sh +49 -4
- package/runtime/codex/bin/codex-skill-render.mjs +23 -4
- package/runtime/codex/bin/graph-op.sh +719 -49
- package/runtime/codex/bin/graph-wal.sh +3 -2
- package/runtime/codex/bin/handoff-run.sh +72 -0
- package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
- package/runtime/codex/bin/index-handoff.sh +26 -17
- package/runtime/codex/bin/ingest-graph.sh +80 -0
- package/runtime/codex/bin/ingest-surface.html +429 -0
- package/runtime/codex/bin/ingest.py +614 -0
- package/runtime/codex/bin/ingest.sh +231 -0
- package/runtime/codex/bin/ingest_graph.py +776 -0
- package/runtime/codex/bin/ingest_surface.py +464 -0
- package/runtime/codex/bin/lib/git-sync.sh +2 -6
- package/runtime/codex/bin/lib/greeting.sh +157 -22
- package/runtime/codex/bin/lib/identity.sh +50 -29
- package/runtime/codex/bin/lib/noncode.sh +31 -0
- package/runtime/codex/bin/person.py +610 -0
- package/runtime/codex/bin/person.sh +448 -0
- package/runtime/codex/bin/session-autosave.sh +184 -0
- package/runtime/codex/bin/session-log.sh +199 -0
- package/runtime/codex/bin/settings.sh +1 -1
- package/runtime/codex/manifest.json +19 -1
- package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
- package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
- package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
- package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
- package/runtime/pi/.codex/skills/save/SKILL.md +29 -5
- package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
- package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
- package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
- package/runtime/pi/.codex/spec-manifest.json +19 -7
- package/runtime/pi/.pi/APPEND_SYSTEM.md +57 -14
- package/runtime/pi/.pi/extensions/egregore.ts +98 -8
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +63 -19
- package/runtime/pi/bin/activity-data.sh +1 -1
- package/runtime/pi/bin/agent.sh +88 -25
- package/runtime/pi/bin/attendant.sh +525 -0
- package/runtime/pi/bin/capture-reconcile.sh +67 -0
- package/runtime/pi/bin/capture-run.sh +341 -0
- package/runtime/pi/bin/codex-render-spec.mjs +29 -14
- package/runtime/pi/bin/codex-session-start.sh +49 -4
- package/runtime/pi/bin/codex-skill-render.mjs +23 -4
- package/runtime/pi/bin/graph-op.sh +719 -49
- package/runtime/pi/bin/graph-wal.sh +3 -2
- package/runtime/pi/bin/handoff-run.sh +72 -0
- package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
- package/runtime/pi/bin/index-handoff.sh +26 -17
- package/runtime/pi/bin/ingest-graph.sh +80 -0
- package/runtime/pi/bin/ingest-surface.html +429 -0
- package/runtime/pi/bin/ingest.py +614 -0
- package/runtime/pi/bin/ingest.sh +231 -0
- package/runtime/pi/bin/ingest_graph.py +776 -0
- package/runtime/pi/bin/ingest_surface.py +464 -0
- package/runtime/pi/bin/lib/git-sync.sh +2 -6
- package/runtime/pi/bin/lib/greeting.sh +157 -22
- package/runtime/pi/bin/lib/identity.sh +50 -29
- package/runtime/pi/bin/lib/noncode.sh +31 -0
- package/runtime/pi/bin/person.py +610 -0
- package/runtime/pi/bin/person.sh +448 -0
- package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
- package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
- package/runtime/pi/bin/publish-artifact.sh +14 -0
- package/runtime/pi/bin/session-autosave.sh +184 -0
- package/runtime/pi/bin/session-log.sh +199 -0
- package/runtime/pi/bin/settings.sh +274 -0
- 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)
|
|
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
|
|
79
|
-
- **Topic is ambiguous**
|
|
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
|
-
|
|
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.**
|
|
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
|
|
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
|
|
245
|
-
- **
|
|
246
|
-
- When a request
|
|
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.
|
|
251
|
-
2.
|
|
252
|
-
3.
|
|
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
|
-
|
|
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
|
|
309
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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": "
|
|
5
|
-
"outputHash": "
|
|
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",
|
package/runtime/pi/AGENTS.md
CHANGED
|
@@ -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`)
|
|
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
|
|
152
|
-
- **Topic is ambiguous**
|
|
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
|
-
|
|
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.**
|
|
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-
|
|
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
|
|
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
|
|
338
|
-
- **
|
|
339
|
-
- When a request
|
|
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.
|
|
344
|
-
2.
|
|
345
|
-
3.
|
|
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
|
-
|
|
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
|
|
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
|
|