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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.20.1
2
+ // egregore-launcher-version: 0.20.2
3
3
  //
4
4
  // egregore — launch and browse Egregore instances from your terminal.
5
5
  // Installed by create-egregore. Reads ~/.egregore/instances.json.
@@ -33,7 +33,7 @@ const crypto = require("node:crypto");
33
33
  const { spawnSync, spawn } = require("node:child_process");
34
34
  const readline = require("node:readline");
35
35
 
36
- const VERSION = "0.20.1";
36
+ const VERSION = "0.20.2";
37
37
  const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
38
38
  const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
39
39
  const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
@@ -702,7 +702,9 @@ function codexSupportMessage(egregoreDir) {
702
702
  " .codex/hooks.json",
703
703
  " .codex/hooks/",
704
704
  "",
705
- "Run npx -y create-egregore@latest to install the packaged Codex runtime, then rerun egregore.",
705
+ "Automatic repair did not complete. Retry with:",
706
+ " npx -y create-egregore@latest --refresh-launcher",
707
+ "Then rerun egregore.",
706
708
  "Egregore workflows appear as slash commands. In Codex, invoke them with the matching $skill token or ask in natural language.",
707
709
  ].join("\n");
708
710
  }
@@ -756,6 +758,27 @@ function primeSupportMessage(egregoreDir) {
756
758
  ].join("\n");
757
759
  }
758
760
 
761
+ function repairPortableRuntime(egregoreDir, runtime) {
762
+ const label = runtimeLabel(runtime);
763
+ process.stdout.write(`\n ${CYAN}↻${RESET} ${BOLD}Repairing ${label} support${RESET}${DIM} — updating this checkout${RESET}\n`);
764
+ const result = spawnSync(
765
+ "npx",
766
+ ["-y", `${PUBLIC_NPX_PACKAGE}@latest`, "--refresh-launcher"],
767
+ {
768
+ cwd: egregoreDir,
769
+ stdio: "inherit",
770
+ env: { ...process.env, EGREGORE_RUNTIME_REPAIR: runtime },
771
+ timeout: 3 * 60 * 1000,
772
+ },
773
+ );
774
+ if (result.error || result.status !== 0) {
775
+ process.stderr.write(` ${YELLOW}!${RESET} Automatic ${label} repair failed; the checkout was left in place.\n`);
776
+ return false;
777
+ }
778
+ process.stdout.write(` ${GREEN}✓${RESET} ${label} support repaired\n\n`);
779
+ return true;
780
+ }
781
+
759
782
  // Minimal y/N line prompt for pre-launch decisions (the TUI select machinery
760
783
  // is torn down by the time launch() runs).
761
784
  function confirmLine(question) {
@@ -886,7 +909,9 @@ async function launch(inst, runtimeOverride) {
886
909
  }
887
910
  if (runtime === "codex") {
888
911
  if (!hasCodexCoreSupport(inst.path)) {
889
- die(codexSupportMessage(inst.path));
912
+ if (!repairPortableRuntime(inst.path, runtime) || !hasCodexCoreSupport(inst.path)) {
913
+ die(codexSupportMessage(inst.path));
914
+ }
890
915
  }
891
916
  const skillsInstalled = installCodexSkills(inst.path);
892
917
  const cardRendered = await renderCodexStartupCard(inst.path);
@@ -894,7 +919,10 @@ async function launch(inst, runtimeOverride) {
894
919
  die(codexSupportMessage(inst.path));
895
920
  }
896
921
  } else if (runtime === "pi") {
897
- if (!hasPiCoreSupport(inst.path)) die(piSupportMessage(inst.path));
922
+ if (!hasPiCoreSupport(inst.path) &&
923
+ (!repairPortableRuntime(inst.path, runtime) || !hasPiCoreSupport(inst.path))) {
924
+ die(piSupportMessage(inst.path));
925
+ }
898
926
  }
899
927
  let launchArgs = null;
900
928
  if (runtime === "prime") {
@@ -903,7 +931,10 @@ async function launch(inst, runtimeOverride) {
903
931
  // check + the trust-flag probe, which shells out to the binary), then a
904
932
  // single handoff line explains the remaining in-harness startup.
905
933
  const stopLoader = startSpinner("Starting Prime Agent");
906
- const coreOk = hasPrimeCoreSupport(inst.path);
934
+ let coreOk = hasPrimeCoreSupport(inst.path);
935
+ if (!coreOk && repairPortableRuntime(inst.path, runtime)) {
936
+ coreOk = hasPrimeCoreSupport(inst.path);
937
+ }
907
938
  launchArgs = coreOk ? runtimeLaunchArgs(runtime, inst.path) : null;
908
939
  stopLoader();
909
940
  if (!coreOk) die(primeSupportMessage(inst.path));
@@ -2102,7 +2133,7 @@ function runSettings(inst, args) {
2102
2133
  return { status: 127, stdout: "", stderr: "bin/settings.sh not found — update this egregore (run /pull) to get settings.\n", missing: true };
2103
2134
  }
2104
2135
  try {
2105
- return spawnSync("bash", [script, ...args], { cwd: inst.path, stdio: "pipe", encoding: "utf-8", timeout: 15000 });
2136
+ return spawnSync("bash", [script, ...args], { cwd: inst.path, stdio: "pipe", encoding: "utf-8", timeout: 45000 });
2106
2137
  } catch (err) {
2107
2138
  return { status: 1, stdout: "", stderr: String(err) };
2108
2139
  }
@@ -2117,13 +2148,13 @@ function settingsDump(inst) {
2117
2148
  async function cmdSettings(args) {
2118
2149
  const registry = readRegistry();
2119
2150
  if (registry.length === 0) die("no egregores installed yet.\n run: npx -y create-egregore@latest");
2120
- const DOMAINS = new Set(["hosting", "repo", "admin", "people", "dump"]);
2151
+ const DOMAINS = new Set(["hosting", "workflow", "repo", "admin", "people", "dump"]);
2121
2152
  let rest = args.slice();
2122
2153
  let slug = "";
2123
2154
  if (rest.length && !DOMAINS.has(rest[0])) { slug = rest[0]; rest = rest.slice(1); }
2124
2155
  const inst = resolveInviteInstance(registry, slug);
2125
2156
  if (!inst) {
2126
- die(`multiple egregores installed; choose one:\n egregore settings <slug> [hosting|repo|admin]\n run: egregore ls`);
2157
+ die(`multiple egregores installed; choose one:\n egregore settings <slug> [hosting|workflow|repo|admin|people]\n run: egregore ls`);
2127
2158
  }
2128
2159
  // Interactive editing lives in the launcher (press `s`). With no verb, print
2129
2160
  // the snapshot; otherwise forward straight to the deterministic verb.
@@ -2421,6 +2452,14 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
2421
2452
  } else {
2422
2453
  const rows = [
2423
2454
  { name: "Hosted Artifacts", value: (d.hosting !== false) ? `${GREEN}ON${RESET}` : `${YELLOW}OFF${RESET}` },
2455
+ {
2456
+ name: "Git workflow",
2457
+ value: d.workflow === "simple"
2458
+ ? `${DIM}PRs → main${RESET}`
2459
+ : d.workflow === "staged"
2460
+ ? `${DIM}PRs → develop → main${RESET}`
2461
+ : `${DIM}PRs → ${d.base_branch || "custom"}${RESET}`,
2462
+ },
2424
2463
  { name: "Managed repos", value: `${DIM}${(d.repos || []).length}${RESET}` },
2425
2464
  { name: "Admins", value: `${DIM}${(d.admins || []).length}${RESET}` },
2426
2465
  { name: "People", value: `${DIM}${(d.people || []).length}${RESET}` },
@@ -2433,7 +2472,7 @@ function renderSettingsPanel({ inst, data, cursor, view, domain, items, listCurs
2433
2472
  });
2434
2473
  lines.push(emptyBoxLine(innerWidth, margin));
2435
2474
  if (notice) lines.push(boxLine(notice, innerWidth, margin));
2436
- lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}enter${RESET}${DIM} ${cursor === 0 ? "toggle" : "open"} · ← back · q quit${RESET}`, innerWidth, margin));
2475
+ lines.push(boxLine(`${DIM}↑↓ nav · ${RESET}${BOLD}enter${RESET}${DIM} ${cursor < 2 ? "toggle" : "open"} · ← back · q quit${RESET}`, innerWidth, margin));
2437
2476
  }
2438
2477
 
2439
2478
  lines.push(emptyBoxLine(innerWidth, margin));
@@ -2891,15 +2930,19 @@ async function browser(args = {}) {
2891
2930
 
2892
2931
  // menu view
2893
2932
  if (key.name === "escape" || key.name === "left") { S.settingsInst = null; S.settingsNotice = ""; render(); return; }
2894
- if (key.name === "up" || key.name === "k") { S.settingsCursor = (S.settingsCursor - 1 + 4) % 4; render(); return; }
2895
- if (key.name === "down" || key.name === "j") { S.settingsCursor = (S.settingsCursor + 1) % 4; render(); return; }
2933
+ if (key.name === "up" || key.name === "k") { S.settingsCursor = (S.settingsCursor - 1 + 5) % 5; render(); return; }
2934
+ if (key.name === "down" || key.name === "j") { S.settingsCursor = (S.settingsCursor + 1) % 5; render(); return; }
2896
2935
  if (key.name === "return" || chunk === " ") {
2897
2936
  if (S.settingsCursor === 0) {
2898
2937
  const on = (S.settingsData && S.settingsData.hosting) !== false;
2899
2938
  settingsRunVerb(["hosting", on ? "off" : "on"]);
2900
2939
  render();
2940
+ } else if (S.settingsCursor === 1) {
2941
+ const simple = (S.settingsData && S.settingsData.workflow) === "simple";
2942
+ settingsRunVerb(["workflow", simple ? "staged" : "simple"]);
2943
+ render();
2901
2944
  } else {
2902
- const domain = S.settingsCursor === 1 ? "repo" : S.settingsCursor === 2 ? "admin" : "people";
2945
+ const domain = S.settingsCursor === 2 ? "repo" : S.settingsCursor === 3 ? "admin" : "people";
2903
2946
  S.settingsView = "list"; S.settingsDomain = domain;
2904
2947
  S.settingsItems = settingsItemsFor(S.settingsData, domain);
2905
2948
  S.settingsListCursor = 0; S.settingsMode = "browse"; S.settingsNotice = "";
@@ -3155,7 +3198,7 @@ async function browser(args = {}) {
3155
3198
  return;
3156
3199
  }
3157
3200
  state.settingsInst = pick;
3158
- state.settingsData = settingsDump(pick) || { hosting: true, repos: [], admins: [], people: [] };
3201
+ state.settingsData = settingsDump(pick) || { hosting: true, workflow: "staged", base_branch: "develop", repos: [], admins: [], people: [] };
3159
3202
  state.settingsCursor = 0;
3160
3203
  state.settingsView = "menu";
3161
3204
  state.settingsNotice = "";
package/lib/local.js CHANGED
@@ -31,7 +31,6 @@ const {
31
31
  configureGitCredentials,
32
32
  gitCloneEnv,
33
33
  initMemoryDirs,
34
- ensureDevelop,
35
34
  ensurePersonOnboardingWitness,
36
35
  ensureInstallerMemberRole,
37
36
  run,
@@ -1031,7 +1030,6 @@ async function localFounderFlow(ui, ctx = {}) {
1031
1030
 
1032
1031
  const githubOrg = st.githubOrg;
1033
1032
  const isOrg = st.isOrg;
1034
- const isNewProject = st.isNewProject;
1035
1033
  const orgName = st.orgName;
1036
1034
  const description = st.description;
1037
1035
  let newProjectRepo = st.newProjectRepo;
@@ -1156,13 +1154,14 @@ async function localFounderFlow(ui, ctx = {}) {
1156
1154
  const s3 = ui.spinner(`Creating ${newProjectRepo}...`);
1157
1155
  const projExists = await repoExists(githubToken, githubOrg, newProjectRepo);
1158
1156
  if (projExists) {
1159
- selectedRepos.push({ name: newProjectRepo });
1157
+ const branchInfo = await detectRepoBranches(githubToken, githubOrg, newProjectRepo).catch(() => null);
1158
+ selectedRepos.push({ name: newProjectRepo, base_branch: branchInfo?.defaultBranch || "main" });
1160
1159
  s3.stop(`${newProjectRepo} already exists — added to managed repos`);
1161
1160
  } else {
1162
1161
  try {
1163
1162
  await createRepo(githubToken, githubOrg, newProjectRepo, isOrg, description || "");
1164
1163
  if (await waitForRepo(githubToken, githubOrg, newProjectRepo)) {
1165
- selectedRepos.push({ name: newProjectRepo });
1164
+ selectedRepos.push({ name: newProjectRepo, base_branch: "main" });
1166
1165
  s3.stop(`Created ${newProjectRepo}`);
1167
1166
  } else {
1168
1167
  s3.fail(`${newProjectRepo} creation timed out`);
@@ -1181,6 +1180,7 @@ async function localFounderFlow(ui, ctx = {}) {
1181
1180
  memory_repo: `https://github.com/${githubOrg}/${memoryRepoName}.git`,
1182
1181
  slug: slug,
1183
1182
  repo_name: repoName,
1183
+ base_branch: "main",
1184
1184
  repos: selectedRepos,
1185
1185
  upstream_url: "https://github.com/egregore-labs/egregore.git",
1186
1186
  report_url: "https://xgksfrirtdumacvmfkzj.supabase.co",
@@ -1241,18 +1241,6 @@ async function localFounderFlow(ui, ctx = {}) {
1241
1241
  ui.step(1, totalSteps, "Cloning egregore...");
1242
1242
  cloneRepo(forkUrl, egregoreDir, githubToken, ui, "egregore");
1243
1243
  setGitIdentity(egregoreDir, user);
1244
- ensureDevelop(egregoreDir);
1245
- // ensureDevelop only creates develop on the REMOTE (git branch, no checkout),
1246
- // so the clone is still on main. The egregore operates on develop, so switch
1247
- // to it now — otherwise local overlays (the codex runtime) get committed to
1248
- // main and are orphaned when session-start later checks out develop.
1249
- try {
1250
- execFileSync("git", ["-C", egregoreDir, "checkout", "develop"], { stdio: "pipe" });
1251
- } catch {
1252
- try {
1253
- execFileSync("git", ["-C", egregoreDir, "checkout", "-b", "develop", "origin/develop"], { stdio: "pipe" });
1254
- } catch {}
1255
- }
1256
1244
 
1257
1245
  ui.step(2, totalSteps, "Cloning shared memory...");
1258
1246
  cloneRepo(memoryUrl, memoryDir, githubToken, ui, "memory");
@@ -1356,10 +1344,6 @@ async function localFounderFlow(ui, ctx = {}) {
1356
1344
  const repoUrl = `https://github.com/${githubOrg}/${mrName}.git`;
1357
1345
  cloneRepo(repoUrl, repoDir, githubToken, ui, mrName);
1358
1346
  setGitIdentity(repoDir, user);
1359
- // New repos get develop branch (our workflow). Existing repos keep their default.
1360
- if (isNewProject) {
1361
- ensureDevelop(repoDir);
1362
- }
1363
1347
  } catch {
1364
1348
  ui.warn(`Could not clone ${mrName} — you may not have access.`);
1365
1349
  }
package/lib/setup.js CHANGED
@@ -435,7 +435,6 @@ async function install(data, ui, targetDir, opts = {}) {
435
435
  }
436
436
  }
437
437
  try { run("git config credential.helper store", { cwd: egregoreDir }); } catch {}
438
- ensureDevelop(egregoreDir);
439
438
  ui.success("Cloned egregore");
440
439
 
441
440
  // Set repo-local git identity from GitHub user (not machine-global config)
@@ -587,7 +586,6 @@ async function install(data, ui, targetDir, opts = {}) {
587
586
  }
588
587
  }
589
588
  try { run("git config credential.helper store", { cwd: repoDir }); } catch {}
590
- ensureDevelop(repoDir);
591
589
  clonedRepos.push(repoName);
592
590
  ui.success(`Cloned ${repoName}`);
593
591
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.20.1",
3
+ "version": "0.20.2",
4
4
  "description": "Set up Egregore for your team in one command \u2014 installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -13,7 +13,7 @@
13
13
  "build:runtime": "node ../../bin/capability-distribution.mjs generate-skill-view --root ../.. && node scripts/build-runtime-bundle.mjs && node scripts/build-pi-runtime-bundle.mjs && node scripts/build-prime-runtime-bundle.mjs && node ../../api/scripts/build-connect-runtime-pack.mjs",
14
14
  "build:pi-runtime": "node scripts/build-pi-runtime-bundle.mjs",
15
15
  "build:prime-runtime": "node scripts/build-prime-runtime-bundle.mjs",
16
- "test": "node ../../bin/capability-distribution.mjs validate --root ../.. && node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node scripts/build-prime-runtime-bundle.mjs --check && node ../../bin/prime-render-spec.mjs --check && node ../../bin/pi-render-spec.mjs --check && node ../../api/scripts/build-connect-runtime-pack.mjs --check && node --test test/capability-distribution.test.js test/connect-runtime.test.js test/context.test.js test/path-lookup.test.js test/nixos-portability.test.js test/parse-join-target.test.js test/skip-repos.test.js test/install-origin-guard.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/prime-runtime.test.js test/prime-branch-policy.test.js test/prime-bridge-skill.test.js test/prime-session-smoke.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/installer-connect-choice.test.js test/launcher-connected-status.test.js test/launcher-permissions.test.js test/launcher-update.test.js test/connected-config.test.js",
16
+ "test": "node ../../bin/capability-distribution.mjs validate --root ../.. && node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node scripts/build-prime-runtime-bundle.mjs --check && node ../../bin/prime-render-spec.mjs --check && node ../../bin/pi-render-spec.mjs --check && node ../../api/scripts/build-connect-runtime-pack.mjs --check && node --test test/capability-distribution.test.js test/connect-runtime.test.js test/context.test.js test/path-lookup.test.js test/nixos-portability.test.js test/parse-join-target.test.js test/skip-repos.test.js test/install-origin-guard.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/prime-runtime.test.js test/prime-branch-policy.test.js test/prime-bridge-skill.test.js test/prime-session-smoke.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/installer-connect-choice.test.js test/launcher-connected-status.test.js test/launcher-permissions.test.js test/launcher-update.test.js test/connected-config.test.js test/workflow-settings.test.js",
17
17
  "test:e2e": "node --test test/e2e-local.test.js",
18
18
  "test:all": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/"
19
19
  },
@@ -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
  {
@@ -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