create-egregore 0.19.5 → 0.19.7

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 (130) hide show
  1. package/assets/egregore-launcher.js +411 -25
  2. package/bin/cli.js +34 -20
  3. package/lib/local.js +21 -5
  4. package/lib/pi-runtime.js +186 -0
  5. package/lib/setup.js +56 -11
  6. package/lib/ui.js +1 -1
  7. package/package.json +7 -5
  8. package/runtime/codex/.codex/spec-manifest.json +1 -1
  9. package/runtime/codex/AGENTS.md +7 -2
  10. package/runtime/codex/bin/codex-render-spec.mjs +6 -1
  11. package/runtime/codex/bin/codex-session-start.sh +5 -59
  12. package/runtime/codex/bin/codex-skill-render.mjs +7 -5
  13. package/runtime/codex/bin/lib/context.sh +8 -1
  14. package/runtime/codex/bin/lib/greeting.sh +12 -108
  15. package/runtime/codex/bin/lib/metrics.sh +374 -0
  16. package/runtime/codex/bin/publish-artifact.sh +14 -0
  17. package/runtime/codex/bin/settings.sh +274 -0
  18. package/runtime/codex/manifest.json +2 -0
  19. package/runtime/pi/.codex/config.toml +15 -0
  20. package/runtime/pi/.codex/hooks/branch-guard.js +242 -0
  21. package/runtime/pi/.codex/hooks/search-hint.js +65 -0
  22. package/runtime/pi/.codex/hooks.json +30 -0
  23. package/runtime/pi/.codex/skills/activity/SKILL.md +70 -0
  24. package/runtime/pi/.codex/skills/add/SKILL.md +25 -0
  25. package/runtime/pi/.codex/skills/announce/SKILL.md +54 -0
  26. package/runtime/pi/.codex/skills/answer/SKILL.md +25 -0
  27. package/runtime/pi/.codex/skills/archive/SKILL.md +41 -0
  28. package/runtime/pi/.codex/skills/ask/SKILL.md +59 -0
  29. package/runtime/pi/.codex/skills/branch/SKILL.md +25 -0
  30. package/runtime/pi/.codex/skills/checkup/SKILL.md +41 -0
  31. package/runtime/pi/.codex/skills/commit/SKILL.md +25 -0
  32. package/runtime/pi/.codex/skills/contribute/SKILL.md +25 -0
  33. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +25 -0
  34. package/runtime/pi/.codex/skills/dashboard/SKILL.md +59 -0
  35. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +74 -0
  36. package/runtime/pi/.codex/skills/delete-user/SKILL.md +25 -0
  37. package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +25 -0
  38. package/runtime/pi/.codex/skills/emissary/SKILL.md +41 -0
  39. package/runtime/pi/.codex/skills/env/SKILL.md +25 -0
  40. package/runtime/pi/.codex/skills/handoff/SKILL.md +67 -0
  41. package/runtime/pi/.codex/skills/harvest/SKILL.md +107 -0
  42. package/runtime/pi/.codex/skills/ingest/SKILL.md +25 -0
  43. package/runtime/pi/.codex/skills/ingest-google/SKILL.md +25 -0
  44. package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  45. package/runtime/pi/.codex/skills/invite/SKILL.md +40 -0
  46. package/runtime/pi/.codex/skills/issue/SKILL.md +41 -0
  47. package/runtime/pi/.codex/skills/me/SKILL.md +25 -0
  48. package/runtime/pi/.codex/skills/meeting/SKILL.md +41 -0
  49. package/runtime/pi/.codex/skills/minion/SKILL.md +25 -0
  50. package/runtime/pi/.codex/skills/note/SKILL.md +25 -0
  51. package/runtime/pi/.codex/skills/onboarding/SKILL.md +41 -0
  52. package/runtime/pi/.codex/skills/pr/SKILL.md +25 -0
  53. package/runtime/pi/.codex/skills/project/SKILL.md +41 -0
  54. package/runtime/pi/.codex/skills/pull/SKILL.md +25 -0
  55. package/runtime/pi/.codex/skills/push/SKILL.md +25 -0
  56. package/runtime/pi/.codex/skills/quest/SKILL.md +77 -0
  57. package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +25 -0
  58. package/runtime/pi/.codex/skills/reflect/SKILL.md +41 -0
  59. package/runtime/pi/.codex/skills/review-pr/SKILL.md +41 -0
  60. package/runtime/pi/.codex/skills/save/SKILL.md +62 -0
  61. package/runtime/pi/.codex/skills/scroll/SKILL.md +168 -0
  62. package/runtime/pi/.codex/skills/scroll/agents/openai.yaml +4 -0
  63. package/runtime/pi/.codex/skills/scroll/assets/shell.html +542 -0
  64. package/runtime/pi/.codex/skills/search/SKILL.md +56 -0
  65. package/runtime/pi/.codex/skills/session-view/SKILL.md +25 -0
  66. package/runtime/pi/.codex/skills/setup/SKILL.md +25 -0
  67. package/runtime/pi/.codex/skills/summon/SKILL.md +41 -0
  68. package/runtime/pi/.codex/skills/sync-repos/SKILL.md +25 -0
  69. package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +25 -0
  70. package/runtime/pi/.codex/skills/telemetry/SKILL.md +25 -0
  71. package/runtime/pi/.codex/skills/test/SKILL.md +41 -0
  72. package/runtime/pi/.codex/skills/the-spiral/SKILL.md +84 -0
  73. package/runtime/pi/.codex/skills/thread/SKILL.md +25 -0
  74. package/runtime/pi/.codex/skills/todo/SKILL.md +41 -0
  75. package/runtime/pi/.codex/skills/tui-design/SKILL.md +41 -0
  76. package/runtime/pi/.codex/skills/tutorial/SKILL.md +41 -0
  77. package/runtime/pi/.codex/skills/update/SKILL.md +25 -0
  78. package/runtime/pi/.codex/skills/view/SKILL.md +156 -0
  79. package/runtime/pi/.codex/skills/view/agents/openai.yaml +4 -0
  80. package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +221 -0
  81. package/runtime/pi/.codex/skills/visual-explain/SKILL.md +41 -0
  82. package/runtime/pi/.codex/skills/wrap/SKILL.md +55 -0
  83. package/runtime/pi/.codex/spec-manifest.json +114 -0
  84. package/runtime/pi/.pi/APPEND_SYSTEM.md +255 -0
  85. package/runtime/pi/.pi/extensions/egregore.ts +324 -0
  86. package/runtime/pi/.pi/settings.json +6 -0
  87. package/runtime/pi/.pi/spec-manifest.json +12 -0
  88. package/runtime/pi/AGENTS.md +350 -0
  89. package/runtime/pi/bin/activity-data.sh +242 -0
  90. package/runtime/pi/bin/agent.sh +677 -0
  91. package/runtime/pi/bin/codex-install-skills.sh +78 -0
  92. package/runtime/pi/bin/codex-render-spec.mjs +394 -0
  93. package/runtime/pi/bin/codex-session-start.sh +188 -0
  94. package/runtime/pi/bin/codex-skill-render.mjs +323 -0
  95. package/runtime/pi/bin/codex-sync-skills.sh +227 -0
  96. package/runtime/pi/bin/dashboard-data.sh +178 -0
  97. package/runtime/pi/bin/graph-batch.sh +60 -0
  98. package/runtime/pi/bin/graph-op.sh +431 -0
  99. package/runtime/pi/bin/graph-wal.sh +228 -0
  100. package/runtime/pi/bin/graph.sh +177 -0
  101. package/runtime/pi/bin/handoff-pr-backfill.sh +86 -0
  102. package/runtime/pi/bin/handoff-run.sh +581 -0
  103. package/runtime/pi/bin/handoff-save-egregore.sh +136 -0
  104. package/runtime/pi/bin/index-handoff.sh +312 -0
  105. package/runtime/pi/bin/lib/artifact-id.sh +46 -0
  106. package/runtime/pi/bin/lib/config.sh +71 -0
  107. package/runtime/pi/bin/lib/context.sh +542 -0
  108. package/runtime/pi/bin/lib/dashboard-artifact.sh +142 -0
  109. package/runtime/pi/bin/lib/ensure-develop.sh +183 -0
  110. package/runtime/pi/bin/lib/git-sync.sh +288 -0
  111. package/runtime/pi/bin/lib/greeting.sh +340 -0
  112. package/runtime/pi/bin/lib/hash.sh +13 -0
  113. package/runtime/pi/bin/lib/identity.sh +174 -0
  114. package/runtime/pi/bin/lib/metrics.sh +374 -0
  115. package/runtime/pi/bin/lib/permissions.sh +106 -0
  116. package/runtime/pi/bin/lib/time.sh +56 -0
  117. package/runtime/pi/bin/lib/worktree-links.sh +54 -0
  118. package/runtime/pi/bin/notify.sh +207 -0
  119. package/runtime/pi/bin/pi-branch-policy.mjs +15 -0
  120. package/runtime/pi/bin/pi-product-workflows.mjs +324 -0
  121. package/runtime/pi/bin/pi-render-spec.mjs +114 -0
  122. package/runtime/pi/bin/pi-session-start.sh +24 -0
  123. package/runtime/pi/bin/publish-artifact.sh +201 -0
  124. package/runtime/pi/bin/publish-references.sh +144 -0
  125. package/runtime/pi/bin/repo-state.sh +99 -0
  126. package/runtime/pi/bin/telemetry.sh +391 -0
  127. package/runtime/pi/bin/worktree-create.sh +118 -0
  128. package/runtime/pi/bin/worktree-remove.sh +50 -0
  129. package/runtime/pi/bin/worktree.sh +198 -0
  130. package/runtime/pi/manifest.json +176 -0
@@ -0,0 +1,677 @@
1
+ #!/bin/bash
2
+ # Runtime-neutral Egregore agent bridge.
3
+ #
4
+ # This script exposes the Git-backed memory protocol without requiring
5
+ # Claude Code hooks or slash-command skills. Any agent runtime that can run
6
+ # shell commands inside an Egregore checkout can use it to sync memory,
7
+ # create handoffs, ask questions, answer questions, and inspect activity.
8
+ set -euo pipefail
9
+
10
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ MAIN_PROJECT_DIR="$SCRIPT_DIR"
12
+ if [ -f "$SCRIPT_DIR/.git" ]; then
13
+ WT_GITDIR=$(sed 's/^gitdir: //' "$SCRIPT_DIR/.git" 2>/dev/null || true)
14
+ [ -n "$WT_GITDIR" ] && MAIN_PROJECT_DIR=$(cd "$WT_GITDIR/../../.." 2>/dev/null && pwd || echo "$SCRIPT_DIR")
15
+ fi
16
+ if [ -f "$SCRIPT_DIR/bin/lib/worktree-links.sh" ]; then
17
+ source "$SCRIPT_DIR/bin/lib/worktree-links.sh" >/dev/null 2>/dev/null || true
18
+ egregore_link_shared_state "$SCRIPT_DIR" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
19
+ fi
20
+ CONFIG="$SCRIPT_DIR/egregore.json"
21
+ MEMORY_DIR="${EGREGORE_MEMORY_DIR:-$SCRIPT_DIR/memory}"
22
+
23
+ usage() {
24
+ cat <<'EOF'
25
+ Usage: agent.sh <command> [options]
26
+
27
+ Runtime-neutral commands:
28
+ protocol Print the portable agent protocol
29
+ sync Pull latest shared memory
30
+ activity [--for <person>] Show recent handoffs and questions
31
+ search "query" [-n N] [--fast|--semantic]
32
+ Hybrid recall over shared memory (qmd)
33
+ people List known people from memory/people
34
+ branch --topic TEXT Create or reuse a task branch/worktree
35
+ save [--message TEXT] Commit and push current repo + memory changes
36
+ wrap --topic T --summary TEXT Write a session wrap to memory/wraps
37
+ handoff --from A --to B --topic T [--body TEXT|--body-file PATH] [--no-push] [--no-publish] [--no-notify] [--json]
38
+ ask --from A --to B --topic T --question TEXT [--no-push]
39
+ [--harvest-id ID --harvest-session-id ID --turn N
40
+ --question-intent TEXT --context-mode blind|disclosed|comparative]
41
+ answer --from A --question PATH --body TEXT [--no-push]
42
+
43
+ The script uses the existing memory/ repository and keeps Claude Code
44
+ behavior unchanged.
45
+ EOF
46
+ }
47
+
48
+ die() {
49
+ echo "agent.sh: $*" >&2
50
+ exit 1
51
+ }
52
+
53
+ slugify() {
54
+ echo "$1" \
55
+ | tr '[:upper:]' '[:lower:]' \
56
+ | sed -E 's/[^a-z0-9]+/-/g' \
57
+ | sed -E 's/^-+|-+$//g' \
58
+ | cut -c1-60
59
+ }
60
+
61
+ # Reject control characters that would break YAML frontmatter when interpolated.
62
+ reject_newlines() {
63
+ case "$2" in
64
+ *$'\n'*|*$'\r'*) die "invalid $1: must not contain newlines" ;;
65
+ esac
66
+ }
67
+
68
+ # Emit a value as a JSON-encoded string (valid YAML), so colons, quotes,
69
+ # and other YAML metacharacters in user-supplied values cannot inject keys.
70
+ yaml_str() {
71
+ jq -n --arg v "$1" '$v'
72
+ }
73
+
74
+ ensure_memory() {
75
+ [ -d "$MEMORY_DIR" ] || die "memory directory not found: $MEMORY_DIR"
76
+ }
77
+
78
+ is_memory_git() {
79
+ git -C "$MEMORY_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1
80
+ }
81
+
82
+ memory_branch() {
83
+ git -C "$MEMORY_DIR" branch --show-current 2>/dev/null || echo "main"
84
+ }
85
+
86
+ sync_memory() {
87
+ ensure_memory
88
+ if is_memory_git && git -C "$MEMORY_DIR" remote get-url origin >/dev/null 2>&1; then
89
+ local branch
90
+ branch="$(memory_branch)"
91
+ [ -z "$branch" ] && branch="main"
92
+ git -C "$MEMORY_DIR" pull --rebase origin "$branch" --quiet 2>/dev/null || true
93
+ fi
94
+ }
95
+
96
+ save_memory() {
97
+ local message="$1"
98
+ shift
99
+ local no_push="${NO_PUSH:-0}"
100
+
101
+ ensure_memory
102
+ if ! is_memory_git; then
103
+ return 0
104
+ fi
105
+
106
+ git -C "$MEMORY_DIR" add "$@" >/dev/null 2>&1 || true
107
+ if ! git -C "$MEMORY_DIR" diff --cached --quiet 2>/dev/null; then
108
+ git -C "$MEMORY_DIR" commit -m "$message" --quiet 2>/dev/null || true
109
+ fi
110
+
111
+ [ "$no_push" = "1" ] && return 0
112
+ git -C "$MEMORY_DIR" remote get-url origin >/dev/null 2>&1 || return 0
113
+
114
+ local branch
115
+ branch="$(memory_branch)"
116
+ [ -z "$branch" ] && branch="main"
117
+ for _try in 1 2 3; do
118
+ if git -C "$MEMORY_DIR" pull --rebase origin "$branch" --quiet 2>/dev/null \
119
+ && git -C "$MEMORY_DIR" push origin "$branch" --quiet 2>/dev/null; then
120
+ return 0
121
+ fi
122
+ sleep 1
123
+ done
124
+ die "could not push memory after 3 attempts"
125
+ }
126
+
127
+ person_from_state() {
128
+ if [ -f "$SCRIPT_DIR/.egregore-state.json" ]; then
129
+ jq -r '.github_username // .name // empty' "$SCRIPT_DIR/.egregore-state.json" 2>/dev/null || true
130
+ fi
131
+ }
132
+
133
+ current_author() {
134
+ local author
135
+ author="$(person_from_state)"
136
+ [ -n "$author" ] || author="$(git -C "$SCRIPT_DIR" config user.name 2>/dev/null | awk '{print tolower($1)}')"
137
+ [ -n "$author" ] || author="agent"
138
+ echo "$author"
139
+ }
140
+
141
+ worktree_for_branch() {
142
+ local branch="$1"
143
+ git -C "$MAIN_PROJECT_DIR" worktree list --porcelain 2>/dev/null | awk -v ref="refs/heads/$branch" '
144
+ $1 == "worktree" { path = $2 }
145
+ $1 == "branch" && $2 == ref { print path; exit }
146
+ '
147
+ }
148
+
149
+ base_ref() {
150
+ git -C "$MAIN_PROJECT_DIR" fetch origin develop --quiet 2>/dev/null || true
151
+ if git -C "$MAIN_PROJECT_DIR" show-ref --verify --quiet refs/remotes/origin/develop 2>/dev/null; then
152
+ echo "origin/develop"
153
+ elif git -C "$MAIN_PROJECT_DIR" show-ref --verify --quiet refs/heads/develop 2>/dev/null; then
154
+ echo "develop"
155
+ else
156
+ echo "HEAD"
157
+ fi
158
+ }
159
+
160
+ cmd_protocol() {
161
+ cat <<'EOF'
162
+ Egregore portable agent protocol
163
+
164
+ 1. Work inside an Egregore checkout with a linked memory/ repository.
165
+ 2. Run `bin/agent.sh sync` before reading shared state.
166
+ 3. Read `memory/people/` for collaborators.
167
+ 4. Use `bin/agent.sh handoff` to leave structured context for another agent.
168
+ 5. Use `bin/agent.sh ask` and `bin/agent.sh answer` for asynchronous questions.
169
+ 6. Run `bin/agent.sh activity --for <person>` to see pending work.
170
+ 7. Run `bin/agent.sh search "<query>"` to recall shared memory (decisions, handoffs, knowledge) before answering questions about past work.
171
+ 8. Run `bin/agent.sh branch --topic "<what you are working on>"` before code changes when you need a task branch/worktree.
172
+ 9. Run `bin/agent.sh save` and `bin/agent.sh wrap` for portable save/wrap flows.
173
+
174
+ This protocol is runtime-neutral. Claude Code may still use .claude hooks and
175
+ skills, while Codex or another agent can call this script directly.
176
+ EOF
177
+ }
178
+
179
+ cmd_people() {
180
+ ensure_memory
181
+ local people_dir="$MEMORY_DIR/people"
182
+ [ -d "$people_dir" ] || return 0
183
+ local f github display
184
+ for f in "$people_dir"/*.md; do
185
+ [ -f "$f" ] || continue
186
+ github="$(basename "$f" .md)"
187
+ display="$(sed -nE 's/^name:[[:space:]]*(.*)$/\1/p; s/^# (.*)$/\1/p' "$f" | head -1)"
188
+ [ -z "$display" ] && display="$github"
189
+ printf '%s\t%s\n' "$github" "$display"
190
+ done
191
+ }
192
+
193
+ cmd_sync() {
194
+ sync_memory
195
+ echo "synced: $MEMORY_DIR"
196
+ }
197
+
198
+ cmd_activity() {
199
+ ensure_memory
200
+ local for_person=""
201
+ local limit=12
202
+ while [ $# -gt 0 ]; do
203
+ case "$1" in
204
+ --for) for_person="${2:-}"; shift 2 ;;
205
+ --limit) limit="${2:-12}"; shift 2 ;;
206
+ *) die "unknown activity option: $1" ;;
207
+ esac
208
+ done
209
+
210
+ echo "Handoffs"
211
+ local index="$MEMORY_DIR/handoffs/index.md"
212
+ if [ -f "$index" ]; then
213
+ if [ -n "$for_person" ]; then
214
+ grep -i "handoff to ${for_person}" "$index" 2>/dev/null | head -n "$limit" || true
215
+ else
216
+ grep '^- ' "$index" 2>/dev/null | head -n "$limit" || true
217
+ fi
218
+ fi
219
+
220
+ echo
221
+ echo "Questions"
222
+ local qdir="$MEMORY_DIR/knowledge/questions"
223
+ [ -d "$qdir" ] || return 0
224
+ local q from to topic status
225
+ while IFS= read -r q; do
226
+ from="$(sed -nE 's/^from:[[:space:]]*(.*)$/\1/p' "$q" | head -1)"
227
+ to="$(sed -nE 's/^to:[[:space:]]*(.*)$/\1/p' "$q" | head -1)"
228
+ topic="$(sed -nE 's/^topic:[[:space:]]*(.*)$/\1/p' "$q" | head -1)"
229
+ status="$(sed -nE 's/^status:[[:space:]]*(.*)$/\1/p' "$q" | head -1)"
230
+ [ -z "$status" ] && status="pending"
231
+ if [ -n "$for_person" ] && [ "$(echo "$to" | tr '[:upper:]' '[:lower:]')" != "$(echo "$for_person" | tr '[:upper:]' '[:lower:]')" ]; then
232
+ continue
233
+ fi
234
+ printf '%s\n' "- ${from} -> ${to} [${status}]: ${topic} (${q#$MEMORY_DIR/})"
235
+ done < <(find "$qdir" -type f -name '*.md' | sort -r | head -n "$limit")
236
+ }
237
+
238
+ cmd_branch() {
239
+ local topic="" author="" branch_type="" slug="" branch="" wt_path="" existing_wt="" base=""
240
+
241
+ while [ $# -gt 0 ]; do
242
+ case "$1" in
243
+ --topic|--description) topic="${2:-}"; shift 2 ;;
244
+ --from|--author) author="${2:-}"; shift 2 ;;
245
+ --type) branch_type="${2:-}"; shift 2 ;;
246
+ *) die "unknown branch option: $1" ;;
247
+ esac
248
+ done
249
+
250
+ [ -n "$topic" ] || topic="$(date +%Y-%m-%d)"
251
+ [ -n "$author" ] || author="$(current_author)"
252
+
253
+ if [ -z "$branch_type" ]; then
254
+ case "$(echo "$topic" | tr '[:upper:]' '[:lower:]')" in
255
+ *fix*|*bug*|*broken*|*crash*) branch_type="bugfix" ;;
256
+ *feature*|*add*|*implement*|*new*) branch_type="feature" ;;
257
+ *) branch_type="dev" ;;
258
+ esac
259
+ fi
260
+ case "$branch_type" in
261
+ dev|feature|bugfix) ;;
262
+ *) die "invalid --type: $branch_type (valid: dev|feature|bugfix)" ;;
263
+ esac
264
+
265
+ slug="$(slugify "$topic")"
266
+ [ -n "$slug" ] || slug="$(date +%Y-%m-%d)"
267
+ case "$branch_type" in
268
+ dev) branch="dev/${author}/${slug}" ;;
269
+ feature) branch="feature/${slug}" ;;
270
+ bugfix) branch="bugfix/${slug}" ;;
271
+ esac
272
+
273
+ base="$(base_ref)"
274
+
275
+ if [ -f "$SCRIPT_DIR/.git" ]; then
276
+ if git -C "$SCRIPT_DIR" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
277
+ git -C "$SCRIPT_DIR" checkout "$branch" --quiet
278
+ else
279
+ git -C "$SCRIPT_DIR" checkout -b "$branch" "$base" --quiet
280
+ fi
281
+ egregore_link_shared_state "$SCRIPT_DIR" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
282
+ echo "branch: $branch"
283
+ echo "worktree: $SCRIPT_DIR"
284
+ return 0
285
+ fi
286
+
287
+ existing_wt="$(worktree_for_branch "$branch")"
288
+ if [ -n "$existing_wt" ] && [ -d "$existing_wt" ]; then
289
+ bash "$MAIN_PROJECT_DIR/bin/worktree.sh" setup "$existing_wt" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
290
+ echo "branch: $branch"
291
+ echo "worktree: $existing_wt"
292
+ return 0
293
+ fi
294
+
295
+ if ! git -C "$MAIN_PROJECT_DIR" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
296
+ git -C "$MAIN_PROJECT_DIR" branch "$branch" "$base" >/dev/null 2>&1 || die "failed to create branch $branch"
297
+ fi
298
+
299
+ wt_path="$MAIN_PROJECT_DIR/.claude/worktrees/$slug"
300
+ if [ -e "$wt_path" ]; then
301
+ die "worktree path already exists: $wt_path"
302
+ fi
303
+
304
+ mkdir -p "$MAIN_PROJECT_DIR/.claude/worktrees"
305
+ git -C "$MAIN_PROJECT_DIR" worktree add "$wt_path" "$branch" --quiet 2>/dev/null || die "failed to create worktree at $wt_path"
306
+ bash "$MAIN_PROJECT_DIR/bin/worktree.sh" setup "$wt_path" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
307
+
308
+ echo "branch: $branch"
309
+ echo "worktree: $wt_path"
310
+ }
311
+
312
+ ensure_working_branch() {
313
+ local topic="${1:-$(date +%Y-%m-%d)}"
314
+ local author branch slug base
315
+ branch="$(git -C "$SCRIPT_DIR" branch --show-current 2>/dev/null || echo "")"
316
+ case "$branch" in
317
+ dev/*|feature/*|bugfix/*) echo "$branch"; return 0 ;;
318
+ esac
319
+
320
+ author="$(current_author)"
321
+ slug="$(slugify "$topic")"
322
+ [ -n "$slug" ] || slug="$(date +%Y-%m-%d)"
323
+ branch="dev/${author}/${slug}"
324
+ base="$(base_ref)"
325
+
326
+ if git -C "$SCRIPT_DIR" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
327
+ git -C "$SCRIPT_DIR" checkout "$branch" --quiet || die "failed to checkout $branch"
328
+ else
329
+ git -C "$SCRIPT_DIR" checkout -b "$branch" "$base" --quiet || die "failed to create $branch"
330
+ fi
331
+ echo "$branch"
332
+ }
333
+
334
+ cmd_save() {
335
+ local message="" topic="" no_pr=0 branch pushed="false" committed="false" pr_url="" existing_pr=""
336
+
337
+ while [ $# -gt 0 ]; do
338
+ case "$1" in
339
+ --message|-m) message="${2:-}"; shift 2 ;;
340
+ --topic) topic="${2:-}"; shift 2 ;;
341
+ --no-pr) no_pr=1; shift ;;
342
+ *) die "unknown save option: $1" ;;
343
+ esac
344
+ done
345
+
346
+ [ -n "$topic" ] || topic="portable save"
347
+ [ -n "$message" ] || message="Save: $topic"
348
+
349
+ if [ -d "$MEMORY_DIR" ]; then
350
+ save_memory "$message" "."
351
+ echo "memory: synced"
352
+ fi
353
+
354
+ branch="$(git -C "$SCRIPT_DIR" branch --show-current 2>/dev/null || echo "")"
355
+ if [ -n "$(git -C "$SCRIPT_DIR" status --porcelain 2>/dev/null | head -1)" ]; then
356
+ branch="$(ensure_working_branch "$topic")"
357
+ git -C "$SCRIPT_DIR" add -A >/dev/null 2>&1 || die "git add failed"
358
+ if ! git -C "$SCRIPT_DIR" diff --cached --quiet 2>/dev/null; then
359
+ git -C "$SCRIPT_DIR" commit -m "$message" --quiet || die "git commit failed"
360
+ committed="true"
361
+ fi
362
+ fi
363
+ [ -n "$branch" ] || branch="$(git -C "$SCRIPT_DIR" branch --show-current 2>/dev/null || echo "")"
364
+
365
+ if [ -n "$branch" ] && git -C "$SCRIPT_DIR" remote get-url origin >/dev/null 2>&1; then
366
+ if git -C "$SCRIPT_DIR" push -u origin "$branch" --quiet 2>/dev/null; then
367
+ pushed="true"
368
+ else
369
+ die "push failed for $branch; commits are safe locally"
370
+ fi
371
+ fi
372
+
373
+ if [ "$no_pr" = "0" ] && [ "$pushed" = "true" ] && command -v gh >/dev/null 2>&1; then
374
+ existing_pr="$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // empty' 2>/dev/null || true)"
375
+ if [ -n "$existing_pr" ]; then
376
+ pr_url="$existing_pr"
377
+ else
378
+ pr_url="$(gh pr create --base develop --head "$branch" --title "$message" --body "Saved via bin/agent.sh save." 2>/dev/null || true)"
379
+ fi
380
+ fi
381
+
382
+ echo "branch: ${branch:-none}"
383
+ echo "commit: $committed"
384
+ echo "push: $pushed"
385
+ [ -n "$pr_url" ] && echo "pr: $pr_url"
386
+ return 0
387
+ }
388
+
389
+ cmd_wrap() {
390
+ ensure_memory
391
+ local from="" topic="" summary="" body="" body_file="" no_push=0
392
+
393
+ while [ $# -gt 0 ]; do
394
+ case "$1" in
395
+ --from) from="${2:-}"; shift 2 ;;
396
+ --topic) topic="${2:-}"; shift 2 ;;
397
+ --summary) summary="${2:-}"; shift 2 ;;
398
+ --body) body="${2:-}"; shift 2 ;;
399
+ --body-file) body_file="${2:-}"; shift 2 ;;
400
+ --no-push) no_push=1; shift ;;
401
+ *) die "unknown wrap option: $1" ;;
402
+ esac
403
+ done
404
+
405
+ [ -n "$from" ] || from="$(current_author)"
406
+ [ -n "$topic" ] || die "wrap requires --topic"
407
+ [ -n "$summary" ] || die "wrap requires --summary"
408
+ if [ -n "$body_file" ]; then
409
+ [ -f "$body_file" ] || die "body file not found: $body_file"
410
+ body="$(cat "$body_file")"
411
+ elif [ -z "$body" ] && [ ! -t 0 ]; then
412
+ body="$(cat)"
413
+ fi
414
+ [ -n "$body" ] || body="$summary"
415
+
416
+ local month rel abs slug now
417
+ month="$(date +%Y-%m)"
418
+ slug="$(slugify "$topic")"
419
+ [ -n "$slug" ] || slug="session"
420
+ rel="wraps/$month/$(date +%d)-${from}-${slug}.md"
421
+ abs="$MEMORY_DIR/$rel"
422
+ now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
423
+ mkdir -p "$(dirname "$abs")"
424
+
425
+ {
426
+ printf '# Wrap: %s\n\n' "$topic"
427
+ printf '**Date**: %s\n' "$(date +%Y-%m-%d)"
428
+ printf '**Author**: %s\n' "$from"
429
+ printf '**Wrapped At**: %s\n\n' "$now"
430
+ printf '## Summary\n\n%s\n\n' "$summary"
431
+ printf '## Notes\n\n%s\n' "$body"
432
+ } > "$abs"
433
+
434
+ NO_PUSH="$no_push" save_memory "Wrap: $topic" "$rel"
435
+ echo "wrap: memory/$rel"
436
+ }
437
+
438
+ cmd_handoff() {
439
+ ensure_memory
440
+ local from="" to="" topic="" body="" body_file="" project=""
441
+ local no_publish=0 no_notify=0 json=0
442
+ NO_PUSH=0
443
+
444
+ while [ $# -gt 0 ]; do
445
+ case "$1" in
446
+ --from) from="${2:-}"; shift 2 ;;
447
+ --to) to="${2:-}"; shift 2 ;;
448
+ --topic) topic="${2:-}"; shift 2 ;;
449
+ --body) body="${2:-}"; shift 2 ;;
450
+ --body-file) body_file="${2:-}"; shift 2 ;;
451
+ --project) project="${2:-}"; shift 2 ;;
452
+ --no-push) NO_PUSH=1; shift ;;
453
+ --no-publish) no_publish=1; shift ;;
454
+ --no-notify) no_notify=1; shift ;;
455
+ --json) json=1; shift ;;
456
+ *) die "unknown handoff option: $1" ;;
457
+ esac
458
+ done
459
+
460
+ [ -z "$from" ] && from="$(person_from_state)"
461
+ [ -n "$from" ] || die "handoff requires --from"
462
+ [ -n "$topic" ] || die "handoff requires --topic"
463
+ if [ -n "$body_file" ]; then
464
+ [ -f "$body_file" ] || die "body file not found: $body_file"
465
+ body="$(cat "$body_file")"
466
+ elif [ -z "$body" ] && [ ! -t 0 ]; then
467
+ body="$(cat)"
468
+ fi
469
+ [ -n "$body" ] || body="No briefing provided."
470
+
471
+ local today tmpd result result_out rel project_line stdout_file
472
+ local handoff_args=()
473
+ today="$(date +%Y-%m-%d)"
474
+ tmpd="$(mktemp -d -t egregore-agent-handoff-XXXXXX)"
475
+ trap 'rm -rf "$tmpd"' RETURN
476
+ result_out="$(mktemp -t egregore-agent-handoff-result-XXXXXX.json)"
477
+ stdout_file="$tmpd/stdout"
478
+
479
+ project_line=""
480
+ [ -n "$project" ] && project_line="**Project**: $project"
481
+ [ -n "$to" ] && handoff_args+=(--recipient "$to")
482
+ [ -n "$project" ] && handoff_args+=(--project "$project")
483
+ [ "$NO_PUSH" = "1" ] && handoff_args+=(--no-push)
484
+ [ "$no_publish" = "1" ] && handoff_args+=(--no-publish)
485
+ [ "$no_notify" = "1" ] && handoff_args+=(--no-notify)
486
+
487
+ {
488
+ printf '# Handoff: %s\n\n' "$topic"
489
+ printf '**Date**: %s\n' "$today"
490
+ printf '**Author**: %s\n' "$from"
491
+ [ -n "$to" ] && printf '**To**: %s\n' "$to"
492
+ [ -n "$project_line" ] && printf '%s\n' "$project_line"
493
+ printf '\n## Briefing\n\n%s\n\n' "$body"
494
+ printf '## Next Steps\n\n- Continue from this handoff using the shared Egregore memory protocol.\n'
495
+ } | TMPDIR="$tmpd" bash "$SCRIPT_DIR/bin/handoff-run.sh" \
496
+ --author "$from" \
497
+ --topic "$topic" \
498
+ "${handoff_args[@]}" \
499
+ > "$stdout_file"
500
+
501
+ result="$tmpd/handoff-run-result.json"
502
+ rel="$(jq -r '.file // empty' "$result" 2>/dev/null || true)"
503
+ [ -n "$rel" ] || die "handoff was not created"
504
+ cp "$result" "$result_out"
505
+ if [ "$json" = "1" ]; then
506
+ cat "$result_out"
507
+ return 0
508
+ fi
509
+ [ -s "$stdout_file" ] && sed 's/^/status: /' "$stdout_file"
510
+ echo "handoff: memory/$rel"
511
+ echo "result: $result_out"
512
+ }
513
+
514
+ cmd_ask() {
515
+ ensure_memory
516
+ local from="" to="" topic="" question=""
517
+ local harvest_id="" harvest_session_id="" turn="" question_intent="" context_mode=""
518
+ NO_PUSH=0
519
+
520
+ while [ $# -gt 0 ]; do
521
+ case "$1" in
522
+ --from) from="${2:-}"; shift 2 ;;
523
+ --to) to="${2:-}"; shift 2 ;;
524
+ --topic) topic="${2:-}"; shift 2 ;;
525
+ --question) question="${2:-}"; shift 2 ;;
526
+ --harvest-id) harvest_id="${2:-}"; shift 2 ;;
527
+ --harvest-session-id) harvest_session_id="${2:-}"; shift 2 ;;
528
+ --turn) turn="${2:-}"; shift 2 ;;
529
+ --question-intent) question_intent="${2:-}"; shift 2 ;;
530
+ --context-mode) context_mode="${2:-}"; shift 2 ;;
531
+ --no-push) NO_PUSH=1; shift ;;
532
+ *) die "unknown ask option: $1" ;;
533
+ esac
534
+ done
535
+
536
+ [ -z "$from" ] && from="$(person_from_state)"
537
+ [ -n "$from" ] || die "ask requires --from"
538
+ [ -n "$to" ] || die "ask requires --to"
539
+ [ -n "$topic" ] || die "ask requires --topic"
540
+ [ -n "$question" ] || die "ask requires --question"
541
+
542
+ if [ -n "$context_mode" ]; then
543
+ case "$context_mode" in
544
+ blind|disclosed|comparative) ;;
545
+ *) die "invalid --context-mode: $context_mode (valid: blind|disclosed|comparative)" ;;
546
+ esac
547
+ fi
548
+
549
+ if [ -n "$turn" ]; then
550
+ case "$turn" in
551
+ ''|*[!0-9]*) die "invalid --turn: must be a non-negative integer" ;;
552
+ esac
553
+ fi
554
+
555
+ reject_newlines "--from" "$from"
556
+ reject_newlines "--to" "$to"
557
+ reject_newlines "--topic" "$topic"
558
+ reject_newlines "--question" "$question"
559
+ reject_newlines "--harvest-id" "$harvest_id"
560
+ reject_newlines "--harvest-session-id" "$harvest_session_id"
561
+ reject_newlines "--question-intent" "$question_intent"
562
+
563
+ local now slug rel abs n
564
+ now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
565
+ slug="$(slugify "$topic")"
566
+ mkdir -p "$MEMORY_DIR/knowledge/questions"
567
+ rel="knowledge/questions/$(date +%Y-%m-%d)-${from}-to-${to}-${slug}.md"
568
+ abs="$MEMORY_DIR/$rel"
569
+ n=2
570
+ while [ -e "$abs" ]; do
571
+ rel="knowledge/questions/$(date +%Y-%m-%d)-${from}-to-${to}-${slug}-${n}.md"
572
+ abs="$MEMORY_DIR/$rel"
573
+ n=$((n + 1))
574
+ done
575
+
576
+ {
577
+ echo "---"
578
+ echo "from: $from"
579
+ echo "to: $to"
580
+ echo "topic: $topic"
581
+ echo "status: pending"
582
+ echo "created: $now"
583
+ [ -n "$harvest_id" ] && echo "harvest_id: $(yaml_str "$harvest_id")"
584
+ [ -n "$harvest_session_id" ] && echo "harvest_session_id: $(yaml_str "$harvest_session_id")"
585
+ [ -n "$turn" ] && echo "turn: $turn"
586
+ [ -n "$question_intent" ] && echo "question_intent: $(yaml_str "$question_intent")"
587
+ [ -n "$context_mode" ] && echo "context_mode: $context_mode"
588
+ echo "---"
589
+ echo ""
590
+ echo "## Questions"
591
+ echo ""
592
+ echo "- $question"
593
+ } > "$abs"
594
+
595
+ save_memory "Question: ${topic} (to ${to})" "$rel"
596
+ echo "question: memory/$rel"
597
+ }
598
+
599
+ cmd_answer() {
600
+ ensure_memory
601
+ local from="" question_path="" body=""
602
+ NO_PUSH=0
603
+
604
+ while [ $# -gt 0 ]; do
605
+ case "$1" in
606
+ --from) from="${2:-}"; shift 2 ;;
607
+ --question) question_path="${2:-}"; shift 2 ;;
608
+ --body) body="${2:-}"; shift 2 ;;
609
+ --no-push) NO_PUSH=1; shift ;;
610
+ *) die "unknown answer option: $1" ;;
611
+ esac
612
+ done
613
+
614
+ [ -z "$from" ] && from="$(person_from_state)"
615
+ [ -n "$from" ] || die "answer requires --from"
616
+ [ -n "$question_path" ] || die "answer requires --question"
617
+ [ -n "$body" ] || die "answer requires --body"
618
+
619
+ local abs rel now tmp
620
+ case "$question_path" in
621
+ "$MEMORY_DIR"/*) abs="$question_path" ;;
622
+ memory/*) abs="$MEMORY_DIR/${question_path#memory/}" ;;
623
+ *) abs="$MEMORY_DIR/$question_path" ;;
624
+ esac
625
+ [ -f "$abs" ] || die "question file not found: $question_path"
626
+ rel="${abs#$MEMORY_DIR/}"
627
+ now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
628
+ tmp="$abs.tmp.$$"
629
+
630
+ awk -v from="$from" -v now="$now" '
631
+ BEGIN { in_fm = 0; added = 0; saw_status = 0 }
632
+ NR == 1 && $0 == "---" { in_fm = 1; print; next }
633
+ in_fm && /^status:/ { print "status: answered"; saw_status = 1; next }
634
+ in_fm && $0 == "---" {
635
+ if (!saw_status) print "status: answered"
636
+ if (!added) {
637
+ print "answered_by: " from
638
+ print "answered_at: " now
639
+ added = 1
640
+ }
641
+ in_fm = 0
642
+ print
643
+ next
644
+ }
645
+ { print }
646
+ ' "$abs" > "$tmp" && mv "$tmp" "$abs"
647
+
648
+ {
649
+ printf '\n## Answer\n\n'
650
+ printf '**From**: %s\n' "$from"
651
+ printf '**At**: %s\n\n' "$now"
652
+ printf '%s\n' "$body"
653
+ } >> "$abs"
654
+
655
+ save_memory "Answer question: ${rel}" "$rel"
656
+ echo "answered: memory/$rel"
657
+ }
658
+
659
+ cmd_search() {
660
+ bash "$SCRIPT_DIR/bin/search.sh" query "$@"
661
+ }
662
+
663
+ case "${1:-}" in
664
+ --help|-h|help|"") usage ;;
665
+ protocol) shift; cmd_protocol "$@" ;;
666
+ sync) shift; cmd_sync "$@" ;;
667
+ activity) shift; cmd_activity "$@" ;;
668
+ search) shift; cmd_search "$@" ;;
669
+ people) shift; cmd_people "$@" ;;
670
+ branch) shift; cmd_branch "$@" ;;
671
+ save) shift; cmd_save "$@" ;;
672
+ wrap) shift; cmd_wrap "$@" ;;
673
+ handoff) shift; cmd_handoff "$@" ;;
674
+ ask) shift; cmd_ask "$@" ;;
675
+ answer) shift; cmd_answer "$@" ;;
676
+ *) die "unknown command: $1" ;;
677
+ esac