create-egregore 0.20.1 → 0.20.2

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 (48) hide show
  1. package/assets/egregore-launcher.js +57 -14
  2. package/lib/local.js +4 -20
  3. package/lib/setup.js +0 -2
  4. package/package.json +2 -2
  5. package/runtime/codex/.codex/spec-manifest.json +3 -3
  6. package/runtime/codex/AGENTS.md +23 -9
  7. package/runtime/codex/bin/agent.sh +15 -3
  8. package/runtime/codex/bin/codex-render-spec.mjs +5 -3
  9. package/runtime/codex/bin/contribute-guard.sh +79 -0
  10. package/runtime/codex/bin/handoff-save-egregore.sh +1 -1
  11. package/runtime/codex/bin/lib/git-sync.sh +16 -0
  12. package/runtime/codex/bin/lib/greeting.sh +2 -2
  13. package/runtime/codex/bin/search.sh +46 -4
  14. package/runtime/codex/bin/session-autosave.sh +6 -1
  15. package/runtime/codex/bin/settings.sh +62 -3
  16. package/runtime/codex/bin/worktree-create.sh +4 -1
  17. package/runtime/codex/manifest.json +1 -0
  18. package/runtime/pi/.codex/spec-manifest.json +3 -3
  19. package/runtime/pi/.pi/APPEND_SYSTEM.md +23 -9
  20. package/runtime/pi/.pi/spec-manifest.json +2 -2
  21. package/runtime/pi/AGENTS.md +23 -9
  22. package/runtime/pi/bin/agent.sh +15 -3
  23. package/runtime/pi/bin/codex-render-spec.mjs +5 -3
  24. package/runtime/pi/bin/contribute-guard.sh +79 -0
  25. package/runtime/pi/bin/handoff-save-egregore.sh +1 -1
  26. package/runtime/pi/bin/lib/git-sync.sh +16 -0
  27. package/runtime/pi/bin/lib/greeting.sh +2 -2
  28. package/runtime/pi/bin/search.sh +46 -4
  29. package/runtime/pi/bin/session-autosave.sh +6 -1
  30. package/runtime/pi/bin/settings.sh +62 -3
  31. package/runtime/pi/bin/worktree-create.sh +4 -1
  32. package/runtime/pi/manifest.json +1 -0
  33. package/runtime/prime/.codex/spec-manifest.json +3 -3
  34. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +24 -10
  35. package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
  36. package/runtime/prime/AGENTS.md +23 -9
  37. package/runtime/prime/bin/agent.sh +15 -3
  38. package/runtime/prime/bin/codex-render-spec.mjs +5 -3
  39. package/runtime/prime/bin/contribute-guard.sh +79 -0
  40. package/runtime/prime/bin/handoff-save-egregore.sh +1 -1
  41. package/runtime/prime/bin/lib/git-sync.sh +16 -0
  42. package/runtime/prime/bin/lib/greeting.sh +2 -2
  43. package/runtime/prime/bin/prime-render-spec.mjs +1 -1
  44. package/runtime/prime/bin/search.sh +46 -4
  45. package/runtime/prime/bin/session-autosave.sh +6 -1
  46. package/runtime/prime/bin/settings.sh +62 -3
  47. package/runtime/prime/bin/worktree-create.sh +4 -1
  48. package/runtime/prime/manifest.json +1 -0
@@ -29,6 +29,48 @@ SLUG=$(jq -r '.slug // "egregore"' "$SCRIPT_DIR/egregore.json" 2>/dev/null || ec
29
29
  COLLECTION="${SLUG}-memory"
30
30
  MEM_PATH="$(readlink -f "$SCRIPT_DIR/memory" 2>/dev/null || true)"
31
31
 
32
+ # qmd 2.5.x may render --full-path results as absolute `**file:**` paths,
33
+ # while older output uses qmd:// URIs. Normalize both to the canonical
34
+ # memory/... form users can open from an Egregore checkout, and parse both
35
+ # forms when counting hits or attaching graph state.
36
+ _normalize_file_paths() {
37
+ local line absolute_prefix uri_prefix
38
+ absolute_prefix="**file:** \`$MEM_PATH/"
39
+ uri_prefix="**file:** \`qmd://${COLLECTION}/"
40
+ while IFS= read -r line || [ -n "$line" ]; do
41
+ if [[ "$line" == "$absolute_prefix"* ]]; then
42
+ printf '**file:** `memory/%s\n' "${line#"$absolute_prefix"}"
43
+ elif [[ "$line" == "$uri_prefix"* ]]; then
44
+ printf '**file:** `memory/%s\n' "${line#"$uri_prefix"}"
45
+ else
46
+ printf '%s\n' "$line"
47
+ fi
48
+ done
49
+ }
50
+
51
+ _result_paths() {
52
+ local line path canonical_prefix absolute_prefix uri_prefix
53
+ canonical_prefix="**file:** \`memory/"
54
+ absolute_prefix="**file:** \`$MEM_PATH/"
55
+ uri_prefix="**file:** \`qmd://${COLLECTION}/"
56
+ while IFS= read -r line || [ -n "$line" ]; do
57
+ path=""
58
+ if [[ "$line" == "$canonical_prefix"* ]]; then
59
+ path="${line#"$canonical_prefix"}"
60
+ elif [[ "$line" == "$absolute_prefix"* ]]; then
61
+ path="${line#"$absolute_prefix"}"
62
+ elif [[ "$line" == "$uri_prefix"* ]]; then
63
+ path="${line#"$uri_prefix"}"
64
+ elif [[ "$line" == *"qmd://${COLLECTION}/"* ]]; then
65
+ path="${line#*"qmd://${COLLECTION}/"}"
66
+ fi
67
+ path="${path%%\`*}"
68
+ case "$path" in
69
+ *.md*) printf '%s\n' "${path%%.md*}.md" ;;
70
+ esac
71
+ done | sort -u
72
+ }
73
+
32
74
  QMD_VERSION="2.5.3" # pinned — bump deliberately, never float @latest
33
75
 
34
76
  _qmd() {
@@ -104,7 +146,7 @@ _enrich() {
104
146
  local results paths params out
105
147
  results=$(cat)
106
148
  echo "$results"
107
- paths=$(echo "$results" | grep -o "qmd://${COLLECTION}/[^: ]*\.md" | sed "s|qmd://${COLLECTION}/||" | sort -u)
149
+ paths=$(printf '%s\n' "$results" | _result_paths)
108
150
  [ -z "$paths" ] && return 0
109
151
  params=$(printf '%s\n' "$paths" | jq -R . | jq -sc '{paths: .}')
110
152
  out=$(bash "$SCRIPT_DIR/bin/graph.sh" query "UNWIND \$paths AS fp
@@ -167,7 +209,7 @@ cmd_query() {
167
209
  local t0 out label
168
210
  t0=$(date +%s)
169
211
  if [ "$mode" != "search" ]; then
170
- out=$(_qmd "$mode" "$q" -c "$COLLECTION" -n "$n" --format md --full-path "${all[@]}" 2>/dev/null | _clean)
212
+ out=$(_qmd "$mode" "$q" -c "$COLLECTION" -n "$n" --format md --full-path "${all[@]}" 2>/dev/null | _clean | _normalize_file_paths)
171
213
  if [ -n "$out" ] && [ "$out" != "No results found." ]; then
172
214
  [ "$mode" = "query" ] && label="hybrid" || label="semantic"
173
215
  _banner_and_emit "$label" "$t0" "$emit" "$q" "$out"
@@ -176,7 +218,7 @@ cmd_query() {
176
218
  echo "_(hybrid/semantic returned nothing — showing keyword results. If this persists, run: bash bin/search.sh reindex --embed)_"
177
219
  echo ""
178
220
  fi
179
- out=$(_qmd search "$q" -c "$COLLECTION" -n "$n" --format md --full-path "${all[@]}" | _clean)
221
+ out=$(_qmd search "$q" -c "$COLLECTION" -n "$n" --format md --full-path "${all[@]}" | _clean | _normalize_file_paths)
180
222
  _banner_and_emit "keyword" "$t0" "$emit" "$q" "$out"
181
223
  }
182
224
 
@@ -189,7 +231,7 @@ cmd_query() {
189
231
  _banner_and_emit() {
190
232
  local label="$1" t0="$2" emit="$3" q="$4" out="$5"
191
233
  local hits dur modef product surface plural=""
192
- hits=$(printf '%s\n' "$out" | grep -c '^qmd://' 2>/dev/null | tr -d ' ')
234
+ hits=$(printf '%s\n' "$out" | _result_paths | wc -l | tr -d ' ')
193
235
  dur=$(( $(date +%s) - t0 ))
194
236
  modef="$label"
195
237
  product="Egregore"
@@ -85,7 +85,12 @@ git rev-parse --git-dir >/dev/null 2>&1 || exit 0
85
85
  DIR_HASH=$( { md5sum <<<"$DIR" 2>/dev/null || md5 -q -s "$DIR" 2>/dev/null || echo default; } | cut -c1-8 )
86
86
  STAMP="/tmp/.egregore-autosave-${DIR_HASH}"
87
87
  if [ "$DEBOUNCE" -gt 0 ] && [ -f "$STAMP" ]; then
88
- LAST=$(stat -f %m "$STAMP" 2>/dev/null || stat -c %Y "$STAMP" 2>/dev/null || echo 0)
88
+ if stat -c %Y "$STAMP" >/dev/null 2>&1; then
89
+ LAST=$(stat -c %Y "$STAMP")
90
+ else
91
+ LAST=$(stat -f %m "$STAMP" 2>/dev/null || echo 0)
92
+ fi
93
+ case "$LAST" in ""|*[!0-9]*) LAST=0 ;; esac
89
94
  [ $(( $(date +%s) - LAST )) -lt "$DEBOUNCE" ] && exit 0
90
95
  fi
91
96
  touch "$STAMP" 2>/dev/null || true
@@ -3,14 +3,16 @@ set -euo pipefail
3
3
 
4
4
  # settings.sh — deterministic config verbs for an Egregore instance.
5
5
  #
6
- # Edits egregore.json atomically. No agent, no network, no session. Idempotent:
7
- # re-adding an existing item is a no-op success; removing a missing one is too.
6
+ # Edits egregore.json atomically. No agent, no session. Idempotent: re-adding
7
+ # an existing item is a no-op success; removing a missing one is too. Switching
8
+ # to staged releases is the one networked verb: it creates origin/develop first.
8
9
  # This is the deterministic core the launcher settings screen (and slash
9
10
  # commands) call — the reliability lives here, the surfaces are thin callers.
10
11
  #
11
12
  # Usage:
12
13
  # settings.sh hosting status|on|off
13
14
  # settings.sh relay status|on|off
15
+ # settings.sh workflow status|simple|staged
14
16
  # settings.sh repo list | add <name> [description] | remove <name>
15
17
  # settings.sh admin list | add <github-handle> | remove <github-handle>
16
18
  # settings.sh dump # full settings snapshot as JSON (for the launcher)
@@ -53,6 +55,7 @@ settings.sh — Egregore instance settings
53
55
 
54
56
  settings.sh hosting status|on|off
55
57
  settings.sh relay status|on|off
58
+ settings.sh workflow status|simple|staged
56
59
  settings.sh repo list | add <name> [description] | remove <name>
57
60
  settings.sh admin list | add <github-handle> | remove <github-handle>
58
61
  settings.sh people list | add <github-handle> | remove <github-handle>
@@ -114,6 +117,54 @@ cmd_relay() {
114
117
  esac
115
118
  }
116
119
 
120
+ cmd_workflow() {
121
+ local action="${1:-status}"
122
+ local base
123
+ base="$(jq -r 'if has("base_branch") then .base_branch else "develop" end' "$CONFIG" 2>/dev/null)"
124
+ case "$action" in
125
+ status)
126
+ local workflow="custom"
127
+ [ "$base" = "main" ] && workflow="simple"
128
+ [ "$base" = "develop" ] && workflow="staged"
129
+ if [ "$JSON" = 1 ]; then
130
+ jq -n --arg workflow "$workflow" --arg base_branch "$base" \
131
+ '{domain:"workflow", workflow:$workflow, base_branch:$base_branch}'
132
+ elif [ "$workflow" = "simple" ]; then
133
+ echo "Git workflow: SIMPLE — pull requests target main"
134
+ elif [ "$workflow" = "staged" ]; then
135
+ echo "Git workflow: STAGED — pull requests target develop; releases promote to main"
136
+ else
137
+ echo "Git workflow: CUSTOM — pull requests target $base"
138
+ fi
139
+ ;;
140
+ simple)
141
+ if [ "$base" != "main" ]; then
142
+ git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1 \
143
+ || _die "cannot switch workflow: $SCRIPT_DIR is not a Git repository" 1
144
+ git -C "$SCRIPT_DIR" ls-remote --exit-code --heads origin main >/dev/null 2>&1 \
145
+ || _die "cannot switch workflow: origin/main does not exist or could not be reached" 1
146
+ _save '.base_branch = "main"'
147
+ fi
148
+ echo "Git workflow: SIMPLE — pull requests now target main"
149
+ ;;
150
+ staged)
151
+ if [ "$base" != "develop" ]; then
152
+ git -C "$SCRIPT_DIR" rev-parse --git-dir >/dev/null 2>&1 \
153
+ || _die "cannot switch workflow: $SCRIPT_DIR is not a Git repository" 1
154
+ bash "$SCRIPT_DIR/bin/lib/ensure-develop.sh" "$SCRIPT_DIR" >/dev/null \
155
+ || _die "could not create develop; workflow is unchanged" 1
156
+ git -C "$SCRIPT_DIR" ls-remote --exit-code --heads origin develop >/dev/null 2>&1 \
157
+ || _die "could not verify origin/develop; workflow is unchanged" 1
158
+ _save '.base_branch = "develop"'
159
+ elif ! jq -e 'has("base_branch")' "$CONFIG" >/dev/null 2>&1; then
160
+ _save '.base_branch = "develop"'
161
+ fi
162
+ echo "Git workflow: STAGED — pull requests now target develop; releases promote to main"
163
+ ;;
164
+ *) _die "usage: settings.sh workflow status|simple|staged" 1 ;;
165
+ esac
166
+ }
167
+
117
168
  cmd_repo() {
118
169
  local action="${1:-list}"; shift || true
119
170
  case "$action" in
@@ -273,6 +324,13 @@ cmd_dump() {
273
324
  slug: .slug,
274
325
  hosting: (.features.publishing != false),
275
326
  public_relay: (.features.public_relay == true),
327
+ workflow: (
328
+ if ((if has("base_branch") then .base_branch else "develop" end) == "main") then "simple"
329
+ elif ((if has("base_branch") then .base_branch else "develop" end) == "develop") then "staged"
330
+ else "custom"
331
+ end
332
+ ),
333
+ base_branch: (if has("base_branch") then .base_branch else "develop" end),
276
334
  repos: [.repos[]? | if type=="object" then . else {name:.} end],
277
335
  admins: (.admins // []),
278
336
  people: $people
@@ -295,10 +353,11 @@ DOMAIN="${1:-}"; shift || true
295
353
  case "$DOMAIN" in
296
354
  hosting) cmd_hosting "$@" ;;
297
355
  relay) cmd_relay "$@" ;;
356
+ workflow) cmd_workflow "$@" ;;
298
357
  repo) cmd_repo "$@" ;;
299
358
  admin) cmd_admin "$@" ;;
300
359
  people) cmd_people "$@" ;;
301
360
  dump) cmd_dump ;;
302
361
  ""|-h|--help|help) usage 0 ;;
303
- *) _die "unknown settings domain: '$DOMAIN' (try: hosting, relay, repo, admin, people, dump)" 1 ;;
362
+ *) _die "unknown settings domain: '$DOMAIN' (try: hosting, relay, workflow, repo, admin, people, dump)" 1 ;;
304
363
  esac
@@ -48,7 +48,10 @@ git -C "$REPO_ROOT" fetch origin "$BASE_BRANCH" --quiet 2>/dev/null || true
48
48
 
49
49
  # --- Create branch (idempotent — skip if exists) ---
50
50
  if ! git -C "$REPO_ROOT" show-ref --verify --quiet "refs/heads/$BRANCH" 2>/dev/null; then
51
- git -C "$REPO_ROOT" branch "$BRANCH" "origin/$BASE_BRANCH" >/dev/null 2>&1 || {
51
+ # A task branch starts at the integration branch but must never track it.
52
+ # Otherwise `git push` can target origin/$BASE_BRANCH (especially when the
53
+ # user's push.default is `upstream`) before the first explicit task push.
54
+ git -C "$REPO_ROOT" branch --no-track "$BRANCH" "origin/$BASE_BRANCH" >/dev/null 2>&1 || {
52
55
  echo "Error: failed to create branch $BRANCH" >&2
53
56
  exit 1
54
57
  }
@@ -123,6 +123,7 @@
123
123
  "bin/codex-skill-render.mjs",
124
124
  "bin/codex-sync-skills.sh",
125
125
  "bin/connect-refresh.sh",
126
+ "bin/contribute-guard.sh",
126
127
  "bin/dashboard-data.sh",
127
128
  "bin/graph-batch.sh",
128
129
  "bin/graph-op.sh",
@@ -11,7 +11,7 @@
11
11
  {
12
12
  "section": "Identity & Upstream",
13
13
  "action": "keep",
14
- "sourceHash": "ddcbc0083aff",
14
+ "sourceHash": "86a2c62ce498",
15
15
  "note": "Kept; /update, /contribute, /save tokens rendered as $skill tokens."
16
16
  },
17
17
  {
@@ -35,7 +35,7 @@
35
35
  {
36
36
  "section": "After Greeting — BRANCH ON FIRST RESPONSE",
37
37
  "action": "replace",
38
- "sourceHash": "b13b81aea8f3",
38
+ "sourceHash": "3bac01c0a00a",
39
39
  "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 bash kept verbatim (pure git); surrounding prose compressed 2026-08 for the 32 KiB budget, every rule retained."
40
40
  },
41
41
  {
@@ -89,7 +89,7 @@
89
89
  {
90
90
  "section": "Git Workflow",
91
91
  "action": "keep",
92
- "sourceHash": "23df84b5fe80",
92
+ "sourceHash": "40735c0c09ff",
93
93
  "note": "Kept; slash tokens rendered as $skill tokens. Branch diagram unchanged (code fence)."
94
94
  },
95
95
  {
@@ -12,11 +12,20 @@ This appendix is generated from the reviewed shell-runtime translation of `CLAUD
12
12
 
13
13
  ## Identity & Upstream
14
14
 
15
- This is an Egregore instance — a downstream fork of the upstream framework at `egregore-labs/egregore`. The framework (`bin/`, `.claude/skills/`, `.claude/hooks/`, `.claude/context/`, `.claude/agents/`, `.pi/`, `loom/`, `CLAUDE.md`, `skills/`) is synced from upstream on every session start and via `/update`. It is not authored in this repo.
15
+ Framework direction comes only from `egregore.json.upstream_url`:
16
+
17
+ - **Source** — literal `"none"`: framework changes are authored here.
18
+ `/update` and `/contribute` stop; use `/save`. Public OSS is downstream and
19
+ receives changes only through this repository's release flow.
20
+ - **Downstream** — a repository URL, or an absent key defaulting to
21
+ `egregore-labs/egregore`: framework paths are synced and not authored here.
22
+
23
+ Never infer direction from a repository name or Git remote.
16
24
 
17
25
  **Where changes belong:**
18
- - **Framework is outdated or a skill is missing:** run `/update` first. It pulls the latest from upstream.
19
- - **Framework behavior needs changing or has a bug:** `/contribute` opens a PR against `egregore-labs/egregore`, or file a GitHub issue on that repo. Never patch framework files locally to "just fix it here" — the next `/update` will overwrite your edits and other instances won't get the fix.
26
+ - **Downstream framework is stale:** run `/update`.
27
+ - **Downstream framework needs a fix:** use `/contribute` or file an upstream issue; local patches are overwritten by `/update`.
28
+ - **Source framework needs a fix:** use `/save` into its configured integration and release flow; never `/contribute`.
20
29
  - **Org-level work** (memory, org-specific knowledge, managed repos, `egregore.json`): `/save` to this repo.
21
30
 
22
31
  When a user reports broken or missing framework behavior, the first question is "when did you last `/update`?"
@@ -59,7 +68,7 @@ The integration branch is `develop` unless top-level `egregore.json.base_branch`
59
68
 
60
69
  **Fallback:** If `bin/agent.sh branch` fails, resolve `{base}` with
61
70
  `_get_base_branch`, then use
62
- `git checkout -b dev/{author}/{slug} origin/{base}`.
71
+ `git checkout --no-track -b dev/{author}/{slug} origin/{base}`.
63
72
 
64
73
  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 &`
65
74
 
@@ -125,9 +134,11 @@ The `.pi/extensions/egregore.ts` `tool_call` gate (loaded after Pi project trust
125
134
  Wait for the reply, then branch.
126
135
  - **The user explicitly requested the protected branch** → that request is consent. Record it with `echo '{branch}' > .egregore-branch-consent`, then retry. Never create the marker merely to silence the hook.
127
136
 
128
- Memory, managed-repo, and runtime-state writes bypass the project guard — if one triggers it, correct the target/context instead of asking for consent.
137
+ If Pi project resources are disabled, keep the same rule.
129
138
 
130
- If Pi project resources are disabled, follow the same discipline as a standing instruction: never write or commit on the configured base, develop, main, or master.
139
+ **History:** Do not merge, cherry-pick, or rebase another task merely to read
140
+ it; use `gh pr view/diff`, `git show`, or a temporary worktree. Integrate
141
+ only for dependencies. Deletion needs no rewrite unless purging secrets.
131
142
 
132
143
  ### Onboarding exception
133
144
 
@@ -275,11 +286,14 @@ branch; `base_branch: "main"` is single-branch mode. Users never interact with
275
286
  git directly.
276
287
 
277
288
  ```
278
- main ← stable (/release)
279
- develop ← integration (PRs land here)
280
- dev/{author}/{topic-slug} | feature/{slug} | bugfix/{slug}
289
+ main ← selected `/release` candidates
290
+ develop ← CL integration (available internally)
291
+ dev/{author}/{topic-slug} | feature/{slug} | bugfix/{slug}
281
292
  ```
282
293
 
294
+ - **Develop is not release**: merged PRs stay inside CL.
295
+ - **`/release` is selective**: the Release Desk queues exact PR SHAs into a candidate from main. Never merge all of develop.
296
+ - **OSS is separate**: `/sync-public` reviews delivery after private main.
283
297
  - **On launch**: syncs the configured base branch + memory. Does NOT create a branch.
284
298
  - **Branch creation**: MANDATORY on first work-related message (see above).
285
299
  - **Resuming**: rebase onto the configured base branch and continue.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "generatedBy": "bin/pi-render-spec.mjs",
3
3
  "source": "AGENTS.md generated Codex spec",
4
- "sourceHash": "535e5e6f8cce",
5
- "outputHash": "f76462a38e8d",
4
+ "sourceHash": "b0b4451961ef",
5
+ "outputHash": "52773e6edfc8",
6
6
  "adaptations": [
7
7
  "Pi project trust and APPEND_SYSTEM delivery",
8
8
  "Pi tool_call branch gate",
@@ -92,11 +92,20 @@ This block is the Codex-native Egregore behavioral spec, rendered from CLAUDE.md
92
92
 
93
93
  ## Identity & Upstream
94
94
 
95
- This is an Egregore instance — a downstream fork of the upstream framework at `egregore-labs/egregore`. The framework (`bin/`, `.claude/skills/`, `.claude/hooks/`, `.claude/context/`, `.claude/agents/`, `.pi/`, `loom/`, `CLAUDE.md`, `skills/`) is synced from upstream on every session start and via `$update`. It is not authored in this repo.
95
+ Framework direction comes only from `egregore.json.upstream_url`:
96
+
97
+ - **Source** — literal `"none"`: framework changes are authored here.
98
+ `$update` and `$contribute` stop; use `$save`. Public OSS is downstream and
99
+ receives changes only through this repository's release flow.
100
+ - **Downstream** — a repository URL, or an absent key defaulting to
101
+ `egregore-labs/egregore`: framework paths are synced and not authored here.
102
+
103
+ Never infer direction from a repository name or Git remote.
96
104
 
97
105
  **Where changes belong:**
98
- - **Framework is outdated or a skill is missing:** run `$update` first. It pulls the latest from upstream.
99
- - **Framework behavior needs changing or has a bug:** `$contribute` opens a PR against `egregore-labs/egregore`, or file a GitHub issue on that repo. Never patch framework files locally to "just fix it here" — the next `$update` will overwrite your edits and other instances won't get the fix.
106
+ - **Downstream framework is stale:** run `$update`.
107
+ - **Downstream framework needs a fix:** use `$contribute` or file an upstream issue; local patches are overwritten by `$update`.
108
+ - **Source framework needs a fix:** use `$save` into its configured integration and release flow; never `$contribute`.
100
109
  - **Org-level work** (memory, org-specific knowledge, managed repos, `egregore.json`): `$save` to this repo.
101
110
 
102
111
  When a user reports broken or missing framework behavior, the first question is "when did you last `$update`?"
@@ -139,7 +148,7 @@ The integration branch is `develop` unless top-level `egregore.json.base_branch`
139
148
 
140
149
  **Fallback:** If `bin/agent.sh branch` fails, resolve `{base}` with
141
150
  `_get_base_branch`, then use
142
- `git checkout -b dev/{author}/{slug} origin/{base}`.
151
+ `git checkout --no-track -b dev/{author}/{slug} origin/{base}`.
143
152
 
144
153
  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 &`
145
154
 
@@ -205,9 +214,11 @@ The `.codex/hooks/branch-guard.js` PreToolUse hook (launcher `--enable hooks`) p
205
214
  Wait for the reply, then branch.
206
215
  - **The user explicitly requested the protected branch** → that request is consent. Record it with `echo '{branch}' > .egregore-branch-consent`, then retry. Never create the marker merely to silence the hook.
207
216
 
208
- Memory, managed-repo, and runtime-state writes bypass the project guard — if one triggers it, correct the target/context instead of asking for consent.
217
+ If this Codex build does not support hooks, keep the same rule.
209
218
 
210
- If this Codex build does not support hooks, follow the same discipline as a standing instruction: never write or commit on the configured base, develop, main, or master.
219
+ **History:** Do not merge, cherry-pick, or rebase another task merely to read
220
+ it; use `gh pr view/diff`, `git show`, or a temporary worktree. Integrate
221
+ only for dependencies. Deletion needs no rewrite unless purging secrets.
211
222
 
212
223
  ### Onboarding exception
213
224
 
@@ -355,11 +366,14 @@ branch; `base_branch: "main"` is single-branch mode. Users never interact with
355
366
  git directly.
356
367
 
357
368
  ```
358
- main ← stable (/release)
359
- develop ← integration (PRs land here)
360
- dev/{author}/{topic-slug} | feature/{slug} | bugfix/{slug}
369
+ main ← selected `/release` candidates
370
+ develop ← CL integration (available internally)
371
+ dev/{author}/{topic-slug} | feature/{slug} | bugfix/{slug}
361
372
  ```
362
373
 
374
+ - **Develop is not release**: merged PRs stay inside CL.
375
+ - **`$release` is selective**: the Release Desk queues exact PR SHAs into a candidate from main. Never merge all of develop.
376
+ - **OSS is separate**: `$sync-public` reviews delivery after private main.
363
377
  - **On launch**: syncs the configured base branch + memory. Does NOT create a branch.
364
378
  - **Branch creation**: MANDATORY on first work-related message (see above).
365
379
  - **Resuming**: rebase onto the configured base branch and continue.
@@ -187,6 +187,15 @@ base_ref() {
187
187
  fi
188
188
  }
189
189
 
190
+ clear_base_upstream() {
191
+ local repo="$1" branch="$2" base="$3" upstream=""
192
+ case "$branch" in dev/*|feature/*|bugfix/*) ;; *) return 0 ;; esac
193
+ upstream="$(git -C "$repo" rev-parse --abbrev-ref "${branch}@{upstream}" 2>/dev/null || true)"
194
+ if [ "$upstream" = "$base" ]; then
195
+ git -C "$repo" branch --unset-upstream "$branch" >/dev/null 2>&1 || true
196
+ fi
197
+ }
198
+
190
199
  cmd_protocol() {
191
200
  cat <<'EOF'
192
201
  Egregore portable agent protocol
@@ -306,8 +315,9 @@ cmd_branch() {
306
315
  if git -C "$SCRIPT_DIR" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
307
316
  git -C "$SCRIPT_DIR" checkout "$branch" --quiet
308
317
  else
309
- git -C "$SCRIPT_DIR" checkout -b "$branch" "$base" --quiet
318
+ git -C "$SCRIPT_DIR" checkout --no-track -b "$branch" "$base" --quiet
310
319
  fi
320
+ clear_base_upstream "$SCRIPT_DIR" "$branch" "$base"
311
321
  egregore_link_shared_state "$SCRIPT_DIR" "$MAIN_PROJECT_DIR" >/dev/null 2>/dev/null || true
312
322
  echo "branch: $branch"
313
323
  echo "worktree: $SCRIPT_DIR"
@@ -323,8 +333,9 @@ cmd_branch() {
323
333
  fi
324
334
 
325
335
  if ! git -C "$MAIN_PROJECT_DIR" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
326
- git -C "$MAIN_PROJECT_DIR" branch "$branch" "$base" >/dev/null 2>&1 || die "failed to create branch $branch"
336
+ git -C "$MAIN_PROJECT_DIR" branch --no-track "$branch" "$base" >/dev/null 2>&1 || die "failed to create branch $branch"
327
337
  fi
338
+ clear_base_upstream "$MAIN_PROJECT_DIR" "$branch" "$base"
328
339
 
329
340
  wt_path="$MAIN_PROJECT_DIR/.claude/worktrees/$slug"
330
341
  if [ -e "$wt_path" ]; then
@@ -356,8 +367,9 @@ ensure_working_branch() {
356
367
  if git -C "$SCRIPT_DIR" show-ref --verify --quiet "refs/heads/$branch" 2>/dev/null; then
357
368
  git -C "$SCRIPT_DIR" checkout "$branch" --quiet || die "failed to checkout $branch"
358
369
  else
359
- git -C "$SCRIPT_DIR" checkout -b "$branch" "$base" --quiet || die "failed to create $branch"
370
+ git -C "$SCRIPT_DIR" checkout --no-track -b "$branch" "$base" --quiet || die "failed to create $branch"
360
371
  fi
372
+ clear_base_upstream "$SCRIPT_DIR" "$branch" "$base"
361
373
  echo "$branch"
362
374
  }
363
375
 
@@ -193,7 +193,7 @@ The integration branch is \`develop\` unless top-level \`egregore.json.base_bran
193
193
 
194
194
  **Fallback:** If \`bin/agent.sh branch\` fails, resolve \`{base}\` with
195
195
  \`_get_base_branch\`, then use
196
- \`git checkout -b dev/{author}/{slug} origin/{base}\`.
196
+ \`git checkout --no-track -b dev/{author}/{slug} origin/{base}\`.
197
197
 
198
198
  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 &\`
199
199
 
@@ -259,9 +259,11 @@ The \`.codex/hooks/branch-guard.js\` PreToolUse hook (launcher \`--enable hooks\
259
259
  Wait for the reply, then branch.
260
260
  - **The user explicitly requested the protected branch** → that request is consent. Record it with \`echo '{branch}' > .egregore-branch-consent\`, then retry. Never create the marker merely to silence the hook.
261
261
 
262
- Memory, managed-repo, and runtime-state writes bypass the project guard — if one triggers it, correct the target/context instead of asking for consent.
262
+ If this Codex build does not support hooks, keep the same rule.
263
263
 
264
- If this Codex build does not support hooks, follow the same discipline as a standing instruction: never write or commit on the configured base, develop, main, or master.
264
+ **History:** Do not merge, cherry-pick, or rebase another task merely to read
265
+ it; use \`gh pr view/diff\`, \`git show\`, or a temporary worktree. Integrate
266
+ only for dependencies. Deletion needs no rewrite unless purging secrets.
265
267
 
266
268
  ### Onboarding exception
267
269
 
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
5
+ CONFIG="$SCRIPT_DIR/egregore.json"
6
+ EXPECTED_REPO=""
7
+
8
+ usage() {
9
+ echo "usage: bash bin/contribute-guard.sh [--config <path>] [--expect <owner/repo>]" >&2
10
+ exit 2
11
+ }
12
+
13
+ fail() {
14
+ echo "contribute: $1" >&2
15
+ exit "${2:-2}"
16
+ }
17
+
18
+ while [ $# -gt 0 ]; do
19
+ case "$1" in
20
+ --config)
21
+ [ $# -ge 2 ] || usage
22
+ CONFIG="$2"
23
+ shift 2
24
+ ;;
25
+ --expect)
26
+ [ $# -ge 2 ] || usage
27
+ EXPECTED_REPO="$2"
28
+ shift 2
29
+ ;;
30
+ --help|-h)
31
+ usage
32
+ ;;
33
+ *)
34
+ usage
35
+ ;;
36
+ esac
37
+ done
38
+
39
+ [ -f "$CONFIG" ] || fail "cannot read configuration at $CONFIG; refusing to select an upstream target"
40
+
41
+ UPSTREAM_URL=$(jq -er '
42
+ if has("upstream_url") then
43
+ if .upstream_url == null then ""
44
+ elif (.upstream_url | type) == "string" then .upstream_url
45
+ else error("upstream_url must be a string")
46
+ end
47
+ else ""
48
+ end
49
+ ' "$CONFIG" 2>/dev/null) ||
50
+ fail "configuration is invalid; refusing to select an upstream target"
51
+
52
+ if [ "$UPSTREAM_URL" = "none" ]; then
53
+ fail 'disabled because upstream_url is "none"; this checkout is a framework source, so use the save workflow for its configured integration branch' 3
54
+ fi
55
+
56
+ [ -n "$UPSTREAM_URL" ] || UPSTREAM_URL="https://github.com/egregore-labs/egregore.git"
57
+
58
+ case "$UPSTREAM_URL" in
59
+ https://github.com/*/*)
60
+ UPSTREAM_REPO="${UPSTREAM_URL#*github.com/}"
61
+ ;;
62
+ git@github.com:*/*)
63
+ UPSTREAM_REPO="${UPSTREAM_URL#git@github.com:}"
64
+ ;;
65
+ *)
66
+ fail "unsupported upstream_url '$UPSTREAM_URL'; expected a GitHub repository URL"
67
+ ;;
68
+ esac
69
+
70
+ UPSTREAM_REPO="${UPSTREAM_REPO%/}"
71
+ UPSTREAM_REPO="${UPSTREAM_REPO%.git}"
72
+ printf '%s\n' "$UPSTREAM_REPO" | grep -Eq '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$' ||
73
+ fail "invalid upstream repository '$UPSTREAM_REPO'; refusing external Git operations"
74
+
75
+ if [ -n "$EXPECTED_REPO" ] && [ "$EXPECTED_REPO" != "$UPSTREAM_REPO" ]; then
76
+ fail "target changed from '$EXPECTED_REPO' to '$UPSTREAM_REPO'; refusing external Git operations"
77
+ fi
78
+
79
+ printf '%s\n' "$UPSTREAM_REPO"
@@ -135,7 +135,7 @@ if [ -z "$CURRENT_BRANCH" ] || [ "$CURRENT_BRANCH" = "$BASE_BRANCH" ] \
135
135
  # hash from the autosave sweep) — same-name pushes wedge on non-FF.
136
136
  NEW_BRANCH="dev/${AUTHOR}/${KIND}-${SLUG_DATE}${BRANCH_SUFFIX:+-$BRANCH_SUFFIX}"
137
137
  git fetch origin "$BASE_BRANCH" --quiet 2>/dev/null || true
138
- git checkout -b "$NEW_BRANCH" "$BASE_REF" --quiet 2>/dev/null || \
138
+ git checkout --no-track -b "$NEW_BRANCH" "$BASE_REF" --quiet 2>/dev/null || \
139
139
  git checkout "$NEW_BRANCH" --quiet 2>/dev/null || exit 0
140
140
  CURRENT_BRANCH="$NEW_BRANCH"
141
141
  fi
@@ -32,6 +32,22 @@ if [ -z "${BASE_BRANCH:-}" ]; then
32
32
  fi
33
33
  fi
34
34
 
35
+ # Migrate task branches created by older Egregore versions. `git branch
36
+ # <task> origin/<base>` implicitly made the task branch track the protected
37
+ # integration branch, so a bare push could target it when push.default was
38
+ # `upstream`. Task branches remain unpublished until the explicit save/push
39
+ # flow creates their same-name remote and sets that as upstream.
40
+ _CURRENT_TASK_BRANCH="$(git branch --show-current 2>/dev/null || true)"
41
+ case "$_CURRENT_TASK_BRANCH" in
42
+ dev/*|feature/*|bugfix/*)
43
+ _CURRENT_TASK_UPSTREAM="$(git rev-parse --abbrev-ref '@{upstream}' 2>/dev/null || true)"
44
+ if [ "$_CURRENT_TASK_UPSTREAM" = "origin/$BASE_BRANCH" ]; then
45
+ git branch --unset-upstream "$_CURRENT_TASK_BRANCH" >/dev/null 2>&1 || true
46
+ fi
47
+ ;;
48
+ esac
49
+ unset _CURRENT_TASK_BRANCH _CURRENT_TASK_UPSTREAM
50
+
35
51
  # --- Ensure pull.rebase is set (prevents "divergent branches" errors) ---
36
52
  git config pull.rebase true 2>/dev/null || true
37
53
 
@@ -13,7 +13,7 @@
13
13
  # - Telemetry emit
14
14
  # - First session welcome
15
15
  #
16
- # Inputs: SCRIPT_DIR, STATE_FILE, CONFIG, CTX_DIR, BRANCH, COMMITS_AHEAD,
16
+ # Inputs: SCRIPT_DIR, STATE_FILE, CONFIG, CTX_DIR, BRANCH, BASE_BRANCH, COMMITS_AHEAD,
17
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
@@ -515,5 +515,5 @@ else
515
515
  echo ""
516
516
  echo "IMPORTANT: Display the above greeting to the user exactly as-is (preserve the ASCII art formatting and ornamented status) on their first message. Then ask: What are you working on?"
517
517
  echo ""
518
- echo "BRANCH RULE: When the user responds with what they're working on, your FIRST action is to create a working branch: git fetch origin develop --quiet && git checkout -b dev/{author}/{topic-slug} origin/develop. Do this BEFORE any other work. Derive the topic slug from their description. If they ask a pure question with no work intent, skip branching."
518
+ echo "BRANCH RULE: When the user responds with what they're working on, your FIRST action is to create a working branch: git fetch origin $BASE_BRANCH --quiet && git checkout --no-track -b dev/{author}/{topic-slug} origin/$BASE_BRANCH. Do this BEFORE any other work. Derive the topic slug from their description. If they ask a pure question with no work intent, skip branching."
519
519
  fi