create-egregore 0.19.13 → 0.19.15

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.19.13
2
+ // egregore-launcher-version: 0.19.15
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.19.13";
36
+ const VERSION = "0.19.15";
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");
@@ -701,6 +701,24 @@ function installCodexSkills(egregoreDir) {
701
701
  }
702
702
  }
703
703
 
704
+ function claudeLaunchArgs(egregoreDir) {
705
+ const args = ["start"];
706
+ const memoryLink = path.join(egregoreDir, "memory");
707
+ try {
708
+ const memoryDir = fs.realpathSync(memoryLink);
709
+ if (fs.statSync(memoryDir).isDirectory()) {
710
+ // Egregore memory is intentionally a sibling Git repository. Declare it
711
+ // as part of Claude's workspace so auto mode does not classify normal
712
+ // organizational recall as an unexplained cross-project read.
713
+ args.push("--add-dir", memoryDir);
714
+ }
715
+ } catch {
716
+ // A missing/dangling memory link is diagnosed by session startup. Do not
717
+ // prevent the harness from opening.
718
+ }
719
+ return args;
720
+ }
721
+
704
722
  function runtimeLaunchArgs(runtime, egregoreDir) {
705
723
  if (runtime === "codex") {
706
724
  return [
@@ -711,7 +729,7 @@ function runtimeLaunchArgs(runtime, egregoreDir) {
711
729
  ];
712
730
  }
713
731
  if (runtime === "pi") return piTrustArgs();
714
- return ["start"];
732
+ return claudeLaunchArgs(egregoreDir);
715
733
  }
716
734
 
717
735
  async function launch(inst, runtimeOverride) {
@@ -1098,14 +1116,14 @@ function cmdCliUpdate(latest) {
1098
1116
  { encoding: "utf-8", stdio: ["inherit", "pipe", "pipe"], env: { ...process.env } },
1099
1117
  );
1100
1118
  if (result.status !== 0) {
1101
- console.log(`\r [1/2] ${YELLOW}!${RESET} Install failed`);
1119
+ console.log(`\r${ANSI.clearLine} [1/2] ${YELLOW}!${RESET} Install failed`);
1102
1120
  const detail = String(result.stderr || result.stdout || "").trim().split("\n").filter(Boolean).pop();
1103
1121
  if (detail) console.error(` ${DIM}${detail}${RESET}`);
1104
1122
  console.error(` Retry: npx -y ${PUBLIC_NPX_PACKAGE}@${latest} --refresh-launcher\n`);
1105
1123
  process.exitCode = 1;
1106
1124
  return;
1107
1125
  }
1108
- console.log(`\r [1/2] ${GREEN}✓${RESET} CLI ${latest} installed`);
1126
+ console.log(`\r${ANSI.clearLine} [1/2] ${GREEN}✓${RESET} CLI ${latest} installed`);
1109
1127
 
1110
1128
  if (process.env.EGREGORE_UPDATE_NO_RELAUNCH === "1") {
1111
1129
  console.log(` [2/2] ${GREEN}✓${RESET} Ready for the next launch\n`);
@@ -1250,12 +1268,42 @@ async function cmdConnect(args) {
1250
1268
 
1251
1269
  let intentId = activation.intent_id || "";
1252
1270
  let connectUrl = activation.connect_url || "";
1253
- if (!intentId || !["checkout_pending", "entitled", "backfilling"].includes(activation.status)) {
1271
+ let ticketExpiresAt = Number(activation.connect_ticket_expires_at || 0) * 1000;
1272
+ const resumable = ["created", "checkout_pending", "entitled", "provisioning", "backfilling"].includes(
1273
+ activation.status,
1274
+ );
1275
+ if (
1276
+ intentId &&
1277
+ resumable &&
1278
+ (!activation.connect_ticket || !ticketExpiresAt || ticketExpiresAt <= Date.now() + 60_000)
1279
+ ) {
1280
+ const refreshed = await connectedApiRequest(
1281
+ githubToken,
1282
+ "POST",
1283
+ `/api/billing/connect/${encodeURIComponent(intentId)}/ticket`,
1284
+ {},
1285
+ );
1286
+ if (refreshed.status !== 200) {
1287
+ die(`could not refresh Connected link: ${connectError(refreshed, `HTTP ${refreshed.status}`)}`);
1288
+ }
1289
+ connectUrl = refreshed.data.connect_url;
1290
+ ticketExpiresAt = Number(refreshed.data.connect_ticket_expires_at || 0) * 1000;
1291
+ persistConnectState(inst, {
1292
+ connect_ticket: refreshed.data.connect_ticket,
1293
+ connect_ticket_expires_at: refreshed.data.connect_ticket_expires_at,
1294
+ connect_url: connectUrl,
1295
+ status: refreshed.data.status || activation.status,
1296
+ });
1297
+ }
1298
+ if (
1299
+ !intentId ||
1300
+ !resumable
1301
+ ) {
1254
1302
  console.log(` [1/5] ${DIM}Creating connection intent…${RESET}`);
1255
1303
  const checkout = await connectedApiRequest(
1256
1304
  githubToken,
1257
1305
  "POST",
1258
- "/api/billing/checkout",
1306
+ "/api/billing/connect-intents",
1259
1307
  {
1260
1308
  org_slug: cfg.slug || inst.slug,
1261
1309
  plan: "connect",
@@ -1275,9 +1323,10 @@ async function cmdConnect(args) {
1275
1323
  connectUrl = checkout.data.connect_url;
1276
1324
  persistConnectState(inst, {
1277
1325
  intent_id: intentId,
1278
- checkout_session_id: checkout.data.checkout_session_id,
1326
+ connect_ticket: checkout.data.connect_ticket,
1327
+ connect_ticket_expires_at: checkout.data.connect_ticket_expires_at,
1279
1328
  connect_url: connectUrl,
1280
- status: "checkout_pending",
1329
+ status: checkout.data.status || "created",
1281
1330
  });
1282
1331
  console.log(` [1/5] ${GREEN}✓${RESET} Connection intent`);
1283
1332
  } else {
@@ -1369,12 +1418,17 @@ async function cmdConnect(args) {
1369
1418
  let backfillSummary = { replayed: true };
1370
1419
  try { backfillSummary = JSON.parse((sync.stdout || "").trim()); } catch {}
1371
1420
 
1372
- const complete = await connectedApiRequest(
1373
- githubToken,
1374
- "POST",
1375
- `/api/billing/connect/${encodeURIComponent(intentId)}/complete`,
1376
- { backfill_summary: backfillSummary },
1377
- );
1421
+ let complete;
1422
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1423
+ complete = await connectedApiRequest(
1424
+ githubToken,
1425
+ "POST",
1426
+ `/api/billing/connect/${encodeURIComponent(intentId)}/complete`,
1427
+ { backfill_summary: backfillSummary },
1428
+ );
1429
+ if (complete.status === 200 || complete.status !== 0) break;
1430
+ await sleep(250 * (attempt + 1));
1431
+ }
1378
1432
  if (complete.status !== 200) {
1379
1433
  die(`graph replay finished, but activation could not be finalized: ${connectError(complete, `HTTP ${complete.status}`)}`);
1380
1434
  }
@@ -1839,6 +1893,11 @@ function renderHeroPanel({ selected, pv, width, height, countSuffix, filter, fil
1839
1893
  const top = `${margin}${DIM}┌${"─".repeat(2)}${label}${"─".repeat(Math.max(0, innerWidth - label.length - 2))}┐${RESET}`;
1840
1894
  const bottom = `${margin}${DIM}└${"─".repeat(innerWidth)}┘${RESET}`;
1841
1895
  const mid = `${margin}${DIM}├${"─".repeat(innerWidth)}┤${RESET}`;
1896
+ const updateBand = cliUpdate?.available
1897
+ ? (colorOn
1898
+ ? `${margin}${DIM}│${RESET}${E(43)}${E(30)}${pad(` CLI UPDATE AVAILABLE · ${cliUpdate.current} → ${cliUpdate.latest} · ${E(1)}U${E(22)} INSTALL`, innerWidth)}${RESET}${DIM}│${RESET}`
1899
+ : `${margin}│${pad(` CLI UPDATE AVAILABLE · ${cliUpdate.current} → ${cliUpdate.latest} · U INSTALL`, innerWidth)}│`)
1900
+ : null;
1842
1901
  const lines = [top];
1843
1902
 
1844
1903
  const title = selected ? `${selected.slug || selected.name || "egregore"}` : "egregore";
@@ -1879,20 +1938,10 @@ function renderHeroPanel({ selected, pv, width, height, countSuffix, filter, fil
1879
1938
  lines.push(boxLine(`${BOLD}${CYAN}EGREGORE${RESET} ${DIM}v${VERSION} · ${countSuffix} · ${filterText}${RESET}`, innerWidth, margin));
1880
1939
  }
1881
1940
  lines.push(emptyBoxLine(innerWidth, margin));
1882
- lines.push(mid);
1941
+ lines.push(updateBand || mid);
1883
1942
  lines.push(boxLine(`${BOLD}${title}${RESET} ${DIM}· ${org} · ${runtime} · ${countSuffix} · ${filterText}${RESET}`, innerWidth, margin));
1884
1943
  lines.push(boxLine(`${DIM}skills${RESET} ${skillStatus}`, innerWidth, margin));
1885
- if (cliUpdate?.available) {
1886
- lines.push(boxLine(
1887
- `${YELLOW}→ CLI update ${cliUpdate.latest} available${RESET} ${DIM}· installed ${cliUpdate.current} · ${RESET}${BOLD}U${RESET}${DIM} install${RESET}`,
1888
- innerWidth,
1889
- margin,
1890
- ));
1891
- } else {
1892
- // Reserve the status row so the list never jumps when the async version
1893
- // check resolves and reveals an available release.
1894
- lines.push(emptyBoxLine(innerWidth, margin));
1895
- }
1944
+ lines.push(emptyBoxLine(innerWidth, margin));
1896
1945
 
1897
1946
  const sigilWidth = Math.max(...EGREGORE_SIGIL.map((line) => line.length));
1898
1947
  const twoColumn = bodyWidth >= 68;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bash
2
- # egregore-launcher-version: 0.4.9
2
+ # egregore-launcher-version: 0.4.10
3
3
  # egregore — launch an Egregore instance in Claude Code.
4
4
  # Reads ~/.egregore/instances.json for the registry of installed egregores.
5
5
  #
@@ -22,7 +22,7 @@
22
22
 
23
23
  set -u
24
24
 
25
- VERSION="0.4.9"
25
+ VERSION="0.4.10"
26
26
  REGISTRY="${HOME}/.egregore/instances.json"
27
27
  DEFAULT_FILE="${HOME}/.egregore/default"
28
28
 
@@ -93,7 +93,11 @@ launch() {
93
93
  printf ' %s→%s launching %s%s%s ...\n\n' "$DIM" "$RESET" "$BOLD" "$target_slug" "$RESET"
94
94
  fi
95
95
  cd "$target_path" || die "Could not cd into $target_path"
96
- exec claude start
96
+ local -a claude_args=("start")
97
+ if [ -d "$target_path/memory" ]; then
98
+ claude_args+=("--add-dir" "$target_path/memory")
99
+ fi
100
+ exec claude "${claude_args[@]}"
97
101
  }
98
102
 
99
103
  cmd_ls() {
package/lib/setup.js CHANGED
@@ -426,6 +426,15 @@ async function install(data, ui, targetDir, opts = {}) {
426
426
  ui.success("Linked");
427
427
  }
428
428
 
429
+ // The connected config is required infrastructure, not an optional overlay.
430
+ // A setup race once left develop behind main, so a newly-created work branch
431
+ // could lose egregore.json even though the default branch contained it.
432
+ // Recover that exact file from origin/main when possible, then validate it
433
+ // before writing credentials or registering a launcher.
434
+ if (api_key || data.api_url) {
435
+ ensureConnectedConfig(egregoreDir, data, ui);
436
+ }
437
+
429
438
  // 4. Write .env (secrets only — never committed to git)
430
439
  ui.step(4, totalSteps, "Writing credentials...");
431
440
  const envPath = path.join(egregoreDir, ".env");
@@ -450,19 +459,7 @@ async function install(data, ui, targetDir, opts = {}) {
450
459
  fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
451
460
  try { ensurePersonOnboardingWitness(memoryDir, state); } catch {}
452
461
 
453
- // 4c. Ensure mode is set in egregore.json (connected mode for API-based setup)
454
- const configPath = path.join(egregoreDir, "egregore.json");
455
- if (fs.existsSync(configPath)) {
456
- try {
457
- const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
458
- if (!config.mode) {
459
- config.mode = "connected";
460
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
461
- }
462
- } catch {}
463
- }
464
-
465
- // 4d. Overlay the selected shell-runtime support. Claude Code reads
462
+ // 4c. Overlay the selected shell-runtime support. Claude Code reads
466
463
  // CLAUDE.md, not AGENTS.md; overlaying the Codex AGENTS.md/.codex into a Claude
467
464
  // workspace gives a wandering agent a competing startup protocol (the
468
465
  // xenocracy onboarding regression). Claude joiners keep the cloned repo as-is —
@@ -584,6 +581,74 @@ async function install(data, ui, targetDir, opts = {}) {
584
581
  await launchOrInstruct(path.join(base, forkDirName), alias, ui, opts);
585
582
  }
586
583
 
584
+ /**
585
+ * Require and validate the connected instance configuration.
586
+ *
587
+ * If the current branch is missing egregore.json, restore it from origin/main
588
+ * without changing branches or disturbing existing work. A missing/invalid
589
+ * remote config aborts setup before credentials and launcher registration.
590
+ */
591
+ function ensureConnectedConfig(repoDir, data, ui) {
592
+ const configPath = path.join(repoDir, "egregore.json");
593
+
594
+ if (!fs.existsSync(configPath)) {
595
+ try {
596
+ execFileSync(
597
+ "git",
598
+ ["-C", repoDir, "fetch", "origin", "+main:refs/remotes/origin/main"],
599
+ {
600
+ stdio: "pipe", encoding: "utf-8", timeout: 30000,
601
+ },
602
+ );
603
+ const remoteConfig = execFileSync(
604
+ "git",
605
+ ["-C", repoDir, "show", "origin/main:egregore.json"],
606
+ { stdio: "pipe", encoding: "utf-8", timeout: 10000 },
607
+ );
608
+ JSON.parse(remoteConfig);
609
+ fs.writeFileSync(configPath, remoteConfig.endsWith("\n") ? remoteConfig : `${remoteConfig}\n`);
610
+ if (ui && typeof ui.warn === "function") {
611
+ ui.warn("Restored missing egregore.json from origin/main");
612
+ }
613
+ } catch {
614
+ throw new Error(
615
+ "Connected setup is incomplete: egregore.json is missing from this checkout and origin/main. Retry setup from egregore.xyz/setup."
616
+ );
617
+ }
618
+ }
619
+
620
+ let config;
621
+ try {
622
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
623
+ } catch {
624
+ throw new Error("Connected setup is incomplete: egregore.json is not valid JSON.");
625
+ }
626
+
627
+ const required = ["org_name", "github_org", "memory_repo", "api_url", "slug"];
628
+ const missing = required.filter((key) => !config[key]);
629
+ if (missing.length) {
630
+ throw new Error(
631
+ `Connected setup is incomplete: egregore.json is missing ${missing.join(", ")}. Retry setup from egregore.xyz/setup.`
632
+ );
633
+ }
634
+ if (data.slug && config.slug !== data.slug) {
635
+ throw new Error(
636
+ `Connected setup mismatch: expected org ${data.slug}, but egregore.json belongs to ${config.slug}.`
637
+ );
638
+ }
639
+ if (data.github_org && config.github_org.toLowerCase() !== data.github_org.toLowerCase()) {
640
+ throw new Error(
641
+ `Connected setup mismatch: expected GitHub owner ${data.github_org}, but egregore.json uses ${config.github_org}.`
642
+ );
643
+ }
644
+
645
+ if (config.mode !== "connected") {
646
+ config.mode = "connected";
647
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
648
+ }
649
+ return config;
650
+ }
651
+
587
652
  /**
588
653
  * Ensure a cloned repo has a develop branch. Creates from default branch and pushes if missing.
589
654
  * Idempotent — safe to call on repos that already have develop.
@@ -752,6 +817,20 @@ function piTrustArgs() {
752
817
  }
753
818
  }
754
819
 
820
+ function claudeLaunchArgs(egregoreDir) {
821
+ const args = ["start"];
822
+ const memoryLink = path.join(egregoreDir, "memory");
823
+ try {
824
+ const memoryDir = fs.realpathSync(memoryLink);
825
+ if (fs.statSync(memoryDir).isDirectory()) {
826
+ args.push("--add-dir", memoryDir);
827
+ }
828
+ } catch {
829
+ // Session startup owns the actionable missing-memory diagnostic.
830
+ }
831
+ return args;
832
+ }
833
+
755
834
  function runtimeLaunchArgs(runtime, egregoreDir) {
756
835
  if (normalizeRuntime(runtime) === "codex") {
757
836
  return [
@@ -762,7 +841,7 @@ function runtimeLaunchArgs(runtime, egregoreDir) {
762
841
  ];
763
842
  }
764
843
  if (normalizeRuntime(runtime) === "pi") return piTrustArgs();
765
- return ["start"];
844
+ return claudeLaunchArgs(egregoreDir);
766
845
  }
767
846
 
768
847
  function startSpinner(label) {
@@ -1353,5 +1432,6 @@ module.exports = {
1353
1432
  assistantInstallCommand,
1354
1433
  initMemoryDirs,
1355
1434
  ensureDevelop,
1435
+ ensureConnectedConfig,
1356
1436
  run,
1357
1437
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.19.13",
3
+ "version": "0.19.15",
4
4
  "description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "scripts": {
13
13
  "build:runtime": "node scripts/build-runtime-bundle.mjs && node scripts/build-pi-runtime-bundle.mjs",
14
14
  "build:pi-runtime": "node scripts/build-pi-runtime-bundle.mjs",
15
- "test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.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/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/launcher-update.test.js",
15
+ "test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.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/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/launcher-update.test.js test/connected-config.test.js",
16
16
  "test:e2e": "node --test test/e2e-local.test.js",
17
17
  "test:all": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/"
18
18
  },
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // Mirror of .claude/hooks/search-hint.sh: when the user's prompt is
6
6
  // recall-shaped ("find the handoff about…", "did we decide…"), inject a
7
- // routing hint toward `bin/agent.sh search` — one ranked call over
7
+ // routing hint toward `bash bin/search.sh query` — one ranked call over
8
8
  // memory/ — instead of improvised ls/grep/graph exploration.
9
9
  //
10
10
  // Advisory only, never blocks, rate-limited once per session, exits 0.
@@ -43,6 +43,8 @@ const RECALL = new RegExp(
43
43
  "\\bwho worked on\\b",
44
44
  "\\bdidn.?t (someone|we) already\\b",
45
45
  "\\bwas there (a|any) (handoff|decision|discussion|meeting)\\b",
46
+ "\\b(our|company|org|organization|team)\\b.*\\b(pricing|price|tiers?|plans?|paid|free|strategy|policy|positioning|ownership|decision)\\b",
47
+ "\\b(pricing|price|tiers?|plans?|paid|free)\\b.*\\b(our|company|org|organization|team)\\b",
46
48
  ].join("|"),
47
49
  "i"
48
50
  );
@@ -59,7 +61,7 @@ process.stdout.write(JSON.stringify({
59
61
  hookSpecificOutput: {
60
62
  hookEventName: "UserPromptSubmit",
61
63
  additionalContext:
62
- "Routing hint: this prompt asks for recall from org memory. FIRST action: `bin/agent.sh search \"<the concept, not the literal sentence>\"` — one ranked call over all of memory/ (hybrid keyword+semantic; graph-state annotations attach automatically in connected mode). Read the top hits and cite paths. Do NOT improvise ls/grep/graph exploration first — hand-written graph queries against invented labels (e.g. :Handoff — handoffs are :Session nodes) return empty. Full spec: the search skill.",
64
+ "Routing hint: this prompt asks for recall from org memory. FIRST action: `bash bin/search.sh query \"<the concept, not the literal sentence>\"` — one ranked call over all of memory/ (hybrid keyword+semantic; graph-state annotations attach automatically in connected mode). Read the top hits and cite paths. Do NOT improvise ls/grep/graph exploration first — hand-written graph queries against invented labels (e.g. :Handoff — handoffs are :Session nodes) return empty. Full spec: the search skill.",
63
65
  },
64
66
  }) + "\n");
65
67
  process.exit(0);
@@ -11,12 +11,35 @@ search before answering from conversation context alone.
11
11
 
12
12
  ## Flow
13
13
 
14
- 1. Run the search:
14
+ 1. Organizational recall must begin with the Egregore search entry point in
15
+ step 2. Do not resolve `memory/` to its sibling Git repository, change into
16
+ that absolute path, or improvise raw grep/listing as the first retrieval
17
+ action. Use direct file reads or grep only on evidence paths returned by the
18
+ ranked search.
19
+
20
+ Before any shell, file-read, grep, or other retrieval tool call, send exactly
21
+ one standalone commentary line:
22
+
23
+ - local/filesystem-only:
24
+ `⌕ Egregore · searching your organization’s memory`
25
+ - connected mode with automatic graph enrichment:
26
+ `⌕ Egregore Connect · searching your organization’s memory and relationships`
27
+
28
+ Send the line verbatim. Do not replace it with generic narration such as
29
+ “I’ll search org memory.” It must be visible before the tool call; captured
30
+ command output does not satisfy this requirement. Emit it once even when the
31
+ retrieval needs multiple queries or source reads.
32
+
33
+ 2. Run the search:
15
34
 
16
35
  ```bash
17
- bin/agent.sh search "your query" -n 6
36
+ bash bin/search.sh query "your query" -n 6
18
37
  ```
19
38
 
39
+ - The command also prints the attribution as a direct-shell fallback. That
40
+ captured banner is diagnostic redundancy, not the user-visible product beat.
41
+ If the answer instead begins with a graph-only named read, follow the same
42
+ global Egregore Retrieval Beat before that query.
20
43
  - Default is hybrid (BM25 + vector + rerank); it falls back to keyword
21
44
  search automatically when local models aren't built, with a note.
22
45
  - `--fast` — keyword only (BM25), instant. Use for exact terms: names,
@@ -27,15 +50,15 @@ bin/agent.sh search "your query" -n 6
27
50
  state lines (status, recipient, artifact type, quest) with no flag.
28
51
  `--no-enrich` skips it when speed matters; local mode never attempts it.
29
52
 
30
- 2. Search the *concept*, not the user's literal sentence. If the first query
53
+ 3. Search the *concept*, not the user's literal sentence. If the first query
31
54
  returns weak scores, run a second with different wording — one
32
55
  keyword-ish, one conceptual.
33
56
 
34
- 3. Results are snippets with `memory/...` file paths. Read the source file
57
+ 4. Results are snippets with `memory/...` file paths. Read the source file
35
58
  for anything you will cite or act on — snippets truncate, and memory
36
59
  files carry dates, authors, and caveats that change the answer.
37
60
 
38
- 4. Answer directly, citing file paths. If results are empty or weak, say so
61
+ 5. Answer directly, citing file paths. If results are empty or weak, say so
39
62
  plainly — "nothing in memory on X" is a real answer. Do not present
40
63
  conversation-context guesses as memory.
41
64
 
@@ -50,6 +50,12 @@
50
50
  "sourceHash": "b5d9a149f1a6",
51
51
  "note": "Kept verbatim — bin/graph.sh is runtime-neutral."
52
52
  },
53
+ {
54
+ "section": "Egregore Retrieval Beat",
55
+ "action": "keep",
56
+ "sourceHash": "0c4d4715fb13",
57
+ "note": "Kept verbatim — product attribution applies across runtimes."
58
+ },
53
59
  {
54
60
  "section": "Notifications",
55
61
  "action": "keep",
@@ -95,7 +101,7 @@
95
101
  {
96
102
  "section": "Command Awareness",
97
103
  "action": "keep",
98
- "sourceHash": "ce5eb3015427",
104
+ "sourceHash": "a020fc5d9b98",
99
105
  "note": "Prepended the Codex command surface (skill tokens, native vs adapter split, $save abstraction); disambiguation map kept with /x -> $x token rendering. Tokens with no Codex skill (e.g. /loop, a Claude Code harness feature) are left as-is."
100
106
  },
101
107
  {
@@ -220,6 +220,53 @@ Run `catalog` only when the route is unclear. Named reads return bounded stable
220
220
 
221
221
  ---
222
222
 
223
+ ## Egregore Retrieval Beat
224
+
225
+ Egregore's organizational search is a product surface, not indistinguishable
226
+ agent tool use. For each user-directed lookup of organizational knowledge,
227
+ show exactly one visible attribution line:
228
+
229
+ - memory-only retrieval:
230
+ `⌕ Egregore · searching your organization’s memory`
231
+ - retrieval that actually queries the connected graph:
232
+ `⌕ Egregore Connect · searching your organization’s memory and relationships`
233
+
234
+ **Visibility is the contract.** Emit the applicable line verbatim as a
235
+ standalone assistant message before the first Bash, Grep, Glob, Read, or other
236
+ retrieval tool call. Shell/tool output does not satisfy this requirement:
237
+ Claude Code and other harnesses may collapse it. Do not paraphrase the line or
238
+ replace it with generic narration such as “I’ll search org memory.”
239
+
240
+ Use the memory form for local/filesystem-only organizational retrieval. Use the
241
+ Connect form when the retrieval route will query the connected graph, including
242
+ automatic graph enrichment in `bin/search.sh query`, a graph-only named read,
243
+ or exploratory Cypher. Emit once for the whole retrieval episode, not once per
244
+ hop. The banner printed by `bin/search.sh` remains a direct-shell fallback and
245
+ does not replace the assistant-visible beat.
246
+
247
+ **Routing is part of the contract.** For organizational history, decisions,
248
+ handoffs, meetings, people, pricing, strategy, or other shared-memory content,
249
+ the first retrieval action is:
250
+
251
+ ```bash
252
+ bash bin/search.sh query "<concept>" -n 6
253
+ ```
254
+
255
+ Do not start by resolving `memory/` to its sibling repository, changing
256
+ directory into that absolute path, or improvising `grep`/`ls` over it. The
257
+ search entry point owns keyword/semantic selection and automatically attaches
258
+ graph state in connected mode. Read or grep the returned `memory/...` source
259
+ paths only after the ranked call when verification needs the full document.
260
+ Native Grep/Glob remain the right first action for repository code, filenames,
261
+ and exact error strings—not organizational recall.
262
+
263
+ Do not emit the beat for native repository/code search, Git inspection, startup
264
+ context hydration, graph writes, ingestion projection, background sync,
265
+ maintenance, or command-internal queries that are not answering a user lookup.
266
+ Never name `Egregore Connect` unless a graph read will actually run.
267
+
268
+ ---
269
+
223
270
  ## Notifications
224
271
 
225
272
  **Connected mode only.** Always use `bin/notify.sh` for Telegram notifications — never construct API calls directly. See DEVELOPMENT.md §1 for usage examples. In local mode there are no live notifications.
@@ -306,7 +353,7 @@ Codex reserves leading `/` for built-ins, so Egregore workflows are **skills**,
306
353
  Invoke commands from user intent — don't wait for the slash. Each command file has a `## When to invoke` section. Load it for the full spec.
307
354
 
308
355
  **Core loop** — `$activity` `$dashboard` `$handoff` `$wrap` `$save` `$reflect` `$todo`
309
- **Knowledge** — `$search` `$deep-reflect` `$archive` `$note` `$add` `$meeting` `$ingest` `$scroll`
356
+ **Knowledge** — `$search` `$deep-reflect` `$archive` `$note` `$add` `$meeting` `$ingest` `$scroll` `$mock`
310
357
  **Identity** — `$me` (view profile or set display name)
311
358
  **Coordination** — `$ask` `$quest` `$issue` `$invite` `$delete-user` `$announce`
312
359
  **Connectors** — `$telegram-connect` (Telegram group setup) `$teams-connect` (Microsoft Teams channel setup)
@@ -316,9 +363,9 @@ Invoke commands from user intent — don't wait for the slash. Each command file
316
363
 
317
364
  **Disambiguation:**
318
365
  - Knowledge: `$reflect` (share-ready) · `$note` (half-baked) · `$deep-reflect` (deep research over memory — questions AND cross-referencing) · `$archive` (AI patterns)
319
- - Finding things — the retrieval layers: **Grep/ls** (Claude Code / Codex file ops — always available, OSS) · **`bin/graph.sh`** (indexed relevance + relationships/status — paid). Use grep proactively; the graph deepens it when connected. (Semantic `$search` is dormant — evals showed it only pays off on massive unstructured data.)
366
+ - Finding things — **organizational recall starts with `bash bin/search.sh query`** (ranked filesystem retrieval in OSS; automatic relationship/status enrichment in Connect). Use `bin/graph.sh` named reads first only for a pure relationship/status traversal. Use Grep/Glob first for repository code, filenames, and exact error strings—not shared-memory recall. Do not `cd` into the sibling memory repository.
320
367
  - Finding **something you generated** (a scroll, handoff, emissary, decision, any hosted egregore.xyz link): `bash bin/artifacts.sh find <query>`. Every generative surface already records into memory — hosted artifacts self-register into `memory/artifacts/` at publish (and the record is committed+pushed, so it's findable from *any* session, not just the one that made it); handoffs land in `memory/handoffs/`, emissaries in `memory/handoffs/outbound/`, decisions/findings/patterns in `memory/knowledge/`. The finder searches **all** of them, ranks title/topic matches above passing body mentions, tags each hit by type (`[document]`/`[handoff]`/`[emissary]`/`[decision]`), and surfaces the shareable URL. Findable by **content, not just name**, across all three layers — grep-first for OSS, folding in search + graph when available. *"bring me the artifact where I laid out our GTM plan"* → this.
321
- - Artifacts with questions: `$scroll` (living paper + embedded harvest, updates in place) · `$view` (static render) · `$harvest` (elicitation, no published face)
368
+ - Artifacts with questions: `$scroll` (living paper + embedded harvest, updates in place) · `$view` (static render) · `$harvest` (elicitation, no published face) · `$mock` (pre-build walkthrough of decided design — gauge per stop, verdict copy-back)
322
369
  - Status: `$dashboard` (personal) · `$activity` (org-wide)
323
370
  - Ending: `$wrap` (personal closure) · `$handoff` (notes for others) · `$save` (still working)
324
371
  - Tasks: `$todo` (personal) · `$quest` (team exploration) · `$issue` (something broken)
@@ -301,6 +301,7 @@ const RULES = {
301
301
  },
302
302
  'Config Files': { action: 'keep', note: 'Kept verbatim — shell facts, runtime-neutral.' },
303
303
  'Knowledge Graph': { action: 'keep', note: 'Kept verbatim — bin/graph.sh is runtime-neutral.' },
304
+ 'Egregore Retrieval Beat': { action: 'keep', note: 'Kept verbatim — product attribution applies across runtimes.' },
304
305
  'Notifications': { action: 'keep', note: 'Kept verbatim — bin/notify.sh is runtime-neutral.' },
305
306
  'Onboarding': { action: 'keep', note: 'Kept; /onboarding rendered as $onboarding.' },
306
307
  'Transparency Beat': { action: 'keep', note: 'Kept verbatim.' },