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,306 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # Index a single handoff file into the Neo4j graph.
5
+ # Usage: bash bin/index-handoff.sh <file-path>
6
+ # Returns: {"sessionId":"...","resolved":N} or {"error":"..."}
7
+ #
8
+ # The file-path should be relative to memory/ (e.g. handoffs/2026-02/14-oz-topic.md)
9
+ # or an absolute/relative path that contains "handoffs/" somewhere in it.
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
12
+
13
+ if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
14
+ echo "Usage: index-handoff.sh <handoff-file-path>"
15
+ echo ""
16
+ echo "Index a single handoff file into the Neo4j graph."
17
+ echo "Parses metadata (author, date, topic, recipients) from"
18
+ echo "markdown headers or YAML front matter, creates Session"
19
+ echo "and relationship nodes, and auto-resolves old handoffs."
20
+ echo ""
21
+ echo "Returns: {\"sessionId\":\"...\",\"resolved\":N}"
22
+ exit 0
23
+ fi
24
+
25
+ # --- Local mode gate: bail immediately ---
26
+ _MODE=$(jq -r '.mode // "connected"' "$SCRIPT_DIR/egregore.json" 2>/dev/null)
27
+ if [ "$_MODE" = "local" ]; then
28
+ echo '{"sessionId":"","resolved":0,"mode":"local"}'
29
+ exit 0
30
+ fi
31
+
32
+ # --- Validate input ---
33
+ FILE_ARG="${1:?Usage: index-handoff.sh <handoff-file-path>}"
34
+
35
+ # Resolve to absolute path if needed
36
+ if [[ "$FILE_ARG" = /* ]]; then
37
+ FILE_PATH="$FILE_ARG"
38
+ elif [[ "$FILE_ARG" = memory/* ]]; then
39
+ FILE_PATH="$SCRIPT_DIR/$FILE_ARG"
40
+ else
41
+ FILE_PATH="$SCRIPT_DIR/memory/$FILE_ARG"
42
+ fi
43
+
44
+ if [ ! -f "$FILE_PATH" ]; then
45
+ echo '{"error":"File not found: '"$FILE_ARG"'"}'
46
+ exit 1
47
+ fi
48
+
49
+ # --- Derive filePath (relative to memory/) and sessionId ---
50
+ REL_PATH="${FILE_PATH##*/memory/}"
51
+ if [[ "$REL_PATH" = "$FILE_PATH" ]]; then
52
+ REL_PATH="handoffs/${FILE_PATH##*handoffs/}"
53
+ fi
54
+
55
+ FILENAME="$(basename "$FILE_PATH" .md)"
56
+ DIR_MONTH="$(basename "$(dirname "$FILE_PATH")")"
57
+
58
+ # sessionId = YYYY-MM-DD-author-topic
59
+ # If file is in a YYYY-MM subdirectory, derive from dir + filename
60
+ # If file is in root handoffs/ (no month subdir), extract date from filename
61
+ if [[ "$DIR_MONTH" =~ ^[0-9]{4}-[0-9]{2}$ ]]; then
62
+ DAY="${FILENAME%%-*}"
63
+ REST="${FILENAME#*-}"
64
+ SESSION_ID="${DIR_MONTH}-${DAY}-${REST}"
65
+ else
66
+ # Filename starts with YYYY-MM-DD — use it directly
67
+ SESSION_ID="$FILENAME"
68
+ DAY="$(echo "$FILENAME" | grep -oE '^[0-9]{4}-[0-9]{2}-[0-9]{2}' || true)"
69
+ REST="${FILENAME#*-*-*-}"
70
+ DIR_MONTH="$(echo "$DAY" | grep -oE '^[0-9]{4}-[0-9]{2}' || true)"
71
+ fi
72
+
73
+ # --- Parse handoff metadata from markdown ---
74
+ CONTENT="$(cat "$FILE_PATH")"
75
+
76
+ # Helper: extract from markdown headers, handles both **Key**: Value and **Key:** Value
77
+ md_extract() {
78
+ echo "$CONTENT" | grep -m1 "$1" 2>/dev/null | sed "$2" || true
79
+ }
80
+
81
+ # Helper: extract markdown field by name (handles **Key**: and **Key:** patterns)
82
+ md_field() {
83
+ local key="$1"
84
+ local val
85
+ # Try **Key**: Value (colon outside bold)
86
+ val="$(echo "$CONTENT" | grep -m1 "^\*\*${key}\*\*:" 2>/dev/null | sed "s/\*\*${key}\*\*:[[:space:]]*//" | tr -d '\n' || true)"
87
+ # Try **Key:** Value (colon inside bold)
88
+ if [ -z "$val" ]; then
89
+ val="$(echo "$CONTENT" | grep -m1 "^\*\*${key}:\*\*" 2>/dev/null | sed "s/\*\*${key}:\*\*[[:space:]]*//" | tr -d '\n' || true)"
90
+ fi
91
+ echo "$val"
92
+ }
93
+
94
+ # Helper: extract from YAML front matter (key: value between --- delimiters)
95
+ yaml_extract() {
96
+ local key="$1"
97
+ sed -n '/^---$/,/^---$/p' "$FILE_PATH" 2>/dev/null | grep -m1 "^${key}:" 2>/dev/null | sed "s/^${key}:[[:space:]]*//" | sed 's/^"//' | sed 's/"$//' | xargs 2>/dev/null || true
98
+ }
99
+
100
+ # H1 topic (always from markdown body, not front matter)
101
+ TOPIC="$(md_extract '^# ' 's/^# //; s/^Handoff: //')"
102
+ # Also check YAML front matter topic
103
+ [ -z "$TOPIC" ] && TOPIC="$(yaml_extract 'topic')"
104
+ [ -z "$TOPIC" ] && TOPIC="$REST"
105
+
106
+ # Date: try markdown header, then YAML
107
+ DATE="$(md_field 'Date')"
108
+ [ -z "$DATE" ] && DATE="$(yaml_extract 'date')"
109
+ [ -z "$DATE" ] && DATE="${DIR_MONTH}-${DAY}"
110
+
111
+ # Author: try markdown header (Author or From), then YAML (author or from)
112
+ AUTHOR="$(md_field 'Author')"
113
+ [ -z "$AUTHOR" ] && AUTHOR="$(md_field 'From')"
114
+ [ -z "$AUTHOR" ] && AUTHOR="$(yaml_extract 'author')"
115
+ [ -z "$AUTHOR" ] && AUTHOR="$(yaml_extract 'from')"
116
+ # Strip " → recipient" if present (e.g. "cem → oz" → "cem")
117
+ AUTHOR_HANDLE="$(echo "$AUTHOR" | sed 's/[[:space:]]*→.*//' | awk '{print tolower($1)}')"
118
+ [ -z "$AUTHOR_HANDLE" ] && AUTHOR_HANDLE="unknown"
119
+
120
+ # Recipients: try markdown header (To or For), then YAML (to or for)
121
+ RECIPIENTS_RAW="$(md_field 'To')"
122
+ [ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(md_field 'For')"
123
+ [ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'to')"
124
+ [ -z "$RECIPIENTS_RAW" ] && RECIPIENTS_RAW="$(yaml_extract 'for')"
125
+ # Also extract from "From: author → recipient" pattern
126
+ if [ -z "$RECIPIENTS_RAW" ] && echo "$AUTHOR" | grep -q '→' 2>/dev/null; then
127
+ RECIPIENTS_RAW="$(echo "$AUTHOR" | sed 's/.*→[[:space:]]*//')"
128
+ fi
129
+
130
+ # Project: try markdown header, then YAML
131
+ PROJECT="$(md_field 'Project')"
132
+ [ -z "$PROJECT" ] && PROJECT="$(yaml_extract 'project')"
133
+
134
+ # Summary: 5-level precedence chain
135
+ # 1. Frontmatter summary field
136
+ SUMMARY="$(yaml_extract 'summary')"
137
+ # 2. First non-empty paragraph after ## Briefing
138
+ [ -z "$SUMMARY" ] && SUMMARY="$(echo "$CONTENT" | awk '/^## Briefing/{found=1; next} found && /^#/{exit} found && /^[^#]/ && !/^[[:space:]]*$/{print; exit}' || true)"
139
+ # 3. First non-empty paragraph after ## Session Summary (legacy)
140
+ [ -z "$SUMMARY" ] && SUMMARY="$(echo "$CONTENT" | awk '/^## Session Summary/{found=1; next} found && /^#/{exit} found && /^[^#]/ && !/^[[:space:]]*$/{print; exit}' || true)"
141
+ # 4. First non-heading, non-metadata, non-empty line in body
142
+ [ -z "$SUMMARY" ] && SUMMARY="$(echo "$CONTENT" | awk '!/^[[:space:]]*$/ && !/^#/ && !/^\*\*/ && !/^---/ && !/^\|/ && !/^- /{print; exit}' || true)"
143
+ # 5. Topic fallback
144
+ [ -z "$SUMMARY" ] && SUMMARY="$TOPIC"
145
+
146
+ # Repo State: parse markdown table after "## Repo State"
147
+ # Extracts repo, branch, PR number, and base branch from each table row
148
+ REPO_STATE_JSON="[]"
149
+ REPO_TABLE=$(echo "$CONTENT" | awk '/^## Repo State/{found=1; next} found && /^#/{exit} found && /^\|[^-]/ && !/^\| Repo/{print}' || true)
150
+ if [ -n "$REPO_TABLE" ]; then
151
+ REPO_STATE_JSON=$(echo "$REPO_TABLE" | while IFS='|' read -r _ R_REPO R_BRANCH R_PR R_BASE _; do
152
+ R_REPO=$(echo "$R_REPO" | xargs 2>/dev/null || true)
153
+ R_BRANCH=$(echo "$R_BRANCH" | xargs 2>/dev/null || true)
154
+ R_PR=$(echo "$R_PR" | xargs 2>/dev/null | sed 's/^#//' | sed 's/—//' || true)
155
+ R_BASE=$(echo "$R_BASE" | xargs 2>/dev/null || true)
156
+ [ -z "$R_REPO" ] && continue
157
+ if [ -n "$R_PR" ] && [ "$R_PR" -eq "$R_PR" ] 2>/dev/null; then
158
+ jq -n --arg repo "$R_REPO" --arg branch "$R_BRANCH" --argjson pr "$R_PR" --arg base "$R_BASE" \
159
+ '{repo:$repo, branch:$branch, pr:$pr, base:$base}'
160
+ else
161
+ jq -n --arg repo "$R_REPO" --arg branch "$R_BRANCH" --arg base "$R_BASE" \
162
+ '{repo:$repo, branch:$branch, pr:null, base:$base}'
163
+ fi
164
+ done | jq -s '.' 2>/dev/null || echo "[]")
165
+ fi
166
+
167
+ # --- Build Cypher queries ---
168
+
169
+ # Q1: MERGE Session + relationships
170
+ HANDED_TO_CYPHER=""
171
+ _RECIP_ARGS=()
172
+ if [ -n "$RECIPIENTS_RAW" ]; then
173
+ IFS=',' read -ra RECIPS <<< "$RECIPIENTS_RAW"
174
+ for i in "${!RECIPS[@]}"; do
175
+ R="$(echo "${RECIPS[$i]}" | xargs | tr '[:upper:]' '[:lower:]')"
176
+ [ -z "$R" ] && continue
177
+ HANDED_TO_CYPHER="${HANDED_TO_CYPHER}
178
+ WITH s
179
+ OPTIONAL MATCH (r${i}:Person) WHERE toLower(r${i}.name) = \$recipient${i} OR r${i}.github = \$recipient${i} OR toLower(r${i}.fullName) = \$recipient${i}
180
+ FOREACH (_ IN CASE WHEN r${i} IS NOT NULL THEN [1] ELSE [] END |
181
+ MERGE (s)-[:HANDED_TO]->(r${i})
182
+ )"
183
+ _RECIP_ARGS+=("--arg" "recipient${i}" "$R")
184
+ done
185
+ fi
186
+
187
+ PROJECT_CYPHER=""
188
+ _PROJECT_ARGS=()
189
+ if [ -n "$PROJECT" ]; then
190
+ PROJECT_CYPHER="
191
+ WITH s
192
+ OPTIONAL MATCH (proj:Project) WHERE toLower(proj.name) = toLower(\$project)
193
+ FOREACH (_ IN CASE WHEN proj IS NOT NULL THEN [1] ELSE [] END |
194
+ MERGE (s)-[:ABOUT]->(proj)
195
+ )"
196
+ _PROJECT_ARGS=("--arg" "project" "$PROJECT")
197
+ fi
198
+
199
+ Q1_CYPHER="MATCH (p:Person) WHERE toLower(p.name) = \$author OR p.github = \$author OR toLower(p.fullName) = \$author OR \$author IN [x IN coalesce(p.previousNames, []) | toLower(x)]
200
+ MERGE (s:Session {id: \$sessionId})
201
+ ON CREATE SET s.date = date(\$date), s.topic = \$topic, s.summary = \$summary, s.filePath = \$filePath, s.handoffStatus = 'pending', s.repoState = \$repoState, s.startedAt = datetime(\$date + 'T00:00:00Z')
202
+ ON MATCH SET s.topic = \$topic, s.summary = \$summary, s.filePath = \$filePath, s.repoState = \$repoState
203
+ MERGE (s)-[:BY]->(p)${PROJECT_CYPHER}${HANDED_TO_CYPHER}
204
+ RETURN s.id AS sessionId"
205
+
206
+ Q1_PARAMS=$(jq -n \
207
+ --arg sessionId "$SESSION_ID" \
208
+ --arg author "$AUTHOR_HANDLE" \
209
+ --arg date "$DATE" \
210
+ --arg topic "$TOPIC" \
211
+ --arg summary "$SUMMARY" \
212
+ --arg filePath "$REL_PATH" \
213
+ --arg repoState "$REPO_STATE_JSON" \
214
+ "${_PROJECT_ARGS[@]:+${_PROJECT_ARGS[@]}}" \
215
+ "${_RECIP_ARGS[@]:+${_RECIP_ARGS[@]}}" \
216
+ '$ARGS.named')
217
+
218
+ # Q2: Auto-resolve old read handoffs from this author
219
+ Q2_CYPHER="MATCH (s:Session)-[:HANDED_TO]->(p:Person) WHERE (toLower(p.name) = \$author OR p.github = \$author OR toLower(p.fullName) = \$author) AND s.handoffStatus = 'read' AND s.id <> \$sessionId
220
+ WITH s, p, coalesce(s.handoffReadDate, s.date) AS sinceDate
221
+ MATCH (later:Session)-[:BY]->(p)
222
+ WHERE later.date > sinceDate
223
+ WITH s, count(later) AS laterSessions WHERE laterSessions > 0
224
+ SET s.handoffStatus = 'done'
225
+ RETURN count(s) AS resolved"
226
+
227
+ Q2_PARAMS=$(jq -n --arg author "$AUTHOR_HANDLE" --arg sessionId "$SESSION_ID" \
228
+ '{author: $author, sessionId: $sessionId}')
229
+
230
+ # Q3: Capture the handoff's knowledge-graph neighborhood for artifact rendering.
231
+ # Only the "graph-only" relations — things not already visible in the markdown
232
+ # header. Author/recipients/project live in the metadata row above the graph,
233
+ # so including them would be noise.
234
+ Q3_CYPHER="MATCH (s:Session {id: \$sessionId})
235
+ OPTIONAL MATCH (s)-[:IMPLEMENTS]->(prior:Session)
236
+ OPTIONAL MATCH (prior)-[:BY]->(priorAuthor:Person)
237
+ OPTIONAL MATCH (later:Session)-[:IMPLEMENTS]->(s)
238
+ OPTIONAL MATCH (later)-[:BY]->(laterAuthor:Person)
239
+ OPTIONAL MATCH (s)-[:CONTINUES]->(cont:Session)
240
+ OPTIONAL MATCH (cont)-[:BY]->(contAuthor:Person)
241
+ OPTIONAL MATCH (s)-[:ADVANCED|INVOLVES]->(quest:Quest)
242
+ OPTIONAL MATCH (s)-[:HAS_ACTIVITY]->(art:Artifact)
243
+ OPTIONAL MATCH (s)-[:PRODUCED]->(pr:PR)
244
+ RETURN
245
+ collect(DISTINCT {id: prior.id, topic: prior.topic, author: priorAuthor.name}) AS implementsHandoff,
246
+ collect(DISTINCT {id: later.id, topic: later.topic, author: laterAuthor.name}) AS implementedBy,
247
+ collect(DISTINCT {id: cont.id, topic: cont.topic, author: contAuthor.name}) AS continues,
248
+ collect(DISTINCT {id: quest.id, title: quest.title}) AS quests,
249
+ collect(DISTINCT {id: art.id, title: art.title, type: art.type, path: art.path}) AS artifacts,
250
+ collect(DISTINCT {id: pr.id, number: pr.number, title: pr.title, repo: pr.repo}) AS prs"
251
+
252
+ Q3_PARAMS=$(jq -n --arg sessionId "$SESSION_ID" '{sessionId: $sessionId}')
253
+
254
+ # --- Build batch request ---
255
+ BATCH_JSON=$(jq -n \
256
+ --arg q1 "$Q1_CYPHER" \
257
+ --argjson p1 "$Q1_PARAMS" \
258
+ --arg q2 "$Q2_CYPHER" \
259
+ --argjson p2 "$Q2_PARAMS" \
260
+ --arg q3 "$Q3_CYPHER" \
261
+ --argjson p3 "$Q3_PARAMS" \
262
+ '[{statement: $q1, parameters: $p1}, {statement: $q2, parameters: $p2}, {statement: $q3, parameters: $p3}]')
263
+
264
+ # --- Execute ---
265
+ RESPONSE=$(bash "$SCRIPT_DIR/bin/graph-batch.sh" "$BATCH_JSON" 2>/dev/null) || {
266
+ echo '{"error":"graph-batch.sh failed"}'
267
+ exit 1
268
+ }
269
+
270
+ # --- Mark auto-captured personal Session as handed_off ---
271
+ PROJ_HASH=$(echo -n "$SCRIPT_DIR" | md5 2>/dev/null || echo -n "$SCRIPT_DIR" | md5sum 2>/dev/null | cut -d' ' -f1)
272
+ CURRENT_SID=$(cat "$HOME/.egregore/session-${PROJ_HASH}.id" 2>/dev/null || echo "")
273
+ if [ -n "$CURRENT_SID" ]; then
274
+ bash "$SCRIPT_DIR/bin/graph.sh" query \
275
+ "MATCH (s:Session {id: \$sid}) SET s.status = 'handed_off' RETURN s.id" \
276
+ "$(jq -n --arg sid "$CURRENT_SID" '{sid: $sid}')" 2>/dev/null || true
277
+ fi
278
+
279
+ # --- Parse response ---
280
+ RESOLVED=$(echo "$RESPONSE" | jq -r '.results[1].values[0][0] // 0' 2>/dev/null || echo "0")
281
+
282
+ # Parse Q3 neighborhood into a subgraph object. Collect() entries that didn't
283
+ # match anything come back as {name: null, ...} — filter those out.
284
+ EMPTY_SG='{"implementsHandoff":[],"implementedBy":[],"continues":[],"quests":[],"artifacts":[],"prs":[]}'
285
+ SUBGRAPH_JSON=$(echo "$RESPONSE" | jq -c '
286
+ (.results[2] // null) as $r
287
+ | if $r == null or (($r.values // []) | length) == 0 then
288
+ {implementsHandoff:[], implementedBy:[], continues:[], quests:[], artifacts:[], prs:[]}
289
+ else
290
+ ($r.fields // []) as $f
291
+ | ($r.values[0] // []) as $v
292
+ | reduce range(0; ($f | length)) as $i ({}; .[$f[$i]] = $v[$i])
293
+ | {
294
+ implementsHandoff: ((.implementsHandoff // []) | map(select(.id != null))),
295
+ implementedBy: ((.implementedBy // []) | map(select(.id != null))),
296
+ continues: ((.continues // []) | map(select(.id != null))),
297
+ quests: ((.quests // []) | map(select(.id != null))),
298
+ artifacts: ((.artifacts // []) | map(select(.id != null or .path != null))),
299
+ prs: ((.prs // []) | map(select(.number != null or .id != null)))
300
+ }
301
+ end
302
+ ' 2>/dev/null || echo "$EMPTY_SG")
303
+ [ -z "$SUBGRAPH_JSON" ] && SUBGRAPH_JSON="$EMPTY_SG"
304
+
305
+ jq -n --arg sessionId "$SESSION_ID" --argjson resolved "$RESOLVED" --argjson subgraph "$SUBGRAPH_JSON" \
306
+ '{sessionId: $sessionId, resolved: $resolved, subgraph: $subgraph}'
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ # artifact-id.sh — Deterministic artifact IDs for memory files.
3
+ #
4
+ # Emits `{prefix}-{12 hex}` where prefix is derived from the file extension
5
+ # (m = .md, h = .html) and the hex is the first 12 chars of sha256(canonical_path).
6
+ #
7
+ # Kept in parity with packages/egregore-artifacts/lib/artifact-id.js — both MUST
8
+ # produce identical output for the same input. Any change here requires an
9
+ # equivalent change there (and a passing parity test in test/artifact-id.test.js).
10
+
11
+ _artifact_id_sha256() {
12
+ local input="$1"
13
+ printf '%s' "$input" | shasum -a 256 2>/dev/null | cut -d' ' -f1 \
14
+ || printf '%s' "$input" | sha256sum 2>/dev/null | cut -d' ' -f1
15
+ }
16
+
17
+ # artifact_id_from_path <path>
18
+ # Returns the deterministic artifact id for a repo-relative memory path, or
19
+ # empty (exit 1) if the extension is unsupported.
20
+ artifact_id_from_path() {
21
+ local raw="$1"
22
+ [ -n "$raw" ] || return 1
23
+
24
+ local canonical
25
+ canonical="${raw#./}"
26
+ canonical="$(printf '%s' "$canonical" | sed 's|/\{2,\}|/|g')"
27
+
28
+ # Extension match is case-insensitive to keep parity with the JS renderer
29
+ # (`extension()` in artifact-id.js lowercases before lookup). The canonical
30
+ # path itself is hashed verbatim — only the extension lookup is case-folded.
31
+ local ext_lower
32
+ ext_lower="$(printf '%s' "${canonical##*.}" | tr '[:upper:]' '[:lower:]')"
33
+
34
+ local prefix=""
35
+ case "$ext_lower" in
36
+ md) prefix="m" ;;
37
+ html) prefix="h" ;;
38
+ *) return 1 ;;
39
+ esac
40
+
41
+ local hash
42
+ hash="$(_artifact_id_sha256 "$canonical")"
43
+ [ -n "$hash" ] || return 1
44
+
45
+ printf '%s-%s' "$prefix" "${hash:0:12}"
46
+ }
@@ -0,0 +1,71 @@
1
+ #!/bin/bash
2
+ # Shared config helpers — sourced by session-start.sh and other scripts.
3
+ # Expects SCRIPT_DIR to be set by the caller.
4
+
5
+ # Load specific variables from .env (safe extraction, no code execution)
6
+ _load_env_var() {
7
+ local var_name="$1"
8
+ local env_file="${ENV_FILE:-$SCRIPT_DIR/.env}"
9
+ if [ -f "$env_file" ]; then
10
+ grep "^${var_name}=" "$env_file" 2>/dev/null | cut -d'=' -f2- || true
11
+ fi
12
+ }
13
+
14
+ # Read a value from egregore.json
15
+ _config_val() {
16
+ local key="$1"
17
+ local default="${2:-}"
18
+ local config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
19
+ if [ -f "$config" ]; then
20
+ local val
21
+ val=$(jq -r ".${key} // empty" "$config" 2>/dev/null || true)
22
+ echo "${val:-$default}"
23
+ else
24
+ echo "$default"
25
+ fi
26
+ }
27
+
28
+ # Get base branch for a repo. Reads base_branch from egregore.json repos[] config.
29
+ # Defaults to "develop" for backwards compatibility (existing egregores use develop).
30
+ # Only returns something other than "develop" when base_branch is explicitly set
31
+ # (e.g., for teams who added existing repos with a different default branch).
32
+ # Usage: _get_base_branch "repo-name" (omit for core repo)
33
+ _get_base_branch() {
34
+ local repo_name="${1:-}"
35
+ local config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
36
+
37
+ # Core egregore repo — always develop
38
+ if [ -z "$repo_name" ]; then
39
+ echo "develop"
40
+ return
41
+ fi
42
+
43
+ # Managed repo — check egregore.json for explicit base_branch
44
+ if [ -f "$config" ]; then
45
+ local base
46
+ base=$(jq -r --arg name "$repo_name" \
47
+ '(.repos[]? // empty) | select((if type == "object" then .name else . end) == $name) | if type == "object" then .base_branch // empty else empty end' \
48
+ "$config" 2>/dev/null || true)
49
+ if [ -n "$base" ]; then
50
+ echo "$base"
51
+ return
52
+ fi
53
+ fi
54
+
55
+ # No explicit config — default to develop (backwards compatible)
56
+ echo "develop"
57
+ }
58
+
59
+ # Detect local vs connected mode
60
+ _detect_mode() {
61
+ local config="${CONFIG:-$SCRIPT_DIR/egregore.json}"
62
+ local mode
63
+ mode=$(jq -r '.mode // empty' "$config" 2>/dev/null || true)
64
+ local api_url
65
+ api_url=$(jq -r '.api_url // empty' "$config" 2>/dev/null || true)
66
+ if [ "$mode" = "local" ] || [ -z "$api_url" ]; then
67
+ echo "local"
68
+ else
69
+ echo "connected"
70
+ fi
71
+ }