create-egregore 0.19.8 → 0.19.11

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 (91) hide show
  1. package/lib/local.js +11 -1
  2. package/lib/setup.js +33 -0
  3. package/package.json +1 -1
  4. package/runtime/codex/.codex/hooks/branch-guard.js +242 -34
  5. package/runtime/codex/.codex/skills/activity/SKILL.md +18 -6
  6. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +90 -55
  7. package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +25 -0
  8. package/runtime/codex/.codex/skills/handoff/SKILL.md +6 -2
  9. package/runtime/codex/.codex/skills/save/SKILL.md +21 -3
  10. package/runtime/codex/.codex/skills/scroll/SKILL.md +54 -3
  11. package/runtime/codex/.codex/skills/task/SKILL.md +25 -0
  12. package/runtime/codex/.codex/skills/tui-design/SKILL.md +1 -1
  13. package/runtime/codex/.codex/skills/wrap/SKILL.md +4 -1
  14. package/runtime/codex/.codex/spec-manifest.json +15 -3
  15. package/runtime/codex/AGENTS.md +45 -9
  16. package/runtime/codex/bin/activity-data.sh +1 -1
  17. package/runtime/codex/bin/agent.sh +66 -23
  18. package/runtime/codex/bin/attendant.sh +525 -0
  19. package/runtime/codex/bin/capture-reconcile.sh +67 -0
  20. package/runtime/codex/bin/capture-run.sh +341 -0
  21. package/runtime/codex/bin/codex-render-spec.mjs +15 -4
  22. package/runtime/codex/bin/codex-session-start.sh +45 -3
  23. package/runtime/codex/bin/codex-skill-render.mjs +23 -4
  24. package/runtime/codex/bin/graph-op.sh +719 -49
  25. package/runtime/codex/bin/graph-wal.sh +3 -2
  26. package/runtime/codex/bin/handoff-run.sh +72 -0
  27. package/runtime/codex/bin/handoff-save-egregore.sh +51 -11
  28. package/runtime/codex/bin/index-handoff.sh +26 -17
  29. package/runtime/codex/bin/ingest-graph.sh +80 -0
  30. package/runtime/codex/bin/ingest-surface.html +429 -0
  31. package/runtime/codex/bin/ingest.py +614 -0
  32. package/runtime/codex/bin/ingest.sh +231 -0
  33. package/runtime/codex/bin/ingest_graph.py +776 -0
  34. package/runtime/codex/bin/ingest_surface.py +464 -0
  35. package/runtime/codex/bin/lib/git-sync.sh +2 -6
  36. package/runtime/codex/bin/lib/greeting.sh +157 -22
  37. package/runtime/codex/bin/lib/identity.sh +50 -29
  38. package/runtime/codex/bin/lib/noncode.sh +31 -0
  39. package/runtime/codex/bin/person.py +610 -0
  40. package/runtime/codex/bin/person.sh +448 -0
  41. package/runtime/codex/bin/session-autosave.sh +184 -0
  42. package/runtime/codex/bin/session-log.sh +199 -0
  43. package/runtime/codex/bin/settings.sh +1 -1
  44. package/runtime/codex/manifest.json +19 -1
  45. package/runtime/pi/.codex/hooks/branch-guard.js +242 -34
  46. package/runtime/pi/.codex/skills/activity/SKILL.md +18 -6
  47. package/runtime/pi/.codex/skills/deep-reflect/SKILL.md +90 -55
  48. package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +25 -0
  49. package/runtime/pi/.codex/skills/handoff/SKILL.md +6 -2
  50. package/runtime/pi/.codex/skills/save/SKILL.md +21 -3
  51. package/runtime/pi/.codex/skills/scroll/SKILL.md +54 -3
  52. package/runtime/pi/.codex/skills/task/SKILL.md +25 -0
  53. package/runtime/pi/.codex/skills/tui-design/SKILL.md +1 -1
  54. package/runtime/pi/.codex/skills/wrap/SKILL.md +4 -1
  55. package/runtime/pi/.codex/spec-manifest.json +15 -3
  56. package/runtime/pi/.pi/APPEND_SYSTEM.md +41 -5
  57. package/runtime/pi/.pi/extensions/egregore.ts +98 -8
  58. package/runtime/pi/.pi/spec-manifest.json +2 -2
  59. package/runtime/pi/AGENTS.md +45 -9
  60. package/runtime/pi/bin/activity-data.sh +1 -1
  61. package/runtime/pi/bin/agent.sh +66 -23
  62. package/runtime/pi/bin/attendant.sh +525 -0
  63. package/runtime/pi/bin/capture-reconcile.sh +67 -0
  64. package/runtime/pi/bin/capture-run.sh +341 -0
  65. package/runtime/pi/bin/codex-render-spec.mjs +15 -4
  66. package/runtime/pi/bin/codex-session-start.sh +45 -3
  67. package/runtime/pi/bin/codex-skill-render.mjs +23 -4
  68. package/runtime/pi/bin/graph-op.sh +719 -49
  69. package/runtime/pi/bin/graph-wal.sh +3 -2
  70. package/runtime/pi/bin/handoff-run.sh +72 -0
  71. package/runtime/pi/bin/handoff-save-egregore.sh +51 -11
  72. package/runtime/pi/bin/index-handoff.sh +26 -17
  73. package/runtime/pi/bin/ingest-graph.sh +80 -0
  74. package/runtime/pi/bin/ingest-surface.html +429 -0
  75. package/runtime/pi/bin/ingest.py +614 -0
  76. package/runtime/pi/bin/ingest.sh +231 -0
  77. package/runtime/pi/bin/ingest_graph.py +776 -0
  78. package/runtime/pi/bin/ingest_surface.py +464 -0
  79. package/runtime/pi/bin/lib/git-sync.sh +2 -6
  80. package/runtime/pi/bin/lib/greeting.sh +157 -22
  81. package/runtime/pi/bin/lib/identity.sh +50 -29
  82. package/runtime/pi/bin/lib/noncode.sh +31 -0
  83. package/runtime/pi/bin/person.py +610 -0
  84. package/runtime/pi/bin/person.sh +448 -0
  85. package/runtime/pi/bin/pi-branch-policy.mjs +164 -4
  86. package/runtime/pi/bin/pi-product-workflows.mjs +31 -2
  87. package/runtime/pi/bin/publish-artifact.sh +14 -0
  88. package/runtime/pi/bin/session-autosave.sh +184 -0
  89. package/runtime/pi/bin/session-log.sh +199 -0
  90. package/runtime/pi/bin/settings.sh +274 -0
  91. package/runtime/pi/manifest.json +20 -1
@@ -14,7 +14,7 @@
14
14
  # - First session welcome
15
15
  #
16
16
  # Inputs: SCRIPT_DIR, STATE_FILE, CONFIG, CTX_DIR, BRANCH, COMMITS_AHEAD,
17
- # AUTHOR, LOCAL_MODE, MEMORY_SYNCED, REPOS_STATUS, SAVED_BRANCH,
17
+ # AUTHOR, LOCAL_MODE, MEMORY_SYNCED, SAVED_BRANCH,
18
18
  # HEALTH_*, FRAMEWORK_VERSION, TIME_OF_DAY, EGREGORE_SESSION_ID,
19
19
  # FIRST_SESSION, DISPLAY_NAME_STATE, DASHBOARD_URL
20
20
 
@@ -67,7 +67,8 @@ echo "$SEPARATOR"
67
67
  source "$SCRIPT_DIR/bin/lib/metrics.sh"
68
68
  _render_momentum_board
69
69
 
70
- # Pending questions addressed to you (one summary line — /answer to engage)
70
+ # Pending questions and handoffs addressed to you. These are lightweight
71
+ # discovery beats; management stays in /answer and /activity.
71
72
  PENDING_Q=$(cat "$CTX_DIR/pending_questions" 2>/dev/null || echo "[]")
72
73
  PENDING_Q_COUNT=$(echo "$PENDING_Q" | jq 'length' 2>/dev/null || echo "0")
73
74
  if [ "$PENDING_Q_COUNT" -gt 0 ] 2>/dev/null && [ "$PENDING_Q_COUNT" != "0" ]; then
@@ -83,6 +84,11 @@ if [ "$PENDING_Q_COUNT" -gt 0 ] 2>/dev/null && [ "$PENDING_Q_COUNT" != "0" ]; th
83
84
  printf " ◐ %s pending %s — /answer to engage\n" "$PENDING_Q_COUNT" "$PQ_NOUN"
84
85
  fi
85
86
  fi
87
+ ADDRESSED_RICH=$(cat "$CTX_DIR/addressed_rich" 2>/dev/null || echo "[]")
88
+ ADDRESSED_COUNT=$(echo "$ADDRESSED_RICH" | jq 'length' 2>/dev/null || echo "0")
89
+ if [ "$ADDRESSED_COUNT" -gt 0 ] 2>/dev/null; then
90
+ printf " ◇ %s handoffs for you — say \"show my handoffs\" to review or close\n" "$ADDRESSED_COUNT"
91
+ fi
86
92
 
87
93
  # Show auto-save notice if work was committed from a previous branch
88
94
  if [ -n "$SAVED_BRANCH" ]; then
@@ -114,14 +120,6 @@ if [ "$LOCAL_MODE" = "true" ]; then
114
120
  else
115
121
  printf " ✓ ready\n"
116
122
  fi
117
-
118
- # Managed repos on separate line
119
- if [ -n "$REPOS_STATUS" ]; then
120
- REPOS_COMPACT=$(printf '%s' "$REPOS_STATUS" | sed 's/^ ◇ //;s/^[[:space:]]*//' | paste -sd' ' - | sed 's/[[:space:]]*$//')
121
- if [ -n "$REPOS_COMPACT" ]; then
122
- printf " %s\n" "$REPOS_COMPACT"
123
- fi
124
- fi
125
123
  fi
126
124
  else
127
125
  # Connected mode: check all services
@@ -151,30 +149,167 @@ else
151
149
  F_PAD=$((LINE_WIDTH - FL_LEN - FR_LEN))
152
150
  if [ "$F_PAD" -lt 1 ]; then F_PAD=1; fi
153
151
  printf "%s%*s%s\n" "$FOOTER_LEFT" "$F_PAD" "" "$FOOTER_RIGHT"
152
+ fi
153
+ fi
154
154
 
155
- # Managed repos on separate line (avoids line overflow)
156
- if [ -n "$REPOS_STATUS" ]; then
157
- REPOS_COMPACT=$(printf '%s' "$REPOS_STATUS" | sed 's/^ ◇ //;s/^[[:space:]]*//' | paste -sd' ' - | sed 's/[[:space:]]*$//')
158
- if [ -n "$REPOS_COMPACT" ]; then
159
- printf " %s\n" "$REPOS_COMPACT"
160
- fi
155
+ # Autosave trail — background saves (Stop-hook, sweep, SessionEnd) since the
156
+ # last greeting. The ledger is machine-scoped; this is where sweep rescues of
157
+ # abandoned sessions become visible to the user.
158
+ AUTOSAVE_LOG="${EGREGORE_AUTOSAVE_LOG:-$HOME/.egregore/autosave.log}"
159
+ AUTOSAVE_SEEN="${AUTOSAVE_LOG}.seen"
160
+ if [ -f "$AUTOSAVE_LOG" ]; then
161
+ TOTAL_AS=$(grep -c '|autosave|' "$AUTOSAVE_LOG" 2>/dev/null || echo 0)
162
+ SEEN_AS=$(cat "$AUTOSAVE_SEEN" 2>/dev/null || echo 0)
163
+ case "$SEEN_AS" in ''|*[!0-9]*) SEEN_AS=0 ;; esac
164
+ if [ "$TOTAL_AS" -gt "$SEEN_AS" ]; then
165
+ NEW_AS=$((TOTAL_AS - SEEN_AS))
166
+ MERGED_AS=$(grep '|autosave|' "$AUTOSAVE_LOG" 2>/dev/null | tail -n "$NEW_AS" | grep -c '|merged|' || echo 0)
167
+ if [ "$MERGED_AS" -ge "$NEW_AS" ]; then
168
+ printf " ⟲ auto-saved %s non-coding save(s) → develop while you were away\n" "$NEW_AS"
169
+ else
170
+ printf " ⟲ auto-saved %s non-coding save(s) — %s merged, rest in open PRs\n" "$NEW_AS" "$MERGED_AS"
161
171
  fi
162
172
  fi
173
+ printf '%s' "$TOTAL_AS" > "$AUTOSAVE_SEEN" 2>/dev/null || true
163
174
  fi
164
175
 
165
- # Dashboard link (OSC 8 hyperlink — clickable text in supported terminals)
166
- if [ -n "${DASHBOARD_URL:-}" ]; then
167
- printf " ◆ %s\n" "$DASHBOARD_URL"
168
- fi
176
+ # Greeting links. Org config `pinned_links` in egregore.json replaces the
177
+ # default dashboard + board links. Entries are strings or {url, label}.
178
+ # (OSC 8 hyperlink — clickable text in supported terminals)
179
+ PINNED_LINKS=$(jq -c '.pinned_links // []' "$SCRIPT_DIR/egregore.json" 2>/dev/null || echo "[]")
180
+ if [ "$(printf '%s' "$PINNED_LINKS" | jq 'length' 2>/dev/null || echo 0)" -gt 0 ]; then
181
+ printf '%s' "$PINNED_LINKS" | jq -r '.[] |
182
+ if type == "string" then " ◆ \(.)"
183
+ elif (.label // "") != "" then " ◆ \(.label): \(.url)"
184
+ else " ◆ \(.url)" end' 2>/dev/null
185
+ else
186
+ if [ -n "${DASHBOARD_URL:-}" ]; then
187
+ printf " ◆ %s\n" "$DASHBOARD_URL"
188
+ fi
169
189
 
170
- if [ -n "${BOARD_URL:-}" ]; then
171
- printf " ◆ %s (board)\n" "$BOARD_URL"
190
+ if [ -n "${BOARD_URL:-}" ]; then
191
+ printf " ◆ %s (board)\n" "$BOARD_URL"
192
+ fi
172
193
  fi
173
194
 
174
195
  if [ -n "${LOOM_DOCTOR_BRIEF:-}" ]; then
175
196
  printf " %s\n" "$LOOM_DOCTOR_BRIEF"
176
197
  fi
177
198
 
199
+ # --- Creator review gate: pending turns on owned scrolls/surfaces ---
200
+ # This is deliberately filesystem-only and fail-soft. The two environment seams
201
+ # make the scan safe to exercise without touching the instance memory repo.
202
+ _print_pending_scroll_turns() {
203
+ local memory_root="${EGREGORE_MEMORY_ROOT:-$SCRIPT_DIR/memory}"
204
+ local person="${EGREGORE_PERSON:-${AUTHOR:-}}"
205
+ local rows="" file slug owner count surface_file
206
+
207
+ if [ -z "$person" ] && [ -f "${STATE_FILE:-}" ]; then
208
+ person=$(jq -r '.github_username // .display_name // .name // empty' "$STATE_FILE" 2>/dev/null || true)
209
+ fi
210
+ [ -n "$person" ] || return 0
211
+ [ -d "$memory_root" ] || return 0
212
+
213
+ for file in "$memory_root"/scrolls/.events/*.jsonl; do
214
+ [ -f "$file" ] || continue
215
+ slug=$(basename "$file" .jsonl)
216
+ [[ "$slug" =~ ^[A-Za-z0-9._-]+$ ]] || continue
217
+ safe_slug=$(printf '%s' "$slug" | tr -d '\000-\037\177')
218
+ [ "$safe_slug" = "$slug" ] || continue
219
+ owner=$(sed -nE 's/^[[:space:]-]*(\*\*)?creator(\*\*)?[[:space:]]*:[[:space:]]*//p' "$memory_root/scrolls/$slug.md" 2>/dev/null | head -1 | sed 's/[[:space:]]*$//')
220
+ [ "$owner" = "$person" ] || continue
221
+ count=$(jq -s -r '
222
+ def event_id:
223
+ if (.turn | type) == "object" then .turn.id
224
+ elif (.turn | type) == "string" then .turn
225
+ else .id end;
226
+ def received($events):
227
+ [$events[] | select(.type == "turn-received")
228
+ | {id: event_id, answers: (.turn.answers // .answers // [])}
229
+ | select(.id != null)] | unique_by(.id);
230
+ def review_id: event_id;
231
+ def is_absorbed($events; $id):
232
+ any($events[]; .type == "version-published" and ((.absorbs // .version.absorbs // .record.absorbs // []) | index($id) != null));
233
+ def is_applied($events; $id):
234
+ any($events[]; .type == "turn-applied" and (event_id == $id));
235
+ def is_declined($events; $received; $id):
236
+ ([$received[] | select(.id == $id)] | .[0]) as $turn |
237
+ ([$events[] | select(.type == "turn-reviewed" and review_id == $id)] | .[0]) as $review |
238
+ if $review == null then false
239
+ else if (($turn.answers // []) | length) == 0 then $review.disposition == "declined"
240
+ else (($turn.answers // [])
241
+ | map(($review.answers // {})[.fork] // $review.disposition)
242
+ | map(select(. == "accepted")) | length) == 0
243
+ end
244
+ end;
245
+ . as $events
246
+ | ($events | received($events)) as $received
247
+ | [$received[] as $turn
248
+ | select((is_absorbed($events; $turn.id) | not))
249
+ | select((is_declined($events; $received; $turn.id) | not))
250
+ | select(if $mode == "scroll" then true else (is_applied($events; $turn.id) | not) end)]
251
+ | length
252
+ ' --arg mode scroll "$file" 2>/dev/null) || continue
253
+ [[ "$count" =~ ^[0-9]+$ ]] || return 0
254
+ [ "$count" -gt 0 ] && rows+="${safe_slug}"$'\t'"${count}"$'\n'
255
+ done
256
+
257
+ for file in "$memory_root"/harvests/.events/*.jsonl; do
258
+ [ -f "$file" ] || continue
259
+ slug=$(basename "$file" .jsonl)
260
+ [[ "$slug" =~ ^[A-Za-z0-9._-]+$ ]] || continue
261
+ safe_slug=$(printf '%s' "$slug" | tr -d '\000-\037\177')
262
+ [ "$safe_slug" = "$slug" ] || continue
263
+ surface_file="$memory_root/harvests/$slug.json"
264
+ [ -f "$surface_file" ] || continue
265
+ owner=$(jq -r '.author // empty' "$surface_file" 2>/dev/null) || continue
266
+ [ "$owner" = "$person" ] || continue
267
+ count=$(jq -s -r '
268
+ def event_id:
269
+ if (.turn | type) == "object" then .turn.id
270
+ elif (.turn | type) == "string" then .turn
271
+ else .id end;
272
+ def received($events):
273
+ [$events[] | select(.type == "turn-received")
274
+ | {id: event_id, answers: (.turn.answers // .answers // [])}
275
+ | select(.id != null)] | unique_by(.id);
276
+ def review_id: event_id;
277
+ def is_absorbed($events; $id): false;
278
+ def is_applied($events; $id):
279
+ any($events[]; .type == "turn-applied" and (event_id == $id));
280
+ def is_declined($events; $received; $id):
281
+ ([$received[] | select(.id == $id)] | .[0]) as $turn |
282
+ ([$events[] | select(.type == "turn-reviewed" and review_id == $id)] | .[0]) as $review |
283
+ if $review == null then false
284
+ else if (($turn.answers // []) | length) == 0 then $review.disposition == "declined"
285
+ else (($turn.answers // [])
286
+ | map(($review.answers // {})[.fork] // $review.disposition)
287
+ | map(select(. == "accepted")) | length) == 0
288
+ end
289
+ end;
290
+ . as $events
291
+ | ($events | received($events)) as $received
292
+ | [$received[] as $turn
293
+ | select((is_applied($events; $turn.id) | not))
294
+ | select((is_declined($events; $received; $turn.id) | not))]
295
+ | length
296
+ ' --arg mode harvest "$file" 2>/dev/null) || continue
297
+ [[ "$count" =~ ^[0-9]+$ ]] || return 0
298
+ [ "$count" -gt 0 ] && rows+="${safe_slug}"$'\t'"${count}"$'\n'
299
+ done
300
+
301
+ [ -n "$rows" ] || return 0
302
+ local summary total details
303
+ summary=$(printf '%s' "$rows" | awk -F '\t' 'NF == 2 { totals[$1] += $2 } END { for (slug in totals) print slug "\t" totals[slug] }' | sort -t $'\t' -k1,1) || return 0
304
+ [ -n "$summary" ] || return 0
305
+ total=$(printf '%s\n' "$summary" | awk -F '\t' '{ total += $2 } END { print total + 0 }') || return 0
306
+ details=$(printf '%s\n' "$summary" | awk -F '\t' '{printf "%s%s (%s)", (NR == 1 ? "" : ", "), $1, $2}') || return 0
307
+ [ -n "$details" ] || return 0
308
+ printf " ⧖ %s pending turn(s) on your scrolls: %s\n" "$total" "$details"
309
+ }
310
+
311
+ _print_pending_scroll_turns
312
+
178
313
  # Framework updates come through PRs to develop — no separate auto-update channel.
179
314
  # The develop sync above already keeps framework files current.
180
315
  # Use /update for manual upstream pulls when needed.
@@ -41,8 +41,16 @@ else
41
41
  if [ -n "$GH_TOKEN" ]; then
42
42
  GH_USER_JSON=$(curl -s -H "Authorization: token $GH_TOKEN" https://api.github.com/user --max-time 5 2>/dev/null || echo "")
43
43
  GH_LOGIN=$(echo "$GH_USER_JSON" | jq -r '.login // empty' 2>/dev/null)
44
+ GH_ID=$(echo "$GH_USER_JSON" | jq -r '.id // empty' 2>/dev/null)
44
45
  GH_NAME=$(echo "$GH_USER_JSON" | jq -r '.name // empty' 2>/dev/null)
46
+ GH_EMAIL=$(echo "$GH_USER_JSON" | jq -r '.email // empty' 2>/dev/null)
45
47
  if [ -n "$GH_LOGIN" ]; then
48
+ GH_ID_JSON="null"
49
+ GH_PERSON_ID="github-login:$(echo "$GH_LOGIN" | tr '[:upper:]' '[:lower:]')"
50
+ if [[ "$GH_ID" =~ ^[0-9]+$ ]]; then
51
+ GH_ID_JSON="$GH_ID"
52
+ GH_PERSON_ID="github:$GH_ID"
53
+ fi
46
54
  AUTHOR="$GH_LOGIN"
47
55
  HEALTH_GITHUB="ok"
48
56
  # Set repo-local config and save to state for next time
@@ -63,11 +71,19 @@ else
63
71
  if [ "$HAS_ONBOARDING" = "unset" ]; then
64
72
  # State file exists but no onboarding flag — set to false to trigger it
65
73
  jq --arg u "$GH_LOGIN" --arg n "${GH_NAME:-$GH_LOGIN}" --arg ut "$USAGE_TYPE" \
66
- '.github_username = $u | .github_name = $n | .name = $n | .onboarding_complete = false | .usage_type = $ut' "$STATE_FILE" > "$STATE_FILE.tmp" \
74
+ --argjson gid "$GH_ID_JSON" --arg pid "$GH_PERSON_ID" --arg email "$GH_EMAIL" \
75
+ '.github_username = $u | .github_id = $gid | .person_id = $pid |
76
+ .github_name = $n | .name = $n |
77
+ (if $email != "" then .email = ($email | ascii_downcase) else . end) |
78
+ .onboarding_complete = false | .usage_type = $ut' "$STATE_FILE" > "$STATE_FILE.tmp" \
67
79
  && mv "$STATE_FILE.tmp" "$STATE_FILE"
68
80
  else
69
81
  jq --arg u "$GH_LOGIN" --arg n "${GH_NAME:-$GH_LOGIN}" --arg ut "$USAGE_TYPE" \
70
- '.github_username = $u | .github_name = $n | .name = $n | .usage_type = $ut' "$STATE_FILE" > "$STATE_FILE.tmp" \
82
+ --argjson gid "$GH_ID_JSON" --arg pid "$GH_PERSON_ID" --arg email "$GH_EMAIL" \
83
+ '.github_username = $u | .github_id = $gid | .person_id = $pid |
84
+ .github_name = $n | .name = $n |
85
+ (if $email != "" then .email = ($email | ascii_downcase) else . end) |
86
+ .usage_type = $ut' "$STATE_FILE" > "$STATE_FILE.tmp" \
71
87
  && mv "$STATE_FILE.tmp" "$STATE_FILE"
72
88
  fi
73
89
  FIRST_SESSION="false"
@@ -77,39 +93,44 @@ else
77
93
  GRAPH_PERSON=""
78
94
  GRAPH_DISPLAY_NAME=""
79
95
  GRAPH_PERSON=$(bash "$SCRIPT_DIR/bin/graph.sh" query \
80
- "MATCH (p:Person {github: \$github}) RETURN p.name AS name" \
81
- "$(jq -n --arg github "$GH_LOGIN" '{github: $github}')" 2>/dev/null || echo "")
96
+ "MATCH (p:Person)
97
+ WHERE NOT coalesce(p.kind, '') IN ['external', 'identity_alias']
98
+ AND coalesce(p.status, 'active') = 'active'
99
+ AND p.ingestRef IS NULL
100
+ AND (p.github = \$github
101
+ OR (\$githubId <> '' AND coalesce(toString(p.githubId), '') = \$githubId)
102
+ OR toLower(\$github) IN [alias IN coalesce(p.githubAliases, []) | toLower(alias)])
103
+ RETURN p.name AS name" \
104
+ "$(jq -n --arg github "$GH_LOGIN" --arg githubId "$GH_ID" \
105
+ '{github: $github, githubId: $githubId}')" 2>/dev/null || echo "")
82
106
  if echo "$GRAPH_PERSON" | jq -e '.values | length > 0' &>/dev/null; then
83
107
  GRAPH_DISPLAY_NAME=$(echo "$GRAPH_PERSON" | jq -r '.values[0][0] // empty' 2>/dev/null)
84
108
  # Existing team member — skip onboarding
85
- cat > "$STATE_FILE" << STATEEOF
86
- {
87
- "github_username": "$GH_LOGIN",
88
- "github_name": "${GH_NAME:-$GH_LOGIN}",
89
- "name": "${GRAPH_DISPLAY_NAME:-${GH_NAME:-$GH_LOGIN}}",
90
- "display_name": "${GRAPH_DISPLAY_NAME:-}",
91
- "onboarding_complete": true,
92
- "usage_type": "$USAGE_TYPE",
93
- "session_tracking": true,
94
- "transcript_sharing": true,
95
- "telemetry": true,
96
- "contact_preference": "all",
97
- "telemetry_noticed": true
98
- }
99
- STATEEOF
109
+ jq -n --arg github "$GH_LOGIN" --argjson githubId "$GH_ID_JSON" \
110
+ --arg personId "$GH_PERSON_ID" --arg githubName "${GH_NAME:-$GH_LOGIN}" \
111
+ --arg name "${GRAPH_DISPLAY_NAME:-${GH_NAME:-$GH_LOGIN}}" \
112
+ --arg displayName "${GRAPH_DISPLAY_NAME:-}" --arg email "$GH_EMAIL" \
113
+ --arg usageType "$USAGE_TYPE" \
114
+ '{
115
+ github_username: $github, github_id: $githubId, person_id: $personId,
116
+ github_name: $githubName, name: $name, display_name: $displayName,
117
+ email: (if $email == "" then null else ($email | ascii_downcase) end),
118
+ onboarding_complete: true, usage_type: $usageType,
119
+ session_tracking: true, transcript_sharing: true, telemetry: true,
120
+ contact_preference: "all", telemetry_noticed: true
121
+ }' > "$STATE_FILE"
100
122
  FIRST_SESSION="false"
101
123
  else
102
124
  # Genuinely new user — trigger onboarding
103
- cat > "$STATE_FILE" << STATEEOF
104
- {
105
- "github_username": "$GH_LOGIN",
106
- "github_name": "${GH_NAME:-$GH_LOGIN}",
107
- "name": "${GH_NAME:-$GH_LOGIN}",
108
- "onboarding_complete": false,
109
- "usage_type": "$USAGE_TYPE",
110
- "first_session": true
111
- }
112
- STATEEOF
125
+ jq -n --arg github "$GH_LOGIN" --argjson githubId "$GH_ID_JSON" \
126
+ --arg personId "$GH_PERSON_ID" --arg githubName "${GH_NAME:-$GH_LOGIN}" \
127
+ --arg email "$GH_EMAIL" --arg usageType "$USAGE_TYPE" \
128
+ '{
129
+ github_username: $github, github_id: $githubId, person_id: $personId,
130
+ github_name: $githubName, name: $githubName,
131
+ email: (if $email == "" then null else ($email | ascii_downcase) end),
132
+ onboarding_complete: false, usage_type: $usageType, first_session: true
133
+ }' > "$STATE_FILE"
113
134
  FIRST_SESSION="true"
114
135
  fi
115
136
  fi
@@ -0,0 +1,31 @@
1
+ #!/bin/bash
2
+ # noncode.sh — shared "non-coding change" classifier for auto-merge decisions.
3
+ #
4
+ # The org rule: non-coding PRs auto-merge to develop; code goes to review.
5
+ # Non-coding means:
6
+ # - any .md file, anywhere
7
+ # - anything under content directories (artifacts/, docs/, .threads/) —
8
+ # html/css/images/design bundles there are authored content, not shipped code
9
+ # - session-state droppings (.egregore-worktree-tty)
10
+ # Everything else — bin/, api/, packages/, .claude/, site 2/, root config — is
11
+ # code and stays open for review.
12
+ #
13
+ # Sourced by handoff-save-egregore.sh, session-autosave.sh, agent.sh, and the
14
+ # /save skill. Keep the policy HERE — call sites must not grow their own.
15
+
16
+ # noncode_filter — reads paths on stdin, prints the first code-bearing path.
17
+ # Empty output = everything is non-coding.
18
+ noncode_filter() {
19
+ grep -Ev '\.md$' \
20
+ | grep -Ev '^(artifacts|docs|\.threads)/' \
21
+ | grep -Ev '^\.egregore-worktree-tty$' \
22
+ | head -1 || true
23
+ }
24
+
25
+ # noncode_blockers [base-ref] [repo-dir] — first code-bearing path committed
26
+ # on HEAD relative to base (three-dot: only our side of the diff).
27
+ # Empty output = the branch is non-coding and safe to auto-merge.
28
+ noncode_blockers() {
29
+ local base="${1:-origin/develop}" dir="${2:-.}"
30
+ git -C "$dir" diff "$base...HEAD" --name-only 2>/dev/null | noncode_filter
31
+ }