create-egregore 0.17.4 → 0.18.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 (106) hide show
  1. package/assets/egregore-launcher.js +1965 -0
  2. package/assets/egregore-launcher.sh +385 -0
  3. package/bin/cli.js +472 -81
  4. package/lib/api.js +2 -2
  5. package/lib/auth.js +255 -20
  6. package/lib/codex-runtime.js +268 -0
  7. package/lib/commands.js +31 -0
  8. package/lib/context.js +1 -1
  9. package/lib/local.js +616 -189
  10. package/lib/setup.js +822 -84
  11. package/lib/ui.js +780 -84
  12. package/package.json +12 -3
  13. package/runtime/codex/.codex/config.toml +15 -0
  14. package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
  15. package/runtime/codex/.codex/hooks.json +17 -0
  16. package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
  17. package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
  18. package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
  19. package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
  20. package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
  21. package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
  22. package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
  23. package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
  24. package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
  25. package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
  26. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
  27. package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
  28. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
  29. package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
  30. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
  31. package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
  32. package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
  33. package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
  34. package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
  35. package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
  36. package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
  37. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
  38. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  39. package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
  40. package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
  41. package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
  42. package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
  43. package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
  44. package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
  45. package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
  46. package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
  47. package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
  48. package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
  49. package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
  50. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
  51. package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
  52. package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
  53. package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
  54. package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
  55. package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
  56. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
  57. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
  58. package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
  59. package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
  60. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
  61. package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
  62. package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
  63. package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
  64. package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
  65. package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
  66. package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
  67. package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
  68. package/runtime/codex/.codex/spec-manifest.json +108 -0
  69. package/runtime/codex/AGENTS.md +40 -0
  70. package/runtime/codex/bin/activity-data.sh +177 -0
  71. package/runtime/codex/bin/agent.sh +669 -0
  72. package/runtime/codex/bin/codex-install-skills.sh +78 -0
  73. package/runtime/codex/bin/codex-render-spec.mjs +308 -0
  74. package/runtime/codex/bin/codex-session-start.sh +238 -0
  75. package/runtime/codex/bin/codex-skill-render.mjs +315 -0
  76. package/runtime/codex/bin/codex-sync-skills.sh +223 -0
  77. package/runtime/codex/bin/dashboard-data.sh +174 -0
  78. package/runtime/codex/bin/graph-batch.sh +60 -0
  79. package/runtime/codex/bin/graph-op.sh +380 -0
  80. package/runtime/codex/bin/graph-wal.sh +228 -0
  81. package/runtime/codex/bin/graph.sh +141 -0
  82. package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
  83. package/runtime/codex/bin/handoff-run.sh +431 -0
  84. package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
  85. package/runtime/codex/bin/index-handoff.sh +306 -0
  86. package/runtime/codex/bin/lib/artifact-id.sh +46 -0
  87. package/runtime/codex/bin/lib/config.sh +71 -0
  88. package/runtime/codex/bin/lib/context.sh +463 -0
  89. package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
  90. package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
  91. package/runtime/codex/bin/lib/git-sync.sh +262 -0
  92. package/runtime/codex/bin/lib/greeting.sh +414 -0
  93. package/runtime/codex/bin/lib/hash.sh +13 -0
  94. package/runtime/codex/bin/lib/identity.sh +164 -0
  95. package/runtime/codex/bin/lib/permissions.sh +106 -0
  96. package/runtime/codex/bin/lib/time.sh +56 -0
  97. package/runtime/codex/bin/lib/worktree-links.sh +54 -0
  98. package/runtime/codex/bin/notify.sh +207 -0
  99. package/runtime/codex/bin/publish-artifact.sh +173 -0
  100. package/runtime/codex/bin/publish-references.sh +144 -0
  101. package/runtime/codex/bin/repo-state.sh +99 -0
  102. package/runtime/codex/bin/telemetry.sh +350 -0
  103. package/runtime/codex/bin/worktree-create.sh +118 -0
  104. package/runtime/codex/bin/worktree-remove.sh +50 -0
  105. package/runtime/codex/bin/worktree.sh +198 -0
  106. package/runtime/codex/manifest.json +155 -0
@@ -0,0 +1,78 @@
1
+ #!/bin/bash
2
+ # Make Egregore's project-local Codex skills available without duplicating
3
+ # them in CODEX_HOME.
4
+ set -euo pipefail
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
7
+ SOURCE_DIR="$SCRIPT_DIR/.codex/skills"
8
+ HOOKS_FILE="$SCRIPT_DIR/.codex/hooks.json"
9
+ HOOKS_DIR="$SCRIPT_DIR/.codex/hooks"
10
+ CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}"
11
+ TARGET_DIR="$CODEX_HOME_DIR/skills"
12
+
13
+ [ -d "$SOURCE_DIR" ] || exit 0
14
+
15
+ # Current Codex discovers project skills via .agents/skills (and follows
16
+ # symlinks). The repo commits .agents/skills -> ../.codex/skills; repair it
17
+ # here for checkouts that lost the symlink (zip downloads, some Windows
18
+ # clones). Never clobber a real directory the user created.
19
+ AGENTS_DIR="$SCRIPT_DIR/.agents"
20
+ AGENTS_LINK="$AGENTS_DIR/skills"
21
+ if [ ! -e "$AGENTS_LINK" ] && [ ! -L "$AGENTS_LINK" ]; then
22
+ mkdir -p "$AGENTS_DIR"
23
+ ln -s ../.codex/skills "$AGENTS_LINK" 2>/dev/null || true
24
+ fi
25
+
26
+ is_egregore_skill() {
27
+ local file="$1"
28
+ [ -f "$file" ] || return 1
29
+ grep -q 'Egregore' "$file" 2>/dev/null || return 1
30
+ grep -Eq 'bin/agent\.sh|bin/activity-data\.sh|bin/codex-skill-render\.mjs|Native Codex|generated-by: bin/codex-sync-skills\.sh|\.claude/skills' "$file" 2>/dev/null
31
+ }
32
+
33
+ installed=0
34
+ for skill_dir in "$SOURCE_DIR"/*; do
35
+ [ -d "$skill_dir" ] || continue
36
+ [ -f "$skill_dir/SKILL.md" ] || continue
37
+ installed=$((installed + 1))
38
+ done
39
+
40
+ # Current Codex loads project-local .codex/skills from --cd. Older Egregore
41
+ # builds copied the same skills globally, which makes Codex show duplicates.
42
+ # Default to local-only and remove legacy global copies that are recognizably
43
+ # Egregore-managed. Set EGREGORE_CODEX_INSTALL_GLOBAL=1 for older Codex builds.
44
+ if [ "${EGREGORE_CODEX_INSTALL_GLOBAL:-0}" != "1" ]; then
45
+ removed=0
46
+ if [ -d "$TARGET_DIR" ]; then
47
+ for skill_dir in "$SOURCE_DIR"/*; do
48
+ [ -d "$skill_dir" ] || continue
49
+ [ -f "$skill_dir/SKILL.md" ] || continue
50
+ name="$(basename "$skill_dir")"
51
+ target="$TARGET_DIR/$name/SKILL.md"
52
+ if cmp -s "$skill_dir/SKILL.md" "$target" 2>/dev/null || is_egregore_skill "$target"; then
53
+ rm -rf "$TARGET_DIR/$name"
54
+ removed=$((removed + 1))
55
+ fi
56
+ done
57
+ fi
58
+ hooks_ready=0
59
+ if [ -f "$HOOKS_FILE" ] && [ -d "$HOOKS_DIR" ]; then
60
+ hooks_ready=1
61
+ fi
62
+ echo "codex project skills ready: $installed (removed global duplicates: $removed; hooks ready: $hooks_ready)"
63
+ exit 0
64
+ fi
65
+
66
+ mkdir -p "$TARGET_DIR"
67
+
68
+ copied=0
69
+ for skill_dir in "$SOURCE_DIR"/*; do
70
+ [ -d "$skill_dir" ] || continue
71
+ [ -f "$skill_dir/SKILL.md" ] || continue
72
+ name="$(basename "$skill_dir")"
73
+ mkdir -p "$TARGET_DIR/$name"
74
+ cp "$skill_dir/SKILL.md" "$TARGET_DIR/$name/SKILL.md"
75
+ copied=$((copied + 1))
76
+ done
77
+
78
+ echo "codex global skills installed: $copied"
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+ // Derive the Codex-native Egregore behavioral spec from CLAUDE.md.
3
+ //
4
+ // CLAUDE.md is the single source of truth for egregore behavior. Codex does
5
+ // not read CLAUDE.md — it reads AGENTS.md (root, concatenated, 32 KiB cap).
6
+ // This script renders CLAUDE.md into a generated block inside AGENTS.md the
7
+ // same way bin/codex-sync-skills.sh derives skills: deterministic transform,
8
+ // explicit per-section adaptation, manifest of what was adapted or dropped.
9
+ //
10
+ // node bin/codex-render-spec.mjs # render AGENTS.md + manifest
11
+ // node bin/codex-render-spec.mjs --check # exit 1 if rendered output is stale
12
+ //
13
+ // Adaptation rules (the DECIDED option-b translations):
14
+ // EnterWorktree -> bin/agent.sh branch / git checkout fallback
15
+ // AskUserQuestion -> numbered options in plain text
16
+ // hook-dependent behavior -> .codex/hooks equivalent where it exists,
17
+ // stated as an instruction where it doesn't
18
+ // Claude-only mechanics -> dropped explicitly (recorded in the manifest)
19
+ // /command tokens -> $skill tokens when a Codex skill of that name exists
20
+ //
21
+ // Manifest: .codex/spec-manifest.json — one entry per CLAUDE.md section with
22
+ // action (keep|replace), the source section hash, and a note explaining the
23
+ // translation. `replace` sections embed hand-authored Codex-native bodies;
24
+ // when the source section changes, the recorded hash goes stale and --check
25
+ // fails, forcing a review of the translation.
26
+
27
+ import fs from 'node:fs';
28
+ import path from 'node:path';
29
+ import crypto from 'node:crypto';
30
+ import { fileURLToPath } from 'node:url';
31
+
32
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
33
+ const CLAUDE_MD = path.join(ROOT, 'CLAUDE.md');
34
+ const AGENTS_MD = path.join(ROOT, 'AGENTS.md');
35
+ const MANIFEST = path.join(ROOT, '.codex', 'spec-manifest.json');
36
+
37
+ const BEGIN = '<!-- BEGIN GENERATED EGREGORE CODEX SPEC — derived from CLAUDE.md by bin/codex-render-spec.mjs; do not edit by hand -->';
38
+ const END = '<!-- END GENERATED EGREGORE CODEX SPEC -->';
39
+
40
+ function sha(text) {
41
+ return crypto.createHash('sha256').update(text).digest('hex').slice(0, 12);
42
+ }
43
+
44
+ // ── fence-aware section splitter ────────────────────────────────────────
45
+ // Splits markdown into the intro (before the first H2) and one chunk per H2.
46
+ // `## ` lines inside ``` fences do not start sections.
47
+ function splitSections(markdown) {
48
+ const lines = markdown.split('\n');
49
+ const sections = [];
50
+ let current = { heading: '(intro)', body: [] };
51
+ let inFence = false;
52
+ for (const line of lines) {
53
+ if (/^```/.test(line.trim())) inFence = !inFence;
54
+ if (!inFence && /^## /.test(line)) {
55
+ sections.push(current);
56
+ current = { heading: line.replace(/^## /, '').trim(), body: [] };
57
+ continue;
58
+ }
59
+ current.body.push(line);
60
+ }
61
+ sections.push(current);
62
+ return sections.map((s) => ({ heading: s.heading, body: s.body.join('\n').replace(/^\n+/, '').replace(/\n+$/, '') }));
63
+ }
64
+
65
+ // ── /command -> $skill token transform (outside code fences) ────────────
66
+ // Deterministic: every backticked /token becomes a $skill token except a
67
+ // static denylist of Claude Code harness features that are not Egregore
68
+ // skills on any runtime. Consulting the local .codex/skills directory here
69
+ // would make the rendered output depend on which instance ran the render
70
+ // (public checkouts carry fewer skills than the source repo).
71
+ const NON_SKILL_TOKENS = new Set(['loop', 'fast', 'config', 'clear', 'help']);
72
+
73
+ function transformTokens(body) {
74
+ const lines = body.split('\n');
75
+ let inFence = false;
76
+ const out = lines.map((line) => {
77
+ if (/^```/.test(line.trim())) {
78
+ inFence = !inFence;
79
+ return line;
80
+ }
81
+ if (inFence) return line;
82
+ return line.replace(/`\/([a-z][a-z0-9-]*)((?: [^`]*)?)`/g, (match, name, args) =>
83
+ NON_SKILL_TOKENS.has(name) ? match : `\`$${name}${args}\``
84
+ );
85
+ });
86
+ return out.join('\n');
87
+ }
88
+
89
+ // ── hand-authored Codex-native section bodies ────────────────────────────
90
+ // Each is the option-b translation of the matching CLAUDE.md section. The
91
+ // manifest records the source hash so drift is caught by --check.
92
+
93
+ const INTRO_BODY = `You are a collaborator inside Egregore — a shared intelligence layer for organizations using AI coding agents. You operate through Git-based shared memory, Egregore skills, and conventions that accumulate knowledge across sessions and people. You are not a tool. You are a participant.
94
+
95
+ This block is the Codex-native Egregore behavioral spec, rendered from CLAUDE.md (the source of truth for egregore behavior) by \`bin/codex-render-spec.mjs\`. The per-section adaptation manifest is \`.codex/spec-manifest.json\`. The thin agent protocol above this block applies to every shell-capable agent; this block is the full behavioral contract for Codex sessions.`;
96
+
97
+ const ON_LAUNCH_BODY = `The \`egregore\` launcher renders the Egregore startup card (identity, handoffs, team activity) 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\`.`;
98
+
99
+ const AFTER_GREETING_BODY = `**Mandatory behavioral rule.** When the user describes work, your **first action** — before reading files, exploring code, or anything else — is to get onto a working branch:
100
+
101
+ 1. Derive a topic slug from what the user said (kebab-case, 2–4 words)
102
+ 2. Run \`bin/agent.sh branch --topic "<topic>"\` — it creates a work branch from \`origin/develop\` in a task worktree (\`dev/{author}/{slug}\`, or \`feature/{slug}\` / \`bugfix/{slug}\` when the topic reads as a feature or fix) and prints the worktree path. Continue all file work from that printed path.
103
+ 3. Confirm: \`On {branch} (worktree).\`
104
+
105
+ **Fallback:** If \`bin/agent.sh branch\` fails, use \`git checkout -b dev/{author}/{slug} origin/develop\`.
106
+
107
+ 4. Update graph (fire-and-forget): \`bash bin/graph-op.sh set-topic "$(cat .egregore-session-id 2>/dev/null)" "topic from slug" "dev/author/slug" 2>/dev/null &\`
108
+
109
+ ### Handoff claiming
110
+
111
+ If \`addressed_to_user\` handoffs exist and the user is picking one up, create the IMPLEMENTS link after branch creation:
112
+ \`\`\`bash
113
+ bash bin/graph-op.sh claim-handoff "$SESSION_ID" "$HANDOFF_SESSION_ID" 2>/dev/null &
114
+ \`\`\`
115
+
116
+ **Auto-checkout repos from handoff**: After claiming, check the \`addressed_rich\` context for \`repoState\`. If the handoff includes repo state (non-empty \`repoState\` array), check out the handoff's branches in each managed repo:
117
+
118
+ \`\`\`bash
119
+ PARENT_DIR="$(cd .. && pwd)"
120
+ # For each entry in repoState:
121
+ REPO_DIR="$PARENT_DIR/$REPO_NAME"
122
+ if [ -d "$REPO_DIR/.git" ] || [ -f "$REPO_DIR/.git" ]; then
123
+ git -C "$REPO_DIR" fetch origin "$BRANCH" --quiet 2>/dev/null
124
+ git -C "$REPO_DIR" checkout "$BRANCH" 2>/dev/null || \\
125
+ git -C "$REPO_DIR" checkout -b "$BRANCH" "origin/$BRANCH" 2>/dev/null
126
+ fi
127
+ \`\`\`
128
+
129
+ Report results: \`✓ Checked out {branch} in {repo1}, {repo2}\`. If a branch no longer exists (PR was merged): \`◐ {repo}: PR #{N} merged — on {base}\`. If \`repoState\` is absent or empty, skip auto-checkout silently.
130
+
131
+ **Exceptions** — skip branching when:
132
+ - The user explicitly created or named a branch themselves
133
+ - Already on a working branch AND the user's intent continues the current branch's topic
134
+
135
+ **Topic pivot while on a working branch:** If the user describes work **unrelated** to the current branch's topic, treat it as a new topic and create a new branch (\`bin/agent.sh branch --topic "<new topic>"\`). Do NOT mix unrelated work on one branch.
136
+
137
+ If on develop after two messages, create a branch immediately from whatever context you have.
138
+
139
+ ### Branch-guard protocol
140
+
141
+ 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:
142
+
143
+ - **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.
144
+ - **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):
145
+
146
+ \`\`\`text
147
+ Need a working branch first. Topic?
148
+ 1. <suggested slug from context>
149
+ 2. <alternative slug>
150
+ 3. Other: (name it)
151
+ \`\`\`
152
+
153
+ Wait for the reply, then branch.
154
+
155
+ If this Codex build does not support hooks, follow the same discipline as a standing instruction: never write or commit on develop/main/master.
156
+
157
+ ### Onboarding exception
158
+
159
+ If the startup card output contains \`onboarding_needed\`, invoke the \`$onboarding\` skill instead of greeting.`;
160
+
161
+ const COMMAND_AWARENESS_PREPEND = `Codex reserves leading \`/\` for built-ins, so Egregore workflows are **skills**, not slash commands. Invoke them with the matching \`$name\` skill token or from natural language intent ("show activity", "make a handoff"). Hand-written native Codex skills: \`$activity\`, \`$handoff\`, \`$wrap\`, \`$announce\`, \`$harvest\`, \`$the-spiral\`, \`$dashboard\`, \`$deep-reflect\`, \`$quest\`, \`$invite\`, \`$ask\`, \`$save\`. Every other workflow has a generated adapter skill of the same name. \`$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.`;
162
+
163
+ const SOCRATIC_BODY = `**Triggers**: "ask me questions", "question me", "help me think through", or any request to be questioned.
164
+
165
+ Codex has no structured question tool — render each batch as compact numbered questions in plain text, each with 2–4 lettered options plus an \`Other:\` line, then STOP and wait for the user's answers. Derive 2–4 context-specific questions per batch. Iteratively deepen based on answers. Converge toward decisions. After 4–5 rounds, synthesize and propose next steps. Route insights to \`$reflect\`.
166
+
167
+ **Rules:** Max 4 questions per batch. When choices aren't mutually exclusive, say "pick any that apply".`;
168
+
169
+ const ISOLATION_BODY = `Sessions are confined to this project + memory + managed repos. On Codex this boundary is a standing instruction, not an enforced hook — hold it yourself.
170
+
171
+ - **Never modify \`~/.egregore/instances.json\`** — managed by session-start.sh
172
+ - **Never access another instance's files** — refuse even if asked
173
+ - When a request points outside the boundary, do not improvise a workaround. Ask in plain text with numbered options:
174
+
175
+ \`\`\`text
176
+ That path is outside this Egregore's boundary. How should we proceed?
177
+ 1. Paste the contents inline
178
+ 2. Move the file into the repo and point me at the new path
179
+ 3. Cancel
180
+ \`\`\`
181
+
182
+ See DEVELOPMENT.md §3 for boundary details.`;
183
+
184
+ // ── per-section rules ────────────────────────────────────────────────────
185
+ const RULES = {
186
+ '(intro)': {
187
+ action: 'replace',
188
+ body: INTRO_BODY,
189
+ note: 'Reframed for Codex: names AGENTS.md as the delivery surface and the manifest; "organizations using Claude Code" generalized.',
190
+ },
191
+ 'Identity & Upstream': {
192
+ action: 'keep',
193
+ note: 'Kept; /update, /contribute, /save tokens rendered as $skill tokens.',
194
+ },
195
+ 'On Launch — MANDATORY FIRST ACTION': {
196
+ action: 'replace',
197
+ body: ON_LAUNCH_BODY,
198
+ note: 'Claude SessionStart hook does not run on Codex; the egregore launcher renders the card via bin/codex-session-start.sh before the session. Greeting-replay instruction dropped (card is already on screen).',
199
+ },
200
+ 'After Greeting — BRANCH ON FIRST RESPONSE': {
201
+ action: 'replace',
202
+ body: AFTER_GREETING_BODY,
203
+ note: 'EnterWorktree -> bin/agent.sh branch with git checkout fallback; AskUserQuestion -> numbered options in plain text; branch-guard mapped to .codex/hooks/branch-guard.js with instruction fallback; plan-mode note dropped (no plan mode on Codex); /branch + /onboarding -> skill tokens. Handoff claiming and repoState auto-checkout kept verbatim (pure git).',
204
+ },
205
+ 'Config Files': { action: 'keep', note: 'Kept verbatim — shell facts, runtime-neutral.' },
206
+ 'Knowledge Graph': { action: 'keep', note: 'Kept verbatim — bin/graph.sh is runtime-neutral.' },
207
+ 'Notifications': { action: 'keep', note: 'Kept verbatim — bin/notify.sh is runtime-neutral.' },
208
+ 'Onboarding': { action: 'keep', note: 'Kept; /onboarding rendered as $onboarding.' },
209
+ 'Transparency Beat': { action: 'keep', note: 'Kept verbatim.' },
210
+ 'Memory': { action: 'keep', note: 'Kept verbatim.' },
211
+ 'Git Workflow': {
212
+ action: 'keep',
213
+ note: 'Kept; slash tokens rendered as $skill tokens. Branch diagram unchanged (code fence).',
214
+ },
215
+ 'Working Conventions': { action: 'keep', note: 'Kept verbatim.' },
216
+ 'Command Awareness': {
217
+ action: 'keep',
218
+ prepend: COMMAND_AWARENESS_PREPEND,
219
+ note: 'Prepended the Codex command surface (skill tokens, native vs adapter split, $save abstraction); disambiguation map kept with /x -> $x token rendering. Tokens with no Codex skill (e.g. /loop, a Claude Code harness feature) are left as-is.',
220
+ },
221
+ 'Socratic Questioning (MANDATORY)': {
222
+ action: 'replace',
223
+ body: SOCRATIC_BODY,
224
+ note: 'AskUserQuestion -> numbered plain-text question batches with explicit stop-and-wait; multiSelect -> "pick any that apply".',
225
+ },
226
+ 'Telemetry': { action: 'keep', note: 'Kept verbatim — bin/telemetry.sh is runtime-neutral.' },
227
+ 'Mode': { action: 'keep', note: 'Kept; /env and /checkup rendered as $skill tokens.' },
228
+ 'Environment Isolation': {
229
+ action: 'replace',
230
+ body: ISOLATION_BODY,
231
+ note: 'boundary-check is a Claude PreToolUse hook with no .codex equivalent — behavior stated as a standing instruction; AskUserQuestion remediation -> numbered options.',
232
+ },
233
+ };
234
+
235
+ // ── render ───────────────────────────────────────────────────────────────
236
+ function render() {
237
+ const claudeMd = fs.readFileSync(CLAUDE_MD, 'utf-8');
238
+ const sections = splitSections(claudeMd);
239
+
240
+ const manifest = [];
241
+ const parts = [];
242
+
243
+ for (const section of sections) {
244
+ const rule = RULES[section.heading];
245
+ if (!rule) {
246
+ console.error(`codex-render-spec: no rule for CLAUDE.md section "${section.heading}" — add one to RULES in bin/codex-render-spec.mjs`);
247
+ process.exit(1);
248
+ }
249
+ const sourceHash = sha(section.body);
250
+ manifest.push({ section: section.heading, action: rule.action, sourceHash, note: rule.note });
251
+
252
+ const heading = section.heading === '(intro)' ? '## Egregore on Codex' : `## ${section.heading}`;
253
+ let body;
254
+ if (rule.action === 'replace') {
255
+ body = rule.body;
256
+ } else {
257
+ body = transformTokens(section.body);
258
+ if (rule.prepend) body = `${rule.prepend}\n\n${body}`;
259
+ }
260
+ parts.push(`${heading}\n\n${body}`);
261
+ }
262
+
263
+ for (const heading of Object.keys(RULES)) {
264
+ if (!sections.some((s) => s.heading === heading)) {
265
+ console.error(`codex-render-spec: RULES contains "${heading}" but CLAUDE.md has no such section — remove or update the rule`);
266
+ process.exit(1);
267
+ }
268
+ }
269
+
270
+ const block = `${BEGIN}\n\n${parts.join('\n\n---\n\n')}\n\n${END}`;
271
+ return { block, manifest };
272
+ }
273
+
274
+ function spliceAgentsMd(agentsMd, block) {
275
+ const beginIdx = agentsMd.indexOf(BEGIN);
276
+ const endIdx = agentsMd.indexOf(END);
277
+ if (beginIdx !== -1 && endIdx !== -1) {
278
+ return agentsMd.slice(0, beginIdx) + block + agentsMd.slice(endIdx + END.length);
279
+ }
280
+ return `${agentsMd.replace(/\n+$/, '')}\n\n${block}\n`;
281
+ }
282
+
283
+ const check = process.argv.includes('--check');
284
+ const { block, manifest } = render();
285
+ const manifestJson = `${JSON.stringify({ generatedBy: 'bin/codex-render-spec.mjs', source: 'CLAUDE.md', sections: manifest }, null, 2)}\n`;
286
+
287
+ const currentAgents = fs.existsSync(AGENTS_MD) ? fs.readFileSync(AGENTS_MD, 'utf-8') : '';
288
+ const nextAgents = spliceAgentsMd(currentAgents, block);
289
+
290
+ const CODEX_DOC_BUDGET = 32 * 1024;
291
+ if (Buffer.byteLength(nextAgents, 'utf-8') > CODEX_DOC_BUDGET) {
292
+ console.error(`codex-render-spec: AGENTS.md would be ${Buffer.byteLength(nextAgents, 'utf-8')} bytes — over Codex's ${CODEX_DOC_BUDGET}-byte project-doc budget. Trim CLAUDE.md or tighten translations.`);
293
+ process.exit(1);
294
+ }
295
+
296
+ if (check) {
297
+ const currentManifest = fs.existsSync(MANIFEST) ? fs.readFileSync(MANIFEST, 'utf-8') : '';
298
+ const stale = nextAgents !== currentAgents || manifestJson !== currentManifest;
299
+ if (stale) {
300
+ console.error('codex spec out of date — run: node bin/codex-render-spec.mjs');
301
+ process.exit(1);
302
+ }
303
+ console.log('codex spec up to date');
304
+ } else {
305
+ fs.writeFileSync(AGENTS_MD, nextAgents);
306
+ fs.writeFileSync(MANIFEST, manifestJson);
307
+ console.log(`codex spec rendered: AGENTS.md (${Buffer.byteLength(nextAgents, 'utf-8')} bytes), .codex/spec-manifest.json (${manifest.length} sections)`);
308
+ }
@@ -0,0 +1,238 @@
1
+ #!/bin/bash
2
+ set -o pipefail
3
+
4
+ # Codex does not run Claude Code SessionStart hooks. This is the runtime-neutral
5
+ # startup card: it reuses the same identity, git sync, and context-gathering
6
+ # libraries as Claude's SessionStart, then renders a terminal greeting before
7
+ # Codex itself starts.
8
+
9
+ FRAMEWORK_VERSION="3"
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ cd "$SCRIPT_DIR" || exit 1
12
+
13
+ IS_WORKTREE="false"
14
+ MAIN_PROJECT_DIR="$SCRIPT_DIR"
15
+ if [ -f "$SCRIPT_DIR/.git" ]; then
16
+ IS_WORKTREE="true"
17
+ WT_GITDIR=$(sed 's/^gitdir: //' "$SCRIPT_DIR/.git" 2>/dev/null)
18
+ MAIN_PROJECT_DIR=$(cd "$WT_GITDIR/../../.." 2>/dev/null && pwd)
19
+ fi
20
+
21
+ if [ -f "$SCRIPT_DIR/bin/lib/worktree-links.sh" ]; then
22
+ source "$SCRIPT_DIR/bin/lib/worktree-links.sh" >/dev/null 2>/dev/null || true
23
+ egregore_link_shared_state "$SCRIPT_DIR" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
24
+ fi
25
+
26
+ HEALTH_GITHUB="skip"
27
+ HEALTH_GIT="skip"
28
+ HEALTH_APIKEY="skip"
29
+ HEALTH_GRAPH="skip"
30
+ HEALTH_TELEGRAM="skip"
31
+
32
+ STATE_FILE="$SCRIPT_DIR/.egregore-state.json"
33
+ ENV_FILE="$SCRIPT_DIR/.env"
34
+ CONFIG="$SCRIPT_DIR/egregore.json"
35
+ DATE=$(date +%Y-%m-%d)
36
+
37
+ source "$SCRIPT_DIR/bin/lib/config.sh"
38
+ source "$SCRIPT_DIR/bin/lib/hash.sh"
39
+ source "$SCRIPT_DIR/bin/lib/time.sh"
40
+ source "$SCRIPT_DIR/bin/lib/identity.sh" >/dev/null 2>/dev/null
41
+
42
+ LOCAL_MODE="false"
43
+ [ "$(_detect_mode)" = "local" ] && LOCAL_MODE="true"
44
+
45
+ if [ "$LOCAL_MODE" != "true" ]; then
46
+ if [ -f "$ENV_FILE" ] && grep -q '^EGREGORE_API_KEY=.' "$ENV_FILE" 2>/dev/null; then
47
+ HEALTH_APIKEY="ok"
48
+ else
49
+ HEALTH_APIKEY="fail"
50
+ fi
51
+ fi
52
+
53
+ source "$SCRIPT_DIR/bin/lib/git-sync.sh" >/dev/null 2>/dev/null
54
+ source "$SCRIPT_DIR/bin/lib/context.sh" >/dev/null 2>/dev/null
55
+ trap 'rm -rf "${CTX_DIR:-}"' EXIT
56
+
57
+ read_json() {
58
+ local file="$1"
59
+ local expr="$2"
60
+ local fallback="${3:-}"
61
+ if [ -f "$file" ] && command -v jq >/dev/null 2>&1; then
62
+ jq -r "$expr // empty" "$file" 2>/dev/null | head -1
63
+ else
64
+ printf '%s\n' "$fallback"
65
+ fi
66
+ }
67
+
68
+ shorten() {
69
+ local value="$1"
70
+ local max="$2"
71
+ if command -v jq >/dev/null 2>&1; then
72
+ jq -nr --arg value "$value" --argjson max "$max" '
73
+ $value
74
+ | if length > $max then .[0:($max - 3)] + "..." else . end
75
+ '
76
+ return
77
+ fi
78
+ if [ "${#value}" -gt "$max" ]; then
79
+ printf '%s...\n' "${value:0:$((max - 3))}"
80
+ else
81
+ printf '%s\n' "$value"
82
+ fi
83
+ }
84
+
85
+ ORG_NAME="$(read_json "$CONFIG" '.org_name' '')"
86
+ [ -z "$ORG_NAME" ] && ORG_NAME="$(read_json "$CONFIG" '.slug' 'Egregore')"
87
+ GITHUB_ORG="$(read_json "$CONFIG" '.github_org' '')"
88
+ REPO_NAME="$(read_json "$CONFIG" '.repo_name' 'egregore')"
89
+ SLUG="$(read_json "$CONFIG" '.slug' '')"
90
+ DISPLAY_NAME="$(read_json "$STATE_FILE" '.display_name' '')"
91
+ [ -z "$DISPLAY_NAME" ] && DISPLAY_NAME="${DISPLAY_NAME_STATE:-$AUTHOR}"
92
+ ONBOARDING_COMPLETE="$(read_json "$STATE_FILE" '.onboarding_complete' '')"
93
+ [ -z "$ONBOARDING_COMPLETE" ] && ONBOARDING_COMPLETE="unknown"
94
+
95
+ TEAM_DATA="$(cat "$CTX_DIR/team" 2>/dev/null || echo "[]")"
96
+ ADDRESSED_RICH="$(cat "$CTX_DIR/addressed_rich" 2>/dev/null || echo "[]")"
97
+ PENDING_Q="$(cat "$CTX_DIR/pending_questions" 2>/dev/null || echo "[]")"
98
+ LAST_ACTIVITY="$(cat "$CTX_DIR/activity" 2>/dev/null || true)"
99
+ ADDRESSED_COUNT="$(echo "$ADDRESSED_RICH" | jq 'length' 2>/dev/null || echo "0")"
100
+ PENDING_Q_COUNT="$(echo "$PENDING_Q" | jq 'length' 2>/dev/null || echo "0")"
101
+
102
+ render_for_you_summary() {
103
+ if [ "$PENDING_Q_COUNT" -gt 0 ] 2>/dev/null; then
104
+ local senders noun
105
+ senders="$(echo "$PENDING_Q" | jq -r '[.[].from] | unique | join(", ")' 2>/dev/null)"
106
+ [ "$PENDING_Q_COUNT" = "1" ] && noun="question" || noun="questions"
107
+ if [ -n "$senders" ]; then
108
+ printf ' ◐ %s pending %s from %s - bin/agent.sh answer\n' "$PENDING_Q_COUNT" "$noun" "$senders"
109
+ else
110
+ printf ' ◐ %s pending %s - bin/agent.sh answer\n' "$PENDING_Q_COUNT" "$noun"
111
+ fi
112
+ elif [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
113
+ printf ' ◇ %s handoffs for %s - bin/agent.sh activity --for %s\n' "$ADDRESSED_COUNT" "$AUTHOR" "$AUTHOR"
114
+ else
115
+ printf ' ◇ nothing pending for %s\n' "$AUTHOR"
116
+ fi
117
+ }
118
+
119
+ render_handoff_rows() {
120
+ if [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
121
+ echo "$ADDRESSED_RICH" | jq -r '.[] | [.author, .date, .topic] | @tsv' 2>/dev/null | head -4 | while IFS=$'\t' read -r author date topic; do
122
+ [ -z "$author" ] && continue
123
+ printf ' ○ %-10s%-12s%s\n' "$(shorten "$author" 9)" "$date" "$(shorten "$topic" 40)"
124
+ done
125
+ elif [ -n "$LAST_ACTIVITY" ]; then
126
+ local last_when last_msg
127
+ last_when="$(printf '%s\n' "$LAST_ACTIVITY" | cut -d'|' -f1 | sed 's/ hours\{0,1\} ago/h ago/; s/ days\{0,1\} ago/d ago/; s/ minutes\{0,1\} ago/m ago/')"
128
+ last_msg="$(printf '%s\n' "$LAST_ACTIVITY" | cut -d'|' -f2-)"
129
+ printf ' ◇ %-10s%-12s%s\n' "you" "$last_when" "$(shorten "$last_msg" 40)"
130
+ fi
131
+ }
132
+
133
+ render_team_rows() {
134
+ local now_render online_threshold
135
+ now_render=$(date +%s)
136
+ online_threshold=7200
137
+ echo "$TEAM_DATA" | jq -r '.[] | [.name, .last_seen_sort, .last_seen, (.branches | join(", "))] | @tsv' 2>/dev/null | head -4 | while IFS=$'\t' read -r name epoch seen branches; do
138
+ [ -z "$name" ] && continue
139
+ local dot
140
+ if [ "$epoch" -gt 0 ] 2>/dev/null && [ $((now_render - epoch)) -lt "$online_threshold" ] 2>/dev/null; then
141
+ dot="●"
142
+ else
143
+ dot="○"
144
+ fi
145
+ printf ' %s %-10s%-12s%s\n' "$dot" "$(shorten "$name" 9)" "$seen" "$(shorten "$branches" 40)"
146
+ done
147
+ }
148
+
149
+ banner() {
150
+ cat <<'EOF'
151
+
152
+ ███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
153
+ ██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝ ██╔═══██╗██╔══██╗██╔════╝
154
+ █████╗ ██║ ███╗██████╔╝█████╗ ██║ ███╗██║ ██║██████╔╝█████╗
155
+ ██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██║ ██║██║ ██║██╔══██╗██╔══╝
156
+ ███████╗╚██████╔╝██║ ██║███████╗╚██████╔╝╚██████╔╝██║ ██║███████╗
157
+ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝
158
+
159
+ EOF
160
+ }
161
+
162
+ render_card() {
163
+ local separator identity_left identity_right line_width pad footer_left footer_right board_url has_failure failed_services
164
+ separator=" ..................................................................."
165
+ identity_left=" ${GITHUB_ORG:-$ORG_NAME}/${REPO_NAME}"
166
+ identity_right="${DISPLAY_NAME} · ${BRANCH:-?}"
167
+ line_width=67
168
+ pad=$((line_width - ${#identity_left} - ${#identity_right}))
169
+ [ "$pad" -lt 1 ] && pad=1
170
+
171
+ banner
172
+ printf '%s%*s%s\n' "$identity_left" "$pad" "" "$identity_right"
173
+ printf '%s\n' "$separator"
174
+ printf ' ◦ for you\n'
175
+ render_for_you_summary
176
+ render_handoff_rows
177
+ printf '\n'
178
+ printf ' ◦ around\n'
179
+ render_team_rows
180
+ printf '%s\n' "$separator"
181
+
182
+ has_failure="false"
183
+ failed_services=""
184
+ for pair in "github:$HEALTH_GITHUB" "git:$HEALTH_GIT" "api-key:$HEALTH_APIKEY" "graph:$HEALTH_GRAPH" "telegram:$HEALTH_TELEGRAM"; do
185
+ svc="${pair%%:*}"
186
+ svc_status="${pair#*:}"
187
+ if [ "$svc_status" = "fail" ]; then
188
+ has_failure="true"
189
+ failed_services="${failed_services} ${svc} ✗"
190
+ fi
191
+ done
192
+
193
+ if [ "$has_failure" = "true" ]; then
194
+ printf ' ⚠%s\n' "$failed_services"
195
+ else
196
+ footer_left=" ✓ ready"
197
+ if [ "${MEMORY_SYNCED:-false}" = "true" ]; then
198
+ footer_right="◆ memory synced"
199
+ else
200
+ footer_right="◆ memory not synced"
201
+ fi
202
+ pad=$((line_width - ${#footer_left} - ${#footer_right}))
203
+ [ "$pad" -lt 1 ] && pad=1
204
+ printf '%s%*s%s\n' "$footer_left" "$pad" "" "$footer_right"
205
+ fi
206
+
207
+ if [ -n "$SLUG" ]; then
208
+ board_url="https://egregore.xyz/view/${SLUG}/board"
209
+ printf ' ◆ %s\n' "$board_url"
210
+ fi
211
+ printf '\nWhat are you working on?\n'
212
+ }
213
+
214
+ case "${1:---card}" in
215
+ --card|card)
216
+ render_card
217
+ ;;
218
+ --prompt|prompt)
219
+ cat <<EOF
220
+ You are Codex running inside Egregore for ${ORG_NAME} as ${AUTHOR}.
221
+ The launcher already rendered the Egregore startup card using the shared identity, git-sync, graph, and filesystem context pipeline.
222
+ This session is Trusted Codex: full shell/network access is enabled and approval prompts are disabled by the launcher.
223
+ Do not rerun startup checks or narrate startup unless the user asks.
224
+ Follow AGENTS.md before code changes.
225
+ After the user names what they are working on, use bin/agent.sh branch --topic "<work topic>" if you are not already in an appropriate task worktree, then continue file work from the printed worktree path.
226
+ Use bin/agent.sh to update shared activity: handoff, ask, and answer write to memory/ and push when possible.
227
+ Use bin/agent.sh handoff for internal team session-handoffs. Portable external capsules are emissaries; use egregore-emissary when installed, not the deprecated egregore-handoff CLI.
228
+ Codex reserves leading slash commands for built-ins, so custom Egregore /activity-style commands will not reach the agent. Egregore native Codex skills are: \$activity, \$handoff, \$wrap, \$announce, \$harvest, \$the-spiral, \$dashboard, \$deep-reflect, \$quest, \$invite, \$ask, and \$save. Natural-language requests such as "show activity" or "make a handoff" work too. Remaining mirrored workflows are adapter skills for long-tail coverage.
229
+ \$save is the user-facing abstraction for committing, pushing, opening or reusing pull requests, and syncing memory; do not make users manage the git workflow by hand.
230
+ Egregore Codex workflows are skill invocations, not Claude Code slash commands. Do not rely on Claude Code-only command machinery.
231
+ If onboarding_complete is false, use the memory protocol directly and make sure memory/people/${AUTHOR}.md exists before the first handoff.
232
+ EOF
233
+ ;;
234
+ *)
235
+ echo "usage: bin/codex-session-start.sh [--card|--prompt]" >&2
236
+ exit 2
237
+ ;;
238
+ esac