memgineering 0.25.3 → 0.26.0

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.
package/dist/index.js CHANGED
@@ -1325,8 +1325,8 @@ var init_exclude = __esm({
1325
1325
  [...ALWAYS_SKIP_DIRS].map((d) => fold(d))
1326
1326
  );
1327
1327
  ALWAYS_SKIP_SEGMENTS_FOLDED = ALWAYS_SKIP_SEGMENTS.map((s) => fold(s));
1328
- ALWAYS_SKIP_FILE = (basename4) => {
1329
- const b = fold(basename4);
1328
+ ALWAYS_SKIP_FILE = (basename5) => {
1329
+ const b = fold(basename5);
1330
1330
  return b.startsWith(".env") || b === ".ds_store" || b === ".memgdeny" || b === ".memgallow" || b === ".memgignore";
1331
1331
  };
1332
1332
  EMPTY_IGNORE_SET = { rules: [], unsupported: [] };
@@ -5541,6 +5541,13 @@ var init_config = __esm({
5541
5541
  * Absent on a machine set up before it was recorded.
5542
5542
  */
5543
5543
  hook: z8.enum(["on", "off"]).optional().catch(void 0),
5544
+ /**
5545
+ * Which "left your hooks alone" notices the automatic hook refresh has
5546
+ * already given, as `<file>:<reason>` — so a symlinked or unparseable
5547
+ * settings file is mentioned once, not at the start of every session
5548
+ * (lib/hook-refresh.ts, `refreshNotice`).
5549
+ */
5550
+ hookNoticesTold: z8.array(z8.string()).optional().catch(void 0),
5544
5551
  /**
5545
5552
  * Notes `setup --web` was asked to upload, not yet sent.
5546
5553
  *
@@ -6733,7 +6740,7 @@ var init_cloud_pointer = __esm({
6733
6740
 
6734
6741
  // src/commands/link.ts
6735
6742
  import { readFile as readFile18 } from "fs/promises";
6736
- import { join as join17 } from "path";
6743
+ import { join as join18 } from "path";
6737
6744
  import { Command as Command13 } from "commander";
6738
6745
  import prompts2 from "prompts";
6739
6746
  function sameBasename(all, entry) {
@@ -6937,7 +6944,7 @@ async function recordLink(adapter, observationsDir) {
6937
6944
  }
6938
6945
  async function refuseSilentRules(path, denied, excluded, unsupported) {
6939
6946
  const denyEntries = (await LocalFileReadAdapter.readDenyFile(path, ".memgdeny")).entries;
6940
- const rawDenyLines = await readFile18(join17(path, ".memgdeny"), "utf8").then(
6947
+ const rawDenyLines = await readFile18(join18(path, ".memgdeny"), "utf8").then(
6941
6948
  (t) => t.split("\n").map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#"))
6942
6949
  ).catch(() => []);
6943
6950
  const asWritten = (folded) => rawDenyLines.find(
@@ -7565,6 +7572,228 @@ var init_push = __esm({
7565
7572
  }
7566
7573
  });
7567
7574
 
7575
+ // src/lib/guard-shell.ts
7576
+ function guardHookCommand() {
7577
+ return `/bin/sh -c '${GUARD_SHELL_SCRIPT}'`;
7578
+ }
7579
+ var GUARD_SHELL_SCRIPT;
7580
+ var init_guard_shell = __esm({
7581
+ "src/lib/guard-shell.ts"() {
7582
+ "use strict";
7583
+ GUARD_SHELL_SCRIPT = [
7584
+ 'h="${MEMGINEERING_HOME-$HOME/.memgineering}"',
7585
+ 'f="$h/guard/fast"',
7586
+ 'c="$h/config.json"',
7587
+ 'k="$h/credentials.json"',
7588
+ "g() { memgineering guard; exit 0; }",
7589
+ '[ -f "$c" ] && [ -f "$k" ] && [ -d "$f" ] || g',
7590
+ "p=$(cat)",
7591
+ 'go() { printf "%s\\n" "$p" | g; exit 0; }',
7592
+ '[ -n "$p" ] || exit 0',
7593
+ 'q=\\"',
7594
+ 'case "$p" in */memory/* | *"$q"memory/* | *"\\\\/memory\\\\/"* | *"$q"memory\\\\/*) go ;; esac',
7595
+ 'w=${p%%"$q"*}',
7596
+ 'r=${p#"$w"}',
7597
+ 'case "$r" in "$q"session_id"$q"*) ;; *) go ;; esac',
7598
+ 'r=${r#"$q"session_id"$q"}',
7599
+ "w=${r%%[![:space:]]*}",
7600
+ 'r=${r#"$w"}',
7601
+ 'case "$r" in :*) ;; *) go ;; esac',
7602
+ "r=${r#:}",
7603
+ "w=${r%%[![:space:]]*}",
7604
+ 'r=${r#"$w"}',
7605
+ 'case "$r" in "$q"*) ;; *) go ;; esac',
7606
+ 'r=${r#"$q"}',
7607
+ 's=${r%%"$q"*}',
7608
+ 'case "$s" in "" | *[!A-Za-z0-9-]*) go ;; esac',
7609
+ 'm=""',
7610
+ 'if [ -f "$f/$s" ]; then m="$f/$s"; else',
7611
+ ' n=""',
7612
+ ' for e in "$f/$s".empty.*; do',
7613
+ ' [ -f "$e" ] || continue',
7614
+ " t=${e##*.}",
7615
+ ' case "$t" in "" | *[!0-9]*) continue ;; esac',
7616
+ ' [ -n "$n" ] || n=$(date +%s)',
7617
+ ' if [ "$t" -gt "$n" ]; then m="$e"; break; fi',
7618
+ " done",
7619
+ ' [ -n "$m" ] || go',
7620
+ ' [ "$f/.rules-changed" -nt "$m" ] && go',
7621
+ "fi",
7622
+ '[ "$c" -nt "$m" ] && go',
7623
+ '[ "$k" -nt "$m" ] && go',
7624
+ "exit 0"
7625
+ ].join("\n");
7626
+ }
7627
+ });
7628
+
7629
+ // src/lib/hook-refresh.ts
7630
+ import { randomUUID as randomUUID4 } from "crypto";
7631
+ import { chmod, lstat as lstat5, readdir as readdir8, readFile as readFile24, stat as stat7, unlink as unlink5, writeFile as writeFile14 } from "fs/promises";
7632
+ import { basename as basename3, dirname as dirname8, join as join24 } from "path";
7633
+ function moveShippedCommands(parsed, moves) {
7634
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
7635
+ const settings = parsed;
7636
+ const hooks = settings["hooks"];
7637
+ if (hooks === void 0) return { next: settings, changed: 0 };
7638
+ if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks)) return null;
7639
+ let changed = 0;
7640
+ const nextHooks = { ...hooks };
7641
+ for (const move of moves) {
7642
+ const groups = nextHooks[move.event];
7643
+ if (!Array.isArray(groups)) continue;
7644
+ nextHooks[move.event] = groups.map((group) => {
7645
+ const entries = group?.hooks;
7646
+ if (!Array.isArray(entries)) return group;
7647
+ let touched = false;
7648
+ const nextEntries = entries.map((entry) => {
7649
+ const command = entry?.command;
7650
+ if (typeof command !== "string" || command === move.to || !move.from.includes(command)) {
7651
+ return entry;
7652
+ }
7653
+ touched = true;
7654
+ changed += 1;
7655
+ return { ...entry, command: move.to };
7656
+ });
7657
+ return touched ? { ...group, hooks: nextEntries } : group;
7658
+ });
7659
+ }
7660
+ return { next: changed === 0 ? settings : { ...settings, hooks: nextHooks }, changed };
7661
+ }
7662
+ async function lockHolder() {
7663
+ try {
7664
+ const [info, token] = await Promise.all([stat7(lockPath2()), readFile24(lockPath2(), "utf8")]);
7665
+ return { token, ageMs: Date.now() - info.mtimeMs };
7666
+ } catch {
7667
+ return null;
7668
+ }
7669
+ }
7670
+ async function unlinkIfStill(token) {
7671
+ const now = await readFile24(lockPath2(), "utf8").catch(() => null);
7672
+ if (now === token) await unlink5(lockPath2()).catch(() => void 0);
7673
+ }
7674
+ async function takeOver(dead) {
7675
+ try {
7676
+ await writeFile14(takeoverPath(), "", { flag: "wx", mode: 384 });
7677
+ } catch (err) {
7678
+ if (err.code === "EEXIST") {
7679
+ const age = await stat7(takeoverPath()).then(
7680
+ (info) => Date.now() - info.mtimeMs,
7681
+ () => null
7682
+ );
7683
+ if (age !== null && age > STALE_LOCK_MS) await unlink5(takeoverPath()).catch(() => void 0);
7684
+ }
7685
+ return false;
7686
+ }
7687
+ try {
7688
+ const held = await lockHolder();
7689
+ if (held !== null && held.token === dead && held.ageMs > STALE_LOCK_MS) {
7690
+ await unlink5(lockPath2()).catch(() => void 0);
7691
+ }
7692
+ return true;
7693
+ } finally {
7694
+ await unlink5(takeoverPath()).catch(() => void 0);
7695
+ }
7696
+ }
7697
+ async function withHookFileLock(fn, waitMs = 0) {
7698
+ const token = `${process.pid}-${randomUUID4()}`;
7699
+ const deadline = Date.now() + waitMs;
7700
+ for (let vanished = 0; ; ) {
7701
+ try {
7702
+ await writeFile14(lockPath2(), token, { flag: "wx", mode: 384 });
7703
+ break;
7704
+ } catch (err) {
7705
+ if (err.code !== "EEXIST") return null;
7706
+ }
7707
+ const held = await lockHolder();
7708
+ if (held === null) {
7709
+ vanished += 1;
7710
+ if (vanished > 20) return null;
7711
+ continue;
7712
+ }
7713
+ if (held.ageMs > STALE_LOCK_MS && await takeOver(held.token)) continue;
7714
+ if (Date.now() >= deadline) return null;
7715
+ await new Promise((r) => setTimeout(r, 50));
7716
+ }
7717
+ try {
7718
+ return await fn();
7719
+ } finally {
7720
+ await unlinkIfStill(token);
7721
+ }
7722
+ }
7723
+ async function clearStaleTemps(path) {
7724
+ const dir = dirname8(path);
7725
+ const prefix = `${basename3(path)}.`;
7726
+ const names = await readdir8(dir).catch(() => []);
7727
+ await Promise.all(
7728
+ names.filter((n) => n.startsWith(prefix) && /\.\d+\.tmp$/.test(n)).map(async (n) => {
7729
+ const age = await stat7(join24(dir, n)).then((s) => Date.now() - s.mtimeMs).catch(() => 0);
7730
+ if (age > STALE_LOCK_MS) await unlink5(join24(dir, n)).catch(() => void 0);
7731
+ })
7732
+ );
7733
+ }
7734
+ async function readSettings(path) {
7735
+ const raw = await readFile24(path, "utf8").catch(() => null);
7736
+ if (raw === null) return void 0;
7737
+ try {
7738
+ return JSON.parse(raw);
7739
+ } catch {
7740
+ return null;
7741
+ }
7742
+ }
7743
+ async function refreshHookFile(path, moves) {
7744
+ const info = await lstat5(path).catch(() => null);
7745
+ if (info === null) return "missing";
7746
+ if (info.isSymbolicLink()) return "symlink";
7747
+ await clearStaleTemps(path);
7748
+ const first = moveShippedCommands(await readSettings(path), moves);
7749
+ if (first === null) return "refused";
7750
+ if (first.changed === 0) return "unchanged";
7751
+ const outcome = await withHookFileLock(async () => {
7752
+ const again = moveShippedCommands(await readSettings(path), moves);
7753
+ if (again === null) return "refused";
7754
+ if (again.changed === 0) return "unchanged";
7755
+ await writeThenRename(path, `${JSON.stringify(again.next, null, 2)}
7756
+ `);
7757
+ await chmod(path, info.mode & 4095).catch(() => void 0);
7758
+ return "changed";
7759
+ });
7760
+ return outcome ?? "busy";
7761
+ }
7762
+ function refreshNotice(report3, told) {
7763
+ const lines2 = [];
7764
+ if (report3.changed.length > 0) {
7765
+ lines2.push(
7766
+ `memgineering brought its own hooks up to this version in ${report3.changed.join(", ")} \u2014 the before-edit check now answers an already-served session without starting node.`
7767
+ );
7768
+ if (report3.changed.some((p) => /[/\\]\.codex[/\\]hooks\.json$/.test(p))) {
7769
+ lines2.push(
7770
+ "Codex will ask to review the changed hook the next time it starts: trust it (press `t`) \u2014 until then neither memgineering hook runs in Codex."
7771
+ );
7772
+ }
7773
+ }
7774
+ const nowTold = [...told];
7775
+ for (const skip of report3.skipped) {
7776
+ const key2 = `${skip.path}:${skip.why}`;
7777
+ if (nowTold.includes(key2)) continue;
7778
+ nowTold.push(key2);
7779
+ lines2.push(
7780
+ skip.why === "symlink" ? `memgineering left its hooks in ${skip.path} as they are: the file is a link, and rewriting it would replace the link. \`memgineering setup\` updates them when you want it to.` : `memgineering left its hooks in ${skip.path} as they are: the file does not parse as JSON. Fix it, then \`memgineering setup\` updates them.`
7781
+ );
7782
+ }
7783
+ return { text: lines2.length === 0 ? null : lines2.join("\n"), told: nowTold };
7784
+ }
7785
+ var STALE_LOCK_MS, lockPath2, takeoverPath;
7786
+ var init_hook_refresh = __esm({
7787
+ "src/lib/hook-refresh.ts"() {
7788
+ "use strict";
7789
+ init_atomic();
7790
+ init_brand();
7791
+ STALE_LOCK_MS = 3e4;
7792
+ lockPath2 = () => join24(brandHome(), "hook-files.lock");
7793
+ takeoverPath = () => `${lockPath2()}.takeover`;
7794
+ }
7795
+ });
7796
+
7568
7797
  // src/commands/setup-brain.ts
7569
7798
  function migration(localBrains) {
7570
7799
  return ` They also keep ${localBrains} brain(s) in folders on this machine. Offer moving those notes to the account instead of starting empty: \`memgineering push --brain <name>\` uploads a folder into a hosted brain and points here (nothing local is deleted), then \`memgineering reindex\` makes the moved notes searchable by meaning. The memgineering-setup skill has the migration steps.`;
@@ -7899,38 +8128,38 @@ var init_setup_signin = __esm({
7899
8128
  });
7900
8129
 
7901
8130
  // src/lib/next-step.ts
7902
- import { mkdir as mkdir15, readFile as readFile24, writeFile as writeFile13 } from "fs/promises";
7903
- import { join as join23 } from "path";
8131
+ import { mkdir as mkdir16, readFile as readFile25, writeFile as writeFile15 } from "fs/promises";
8132
+ import { join as join25 } from "path";
7904
8133
  function isSetUp(cfg) {
7905
8134
  return Object.keys(cfg.system.installed).length > 0;
7906
8135
  }
7907
8136
  function connectedMarker(at) {
7908
- return join23(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
8137
+ return join25(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
7909
8138
  }
7910
8139
  async function connectedNoticeShown(at) {
7911
- return readFile24(connectedMarker(at), "utf8").then(
8140
+ return readFile25(connectedMarker(at), "utf8").then(
7912
8141
  () => true,
7913
8142
  () => false
7914
8143
  );
7915
8144
  }
7916
8145
  async function markConnectedNoticeShown(at) {
7917
8146
  try {
7918
- await mkdir15(brandHome(), { recursive: true });
7919
- await writeFile13(connectedMarker(at), `${(/* @__PURE__ */ new Date()).toISOString()}
8147
+ await mkdir16(brandHome(), { recursive: true });
8148
+ await writeFile15(connectedMarker(at), `${(/* @__PURE__ */ new Date()).toISOString()}
7920
8149
  `);
7921
8150
  } catch {
7922
8151
  }
7923
8152
  }
7924
8153
  async function claimFullSetupNotice() {
7925
- const marker = join23(brandHome(), "setup-notice-shown");
8154
+ const marker = join25(brandHome(), "setup-notice-shown");
7926
8155
  try {
7927
- await readFile24(marker, "utf8");
8156
+ await readFile25(marker, "utf8");
7928
8157
  return false;
7929
8158
  } catch {
7930
8159
  }
7931
8160
  try {
7932
- await mkdir15(brandHome(), { recursive: true });
7933
- await writeFile13(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
8161
+ await mkdir16(brandHome(), { recursive: true });
8162
+ await writeFile15(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
7934
8163
  `);
7935
8164
  } catch {
7936
8165
  }
@@ -8096,12 +8325,12 @@ Synced instead? \`memgineering login\`, then \`memgineering create <name>\`.`;
8096
8325
  // src/lib/provenance.ts
8097
8326
  import { execFile, spawn as spawn2 } from "child_process";
8098
8327
  import { realpath as realpath3 } from "fs/promises";
8099
- import { dirname as dirname8, join as join24, resolve as resolve14 } from "path";
8328
+ import { dirname as dirname9, join as join26, resolve as resolve14 } from "path";
8100
8329
  import { fileURLToPath as fileURLToPath2 } from "url";
8101
8330
  import { promisify } from "util";
8102
8331
  async function detectProvenance(selfPath, opts = {}) {
8103
8332
  const entry = selfPath ?? fileURLToPath2(import.meta.url);
8104
- const selfDir = await realpath3(dirname8(entry)).catch(() => dirname8(entry));
8333
+ const selfDir = await realpath3(dirname9(entry)).catch(() => dirname9(entry));
8105
8334
  if (/[\\/]_npx[\\/]|[\\/]\.npm[\\/]_cacache[\\/]/.test(selfDir)) {
8106
8335
  return {
8107
8336
  kind: "npx",
@@ -8219,7 +8448,7 @@ function isInside2(child, parent) {
8219
8448
  }
8220
8449
  async function unreachableByName(selfPath) {
8221
8450
  const entry = selfPath ?? fileURLToPath2(import.meta.url);
8222
- const selfDir = await realpath3(dirname8(entry)).catch(() => dirname8(entry));
8451
+ const selfDir = await realpath3(dirname9(entry)).catch(() => dirname9(entry));
8223
8452
  if (!/[\\/](node_modules|_npx|_cacache)[\\/]/.test(`${selfDir}/`)) return null;
8224
8453
  const p = await detectProvenance(entry);
8225
8454
  return p.kind === "local" || p.kind === "npx" ? p.reason : null;
@@ -8234,7 +8463,7 @@ var init_provenance = __esm({
8234
8463
  kind: "npm",
8235
8464
  bin: "npm",
8236
8465
  prefixArgs: ["prefix", "-g"],
8237
- moduleRoot: (prefix) => process.platform === "win32" ? join24(prefix, "node_modules") : join24(prefix, "lib", "node_modules"),
8466
+ moduleRoot: (prefix) => process.platform === "win32" ? join26(prefix, "node_modules") : join26(prefix, "lib", "node_modules"),
8238
8467
  install: "npm i -g memgineering@latest"
8239
8468
  },
8240
8469
  {
@@ -8271,9 +8500,9 @@ __export(setup_cloud_exports, {
8271
8500
  });
8272
8501
  import { spawn as spawn3 } from "child_process";
8273
8502
  import { realpathSync as realpathSync2 } from "fs";
8274
- import { readdir as readdir7, stat as stat6 } from "fs/promises";
8503
+ import { readdir as readdir9, stat as stat8 } from "fs/promises";
8275
8504
  import { homedir as homedir5 } from "os";
8276
- import { basename as basename3, dirname as dirname9, join as join25, resolve as resolve15, sep as sep4 } from "path";
8505
+ import { basename as basename4, dirname as dirname10, join as join27, resolve as resolve15, sep as sep4 } from "path";
8277
8506
  async function report2(setupToken, state, detail, code) {
8278
8507
  try {
8279
8508
  await apiRequest({
@@ -8291,16 +8520,16 @@ async function report2(setupToken, state, detail, code) {
8291
8520
  }
8292
8521
  async function folderCandidates() {
8293
8522
  const home = homedir5();
8294
- const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join25(home, d));
8523
+ const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join27(home, d));
8295
8524
  const found = [];
8296
8525
  const countNotes = async (dir) => {
8297
8526
  let count = 0;
8298
- const entries = await readdir7(dir, { withFileTypes: true }).catch(() => []);
8527
+ const entries = await readdir9(dir, { withFileTypes: true }).catch(() => []);
8299
8528
  for (const entry of entries) {
8300
8529
  if (entry.name.startsWith(".")) continue;
8301
8530
  if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) count += 1;
8302
8531
  else if (entry.isDirectory()) {
8303
- const inner = await readdir7(join25(dir, entry.name)).catch(() => []);
8532
+ const inner = await readdir9(join27(dir, entry.name)).catch(() => []);
8304
8533
  count += inner.filter((n) => /\.(md|markdown)$/i.test(n)).length;
8305
8534
  }
8306
8535
  if (count > 999) break;
@@ -8308,7 +8537,7 @@ async function folderCandidates() {
8308
8537
  return count;
8309
8538
  };
8310
8539
  for (const root of roots) {
8311
- const info = await stat6(root).catch(() => null);
8540
+ const info = await stat8(root).catch(() => null);
8312
8541
  if (!info?.isDirectory()) continue;
8313
8542
  const notes = await countNotes(root);
8314
8543
  if (notes > 0) found.push({ path: root, note_count: notes });
@@ -8643,9 +8872,9 @@ function insideFolder(target, folder) {
8643
8872
  realpathSync2(existing);
8644
8873
  break;
8645
8874
  } catch {
8646
- const parent = dirname9(existing);
8875
+ const parent = dirname10(existing);
8647
8876
  if (parent === existing) break;
8648
- rest.unshift(basename3(existing));
8877
+ rest.unshift(basename4(existing));
8649
8878
  existing = parent;
8650
8879
  }
8651
8880
  }
@@ -8692,14 +8921,14 @@ var init_setup_cloud = __esm({
8692
8921
 
8693
8922
  // src/commands/setup.ts
8694
8923
  import { createHash as createHash9 } from "crypto";
8695
- import { access as access2, mkdir as mkdir16, readFile as readFile25 } from "fs/promises";
8696
- import { dirname as dirname10, join as join26 } from "path";
8924
+ import { access as access2, mkdir as mkdir17, readFile as readFile26 } from "fs/promises";
8925
+ import { dirname as dirname11, join as join28 } from "path";
8697
8926
  import { fileURLToPath as fileURLToPath3 } from "url";
8698
- import { Command as Command40 } from "commander";
8927
+ import { Command as Command38 } from "commander";
8699
8928
  import prompts5 from "prompts";
8700
8929
  import { homedir as homedir6 } from "os";
8701
8930
  function setupCommand() {
8702
- return new Command40("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
8931
+ return new Command38("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
8703
8932
  "--wait",
8704
8933
  "with --web: block until the screen is finished, even with nobody watching the terminal"
8705
8934
  ).option(
@@ -9060,7 +9289,7 @@ async function applySetup(choice, dryRun) {
9060
9289
  }
9061
9290
  async function write2() {
9062
9291
  for (const tool of choice.tools) {
9063
- const hubPath = join26(agentHome(), tool.hubFile);
9292
+ const hubPath = join28(agentHome(), tool.hubFile);
9064
9293
  const written = await injectBlock(
9065
9294
  hubPath,
9066
9295
  hub + tailFor(tool, choice.hook),
@@ -9075,8 +9304,8 @@ async function applySetup(choice, dryRun) {
9075
9304
  });
9076
9305
  if (!dryRun && written.after !== null) mark(hubPath, written.after);
9077
9306
  for (const skill of BRAND.subSkills) {
9078
- const content = await loadAsset(join26(skill, "SKILL.md"));
9079
- const path = join26(agentHome(), tool.skillsDir, skill, "SKILL.md");
9307
+ const content = await loadAsset(join28(skill, "SKILL.md"));
9308
+ const path = join28(agentHome(), tool.skillsDir, skill, "SKILL.md");
9080
9309
  const out = await writeIfDifferent(path, content, dryRun);
9081
9310
  results.push({
9082
9311
  tool: tool.label,
@@ -9092,7 +9321,14 @@ async function applySetup(choice, dryRun) {
9092
9321
  results.push({
9093
9322
  tool: tool.label,
9094
9323
  what: hook.what,
9095
- result: choice.hook ? await installHook(hook, tool, dryRun) : await removeHook(hook, tool, dryRun)
9324
+ // Under the lock every writer of these files takes, so a session
9325
+ // starting meanwhile cannot refresh the same file under this write.
9326
+ // A lock nobody gives up within five seconds is a crashed holder or
9327
+ // a stuck one; the write goes ahead rather than leaving setup hung.
9328
+ result: await (async () => {
9329
+ const write3 = () => choice.hook ? installHook(hook, tool, dryRun) : removeHook(hook, tool, dryRun);
9330
+ return await withHookFileLock(write3, 5e3) ?? write3();
9331
+ })()
9096
9332
  });
9097
9333
  }
9098
9334
  }
@@ -9159,7 +9395,7 @@ async function hooksArmed(tools) {
9159
9395
  const capable = tools.filter((t) => t.hooks && t.hooksFile);
9160
9396
  if (capable.length === 0) return void 0;
9161
9397
  for (const tool of capable) {
9162
- const raw = await readFile25(join26(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
9398
+ const raw = await readFile26(join28(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
9163
9399
  () => ""
9164
9400
  );
9165
9401
  if (hooksFor([tool]).some((spec) => raw.includes(spec.signature))) return "on";
@@ -9199,7 +9435,7 @@ async function detectTools() {
9199
9435
  const home = agentHome();
9200
9436
  const found = [];
9201
9437
  for (const tool of TOOLS) {
9202
- if (await exists2(join26(home, tool.dir))) found.push(tool);
9438
+ if (await exists2(join28(home, tool.dir))) found.push(tool);
9203
9439
  }
9204
9440
  return found;
9205
9441
  }
@@ -9299,10 +9535,10 @@ async function injectBlock(filePath, content, dryRun, weWroteThis) {
9299
9535
  const block = `${BRAND.hubMarkerStart}
9300
9536
  ${content.trimEnd()}
9301
9537
  ${BRAND.hubMarkerEnd}`;
9302
- const existing = await readFile25(filePath, "utf8").catch(() => null);
9538
+ const existing = await readFile26(filePath, "utf8").catch(() => null);
9303
9539
  if (existing === null) {
9304
9540
  if (!dryRun) {
9305
- await mkdir16(dirname10(filePath), { recursive: true });
9541
+ await mkdir17(dirname11(filePath), { recursive: true });
9306
9542
  await writeThenRename(filePath, `${block}
9307
9543
  `);
9308
9544
  }
@@ -9342,23 +9578,56 @@ function looksLikeOurGuide(text) {
9342
9578
  return text.includes("memgineering recall") && text.includes("memgineering remember");
9343
9579
  }
9344
9580
  async function writeIfDifferent(filePath, content, dryRun) {
9345
- const existing = await readFile25(filePath, "utf8").catch(() => null);
9581
+ const existing = await readFile26(filePath, "utf8").catch(() => null);
9346
9582
  const after = fingerprint(content);
9347
9583
  if (existing === content) return { outcome: "unchanged", before: existing, after };
9348
9584
  if (!dryRun) {
9349
- await mkdir16(dirname10(filePath), { recursive: true });
9585
+ await mkdir17(dirname11(filePath), { recursive: true });
9350
9586
  await writeThenRename(filePath, content);
9351
9587
  }
9352
9588
  return { outcome: existing === null ? "injected" : "updated", before: existing, after };
9353
9589
  }
9590
+ function guardCommandFor(platform2) {
9591
+ return platform2 === "win32" ? "memgineering guard || true" : guardHookCommand();
9592
+ }
9593
+ async function refreshOwnHookCommands() {
9594
+ const moves = HOOKS.filter((h) => h.event !== "Stop").map((h) => ({
9595
+ event: h.event,
9596
+ from: SHIPPED_HOOK_COMMANDS[h.event],
9597
+ to: h.command
9598
+ }));
9599
+ const changed = [];
9600
+ const skipped = [];
9601
+ for (const tool of TOOLS.filter((t) => t.hooks && t.hooksFile && DEFAULT_HOOK_TOOLS.includes(t.id))) {
9602
+ const path = join28(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9603
+ const outcome = await refreshHookFile(path, moves);
9604
+ if (outcome === "changed") changed.push(path);
9605
+ else if (outcome === "symlink" || outcome === "refused") skipped.push({ path, why: outcome });
9606
+ }
9607
+ return { changed, skipped };
9608
+ }
9609
+ async function refreshOwnHooksAndTell(say) {
9610
+ try {
9611
+ const report3 = await refreshOwnHookCommands();
9612
+ if (report3.changed.length === 0 && report3.skipped.length === 0) return;
9613
+ const cfg = await loadConfig();
9614
+ const told = cfg.system.hookNoticesTold ?? [];
9615
+ const { text, told: nowTold } = refreshNotice(report3, told);
9616
+ if (nowTold.length !== told.length) {
9617
+ await saveConfig({ ...cfg, system: { ...cfg.system, hookNoticesTold: nowTold } });
9618
+ }
9619
+ if (text !== null) say(text);
9620
+ } catch {
9621
+ }
9622
+ }
9354
9623
  function hooksFor(tools) {
9355
9624
  return HOOKS.filter(
9356
9625
  (h) => tools.some((t) => t.hooks && (h.tools ?? DEFAULT_HOOK_TOOLS).includes(t.id))
9357
9626
  );
9358
9627
  }
9359
9628
  async function installHook(spec, tool, dryRun) {
9360
- const settingsPath = join26(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9361
- const raw = await readFile25(settingsPath, "utf8").catch(() => null);
9629
+ const settingsPath = join28(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9630
+ const raw = await readFile26(settingsPath, "utf8").catch(() => null);
9362
9631
  let settings = {};
9363
9632
  if (raw !== null) {
9364
9633
  try {
@@ -9400,15 +9669,15 @@ async function installHook(spec, tool, dryRun) {
9400
9669
  }
9401
9670
  const next = { ...settings, hooks: { ...hooks, [spec.event]: existingGroups } };
9402
9671
  if (!dryRun) {
9403
- await mkdir16(dirname10(settingsPath), { recursive: true });
9672
+ await mkdir17(dirname11(settingsPath), { recursive: true });
9404
9673
  await writeThenRename(settingsPath, `${JSON.stringify(next, null, 2)}
9405
9674
  `);
9406
9675
  }
9407
9676
  return raw === null ? "injected" : "updated";
9408
9677
  }
9409
9678
  async function removeHook(spec, tool, dryRun) {
9410
- const settingsPath = join26(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9411
- const raw = await readFile25(settingsPath, "utf8").catch(() => null);
9679
+ const settingsPath = join28(agentHome(), ...(tool.hooksFile ?? "").split("/"));
9680
+ const raw = await readFile26(settingsPath, "utf8").catch(() => null);
9412
9681
  if (raw === null) return "unchanged";
9413
9682
  let parsed;
9414
9683
  try {
@@ -9432,7 +9701,7 @@ async function removeHook(spec, tool, dryRun) {
9432
9701
  return "removed";
9433
9702
  }
9434
9703
  async function loadAsset(relPath) {
9435
- const raw = await readFile25(join26(await assetRoot(), relPath), "utf8");
9704
+ const raw = await readFile26(join28(await assetRoot(), relPath), "utf8");
9436
9705
  if (relPath === BRAND.hubFileName && raw.startsWith("---")) {
9437
9706
  const end = raw.indexOf("\n---", 4);
9438
9707
  if (end !== -1) return raw.slice(end + 4).trimStart();
@@ -9440,14 +9709,14 @@ async function loadAsset(relPath) {
9440
9709
  return raw;
9441
9710
  }
9442
9711
  async function assetRoot() {
9443
- const here = dirname10(fileURLToPath3(import.meta.url));
9444
- const bundled = join26(here, "..", "assets");
9445
- if (await exists2(join26(bundled, BRAND.hubFileName))) return bundled;
9712
+ const here = dirname11(fileURLToPath3(import.meta.url));
9713
+ const bundled = join28(here, "..", "assets");
9714
+ if (await exists2(join28(bundled, BRAND.hubFileName))) return bundled;
9446
9715
  let dir = here;
9447
9716
  for (; ; ) {
9448
- const candidate = join26(dir, "packages", "skills");
9449
- if (await exists2(join26(candidate, BRAND.hubFileName))) return candidate;
9450
- const parent = dirname10(dir);
9717
+ const candidate = join28(dir, "packages", "skills");
9718
+ if (await exists2(join28(candidate, BRAND.hubFileName))) return candidate;
9719
+ const parent = dirname11(dir);
9451
9720
  if (parent === dir) break;
9452
9721
  dir = parent;
9453
9722
  }
@@ -9465,13 +9734,15 @@ async function exists2(path) {
9465
9734
  return false;
9466
9735
  }
9467
9736
  }
9468
- var TOOLS, HOSTED_CONNECTOR_URL, SUPPORTED_LIST, REFUSAL_HINT, DUPLICATE_HINT, HOOK_TIMEOUT_S, DEFAULT_HOOK_TOOLS, HOOK_WHAT, HOOKS;
9737
+ var TOOLS, HOSTED_CONNECTOR_URL, SUPPORTED_LIST, REFUSAL_HINT, DUPLICATE_HINT, HOOK_TIMEOUT_S, DEFAULT_HOOK_TOOLS, HOOK_WHAT, HOOKS, SHIPPED_HOOK_COMMANDS;
9469
9738
  var init_setup = __esm({
9470
9739
  "src/commands/setup.ts"() {
9471
9740
  "use strict";
9472
9741
  init_atomic();
9473
9742
  init_brand();
9474
9743
  init_deadline();
9744
+ init_guard_shell();
9745
+ init_hook_refresh();
9475
9746
  init_errors();
9476
9747
  init_setup_brain();
9477
9748
  init_setup_signin();
@@ -9599,7 +9870,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
9599
9870
  // here would mean a session that reads and answers never hears a rule.
9600
9871
  codex: "apply_patch|Bash"
9601
9872
  },
9602
- command: "memgineering guard || true",
9873
+ command: guardCommandFor(process.platform),
9603
9874
  signature: "memgineering guard"
9604
9875
  },
9605
9876
  {
@@ -9625,6 +9896,13 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
9625
9896
  tools: ["grok"]
9626
9897
  }
9627
9898
  ];
9899
+ SHIPPED_HOOK_COMMANDS = {
9900
+ SessionStart: [
9901
+ "memgineering resurface --limit 5 || true",
9902
+ `memgineering resurface --limit 5 --deadline ${HOOK_DEADLINE_MS} || true`
9903
+ ],
9904
+ PreToolUse: ["memgineering guard || true"]
9905
+ };
9628
9906
  }
9629
9907
  });
9630
9908
 
@@ -14072,6 +14350,69 @@ init_errors();
14072
14350
  init_index_build();
14073
14351
  init_cloud_brain();
14074
14352
  init_vault();
14353
+
14354
+ // src/lib/guard-fast.ts
14355
+ init_brand();
14356
+ import { randomUUID as randomUUID2 } from "crypto";
14357
+ import { mkdir as mkdir11, readdir as readdir6, rename as rename4, stat as stat3, unlink as unlink3, utimes, writeFile as writeFile9 } from "fs/promises";
14358
+ import { join as join17 } from "path";
14359
+ var fastDir = () => join17(brandHome(), "guard", "fast");
14360
+ var EMPTY_TTL_S = 120;
14361
+ var SESSION_ID = /^[A-Za-z0-9-]{1,128}$/;
14362
+ var EMPTY_MARK = /^(.+)\.empty\.(\d+)$/;
14363
+ var RULES_CHANGED = ".rules-changed";
14364
+ var TMP_PREFIX = ".tmp-";
14365
+ async function place(name, asOfMs) {
14366
+ await mkdir11(fastDir(), { recursive: true, mode: 448 });
14367
+ const tmp = join17(fastDir(), `${TMP_PREFIX}${process.pid}-${randomUUID2()}`);
14368
+ try {
14369
+ await writeFile9(tmp, "", { mode: 384 });
14370
+ const dated = (asOfMs - 1e3) / 1e3;
14371
+ await utimes(tmp, dated, dated);
14372
+ await rename4(tmp, join17(fastDir(), name));
14373
+ } catch (err) {
14374
+ await unlink3(tmp).catch(() => void 0);
14375
+ throw err;
14376
+ }
14377
+ }
14378
+ async function markServed(sessionId, asOfMs) {
14379
+ if (!SESSION_ID.test(sessionId)) return;
14380
+ await place(sessionId, asOfMs).catch(() => void 0);
14381
+ }
14382
+ async function markEmpty(sessionId, asOfMs) {
14383
+ if (!SESSION_ID.test(sessionId)) return;
14384
+ const until = Math.floor(asOfMs / 1e3) + EMPTY_TTL_S;
14385
+ await place(`${sessionId}.empty.${until}`, asOfMs).catch(() => void 0);
14386
+ await unlink3(join17(fastDir(), sessionId)).catch(() => void 0);
14387
+ }
14388
+ async function forgetEmptyChecks() {
14389
+ await mkdir11(fastDir(), { recursive: true, mode: 448 }).then(() => writeFile9(join17(fastDir(), RULES_CHANGED), "", { mode: 384 })).catch(() => void 0);
14390
+ const names = await readdir6(fastDir()).catch(() => []);
14391
+ await Promise.all(
14392
+ names.filter((name) => EMPTY_MARK.test(name)).map((name) => unlink3(join17(fastDir(), name)).catch(() => void 0))
14393
+ );
14394
+ }
14395
+ var SERVED_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
14396
+ var TMP_TTL_MS = 60 * 1e3;
14397
+ async function pruneFast(nowMs = Date.now()) {
14398
+ const names = await readdir6(fastDir()).catch(() => []);
14399
+ const nowS = Math.floor(nowMs / 1e3);
14400
+ await Promise.all(
14401
+ names.map(async (name) => {
14402
+ const path = join17(fastDir(), name);
14403
+ const empty = EMPTY_MARK.exec(name);
14404
+ if (empty) {
14405
+ if (Number(empty[2]) <= nowS) await unlink3(path).catch(() => void 0);
14406
+ return;
14407
+ }
14408
+ const info = await stat3(path).catch(() => null);
14409
+ const ttl = name.startsWith(TMP_PREFIX) ? TMP_TTL_MS : SERVED_TTL_MS;
14410
+ if (info && nowMs - info.mtimeMs > ttl) await unlink3(path).catch(() => void 0);
14411
+ })
14412
+ );
14413
+ }
14414
+
14415
+ // src/commands/remember.ts
14075
14416
  init_ui();
14076
14417
  function rememberCommand() {
14077
14418
  const said = { scope: false, noScope: false };
@@ -14145,6 +14486,7 @@ function rememberCommand() {
14145
14486
  const kindLanded = kind === void 0 || written.kind === kind;
14146
14487
  const boundaryLanded = neverStore.length === 0 || Array.isArray(written.never_store) && neverStore.every((term) => written.never_store?.includes(term) === true);
14147
14488
  const overrideLanded = opts.overrideBoundary === void 0 || "boundary_override" in written;
14489
+ if (binding) await forgetEmptyChecks();
14148
14490
  printDual({
14149
14491
  json: {
14150
14492
  id: written.id,
@@ -14779,6 +15121,7 @@ async function reviseHosted(where, ref, opts, added) {
14779
15121
  }
14780
15122
  }
14781
15123
  }
15124
+ if (opts.rule === true) await forgetEmptyChecks();
14782
15125
  printDual({
14783
15126
  json: {
14784
15127
  brain: where.name,
@@ -16522,7 +16865,7 @@ async function deleteCloudBrain(args) {
16522
16865
 
16523
16866
  // src/commands/curation.ts
16524
16867
  init_src();
16525
- import { stat as stat3 } from "fs/promises";
16868
+ import { stat as stat4 } from "fs/promises";
16526
16869
  import { resolve as resolve8 } from "path";
16527
16870
  import { Command as Command14 } from "commander";
16528
16871
 
@@ -16838,7 +17181,7 @@ async function mustMatchAFile(root, adapter, path) {
16838
17181
  const wanted = path.normalize("NFC").replace(/^\/+/, "");
16839
17182
  const absolute = resolve8(root, wanted);
16840
17183
  if (isInside(absolute, root)) {
16841
- const found = await stat3(absolute).catch(() => null);
17184
+ const found = await stat4(absolute).catch(() => null);
16842
17185
  if (found?.isFile()) return wanted;
16843
17186
  }
16844
17187
  const hits = sameBasename(await adapter.list(""), path);
@@ -17412,8 +17755,8 @@ function ago(iso, now) {
17412
17755
  init_brand();
17413
17756
  init_config();
17414
17757
  import { createHash as createHash7 } from "crypto";
17415
- import { mkdir as mkdir11, readdir as readdir6, stat as stat4, unlink as unlink3, writeFile as writeFile9 } from "fs/promises";
17416
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join18, resolve as resolve9 } from "path";
17758
+ import { mkdir as mkdir12, readdir as readdir7, stat as stat5, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
17759
+ import { dirname as dirname5, isAbsolute as isAbsolute3, join as join19, resolve as resolve9 } from "path";
17417
17760
  import { Command as Command17 } from "commander";
17418
17761
 
17419
17762
  // src/lib/rules.ts
@@ -17484,6 +17827,7 @@ function renderRuleGroups(rules, project) {
17484
17827
  init_src();
17485
17828
  init_deadline();
17486
17829
  init_cloud_brain();
17830
+ init_vault();
17487
17831
  function guardCommand() {
17488
17832
  return new Command17("guard").description("hook entry \u2014 show the user\u2019s rules before the first edit of a session").option("--context-dir <path>", "where the work is happening, when stdin does not say").option("--at <event>", "pre-tool-use | stop", "pre-tool-use").action(async (opts) => {
17489
17833
  try {
@@ -17548,22 +17892,35 @@ async function runStopGuard(payload, contextDirFlag) {
17548
17892
  });
17549
17893
  }
17550
17894
  async function claimRules(sessionId, contextDir) {
17895
+ const asOf = Date.now();
17551
17896
  const where = await resolveTarget({}).catch(() => null);
17552
17897
  if (where?.kind === "cloud") {
17553
- if (!await claimSession(sessionId, where.brainId)) return null;
17898
+ if (!await claimSession(sessionId, where.brainId)) {
17899
+ if (await claimExists(sessionId, where.brainId)) await markServed(sessionId, asOf);
17900
+ return null;
17901
+ }
17554
17902
  const hosted = await hostedRuleLines(where, contextDir).catch(() => null);
17555
17903
  if (hosted !== null) {
17556
- if (hosted.lines.length > 0) return { lines: hosted.lines, total: hosted.total };
17904
+ if (hosted.lines.length > 0) {
17905
+ await markServed(sessionId, asOf);
17906
+ return { lines: hosted.lines, total: hosted.total };
17907
+ }
17557
17908
  await releaseSession(sessionId, where.brainId);
17909
+ await markEmpty(sessionId, asOf);
17558
17910
  return null;
17559
17911
  }
17560
17912
  await releaseSession(sessionId, where.brainId);
17561
17913
  }
17914
+ const named = await resolveBrain(void 0, contextDir).catch(() => null);
17915
+ if (named === null) return null;
17916
+ const root = named.brain.root;
17917
+ if (!await claimSession(sessionId, root)) return null;
17562
17918
  const collected = await collectRules(void 0, contextDir).catch(() => null);
17563
- if (collected === null) return null;
17919
+ if (collected === null || collected.rules.length === 0) {
17920
+ await releaseSession(sessionId, root);
17921
+ return null;
17922
+ }
17564
17923
  const { brainRoot, rules, total } = collected;
17565
- if (rules.length === 0) return null;
17566
- if (!await claimSession(sessionId, brainRoot)) return null;
17567
17924
  await recordEvent(brainRoot, {
17568
17925
  verb: "guard",
17569
17926
  contextDir,
@@ -17619,7 +17976,7 @@ async function readPayload() {
17619
17976
  return null;
17620
17977
  }
17621
17978
  }
17622
- var guardDir = () => join18(brandHome(), "guard");
17979
+ var guardDir = () => join19(brandHome(), "guard");
17623
17980
  var WRITE_TOOLS = /* @__PURE__ */ new Set([
17624
17981
  "Write",
17625
17982
  "Edit",
@@ -17640,7 +17997,7 @@ async function misdeliveryNotice(toolName, filePath, sessionId) {
17640
17997
  return 'This file is this harness\u2019s own memory \u2014 no other tool the user works with will ever read it. Durable facts about the user belong in their brain, which every tool shares: memgineering remember "<the fact>" --reason "<why>" (add --rule if they stated a standing rule). Write it there instead, or in addition.';
17641
17998
  }
17642
17999
  function markerFor(sessionId, brain) {
17643
- return join18(
18000
+ return join19(
17644
18001
  guardDir(),
17645
18002
  `${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
17646
18003
  );
@@ -17648,8 +18005,8 @@ function markerFor(sessionId, brain) {
17648
18005
  async function claimSession(sessionId, brain) {
17649
18006
  const marker = markerFor(sessionId, brain);
17650
18007
  try {
17651
- await mkdir11(guardDir(), { recursive: true, mode: 448 });
17652
- await writeFile9(marker, "", { flag: "wx", mode: 384 });
18008
+ await mkdir12(guardDir(), { recursive: true, mode: 448 });
18009
+ await writeFile10(marker, "", { flag: "wx", mode: 384 });
17653
18010
  } catch {
17654
18011
  return false;
17655
18012
  }
@@ -17657,19 +18014,23 @@ async function claimSession(sessionId, brain) {
17657
18014
  return true;
17658
18015
  }
17659
18016
  async function releaseSession(sessionId, brain) {
17660
- await unlink3(markerFor(sessionId, brain)).catch(() => void 0);
18017
+ await unlink4(markerFor(sessionId, brain)).catch(() => void 0);
18018
+ }
18019
+ async function claimExists(sessionId, brain) {
18020
+ return stat5(markerFor(sessionId, brain)).then(() => true).catch(() => false);
17661
18021
  }
17662
18022
  var MARKER_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
17663
18023
  async function pruneOldMarkers() {
17664
18024
  try {
17665
18025
  const dir = guardDir();
17666
18026
  const now = Date.now();
17667
- for (const name of await readdir6(dir)) {
17668
- const path = join18(dir, name);
18027
+ for (const name of await readdir7(dir)) {
18028
+ const path = join19(dir, name);
17669
18029
  if (!isAbsolute3(path)) continue;
17670
- const info = await stat4(path).catch(() => null);
17671
- if (info && now - info.mtimeMs > MARKER_TTL_MS) await unlink3(path).catch(() => void 0);
18030
+ const info = await stat5(path).catch(() => null);
18031
+ if (info && now - info.mtimeMs > MARKER_TTL_MS) await unlink4(path).catch(() => void 0);
17672
18032
  }
18033
+ await pruneFast(now);
17673
18034
  } catch {
17674
18035
  }
17675
18036
  }
@@ -18687,8 +19048,8 @@ init_api_client();
18687
19048
  init_config();
18688
19049
  init_cloud_brain();
18689
19050
  init_ui();
18690
- import { mkdir as mkdir12, readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
18691
- import { dirname as dirname6, isAbsolute as isAbsolute4, join as join19, resolve as resolve11, sep as sep2 } from "path";
19051
+ import { mkdir as mkdir13, readFile as readFile20, writeFile as writeFile11 } from "fs/promises";
19052
+ import { dirname as dirname6, isAbsolute as isAbsolute4, join as join20, resolve as resolve11, sep as sep2 } from "path";
18692
19053
  import { Command as Command24 } from "commander";
18693
19054
  function pullCommand() {
18694
19055
  return new Command24("pull").description("download your hosted brain to a folder on this machine").argument("[destination]", "folder to write into (created if missing)").option("--brain <name>", "which hosted brain to download").option("--dry-run", "say what would be written and write nothing").action(async (destination, opts) => {
@@ -18756,8 +19117,8 @@ function pullCommand() {
18756
19117
  already.push(note.path);
18757
19118
  continue;
18758
19119
  }
18759
- await mkdir12(dirname6(file), { recursive: true });
18760
- await writeFile10(file, note.content ?? "", "utf8");
19120
+ await mkdir13(dirname6(file), { recursive: true });
19121
+ await writeFile11(file, note.content ?? "", "utf8");
18761
19122
  written.push(note.path);
18762
19123
  if (note.excluded === true) excluded.push(note.path);
18763
19124
  }
@@ -18871,7 +19232,7 @@ function safeJoin(root, relative3) {
18871
19232
  "Nothing was written for it. This should not happen \u2014 please report it."
18872
19233
  );
18873
19234
  }
18874
- const full = resolve11(join19(root, relative3));
19235
+ const full = resolve11(join20(root, relative3));
18875
19236
  if (full !== root && !full.startsWith(root + sep2)) {
18876
19237
  throw memgError(
18877
19238
  "server_refused",
@@ -19793,9 +20154,9 @@ init_errors();
19793
20154
  init_credentials();
19794
20155
 
19795
20156
  // src/lib/untrusted-fence.ts
19796
- import { randomUUID as randomUUID2 } from "crypto";
20157
+ import { randomUUID as randomUUID3 } from "crypto";
19797
20158
  function makeUntrustedSentinel() {
19798
- return `KORDIS-UNTRUSTED-${randomUUID2()}`;
20159
+ return `KORDIS-UNTRUSTED-${randomUUID3()}`;
19799
20160
  }
19800
20161
  function fenceUntrusted(sentinel, text) {
19801
20162
  const neutralized = text.replace(/KORDIS-UNTRUSTED-/g, "KORDIS-UNTRUSTED\u200B-");
@@ -20291,7 +20652,7 @@ init_errors();
20291
20652
  // src/lib/harness-context.ts
20292
20653
  init_brand();
20293
20654
  import { access, readFile as readFile21 } from "fs/promises";
20294
- import { join as join20 } from "path";
20655
+ import { join as join21 } from "path";
20295
20656
  var PER_FILE_CAP = 16 * 1024;
20296
20657
  var TOTAL_CAP = 48 * 1024;
20297
20658
  var TRUNCATION_RESERVE = 64;
@@ -20336,13 +20697,13 @@ function extractHeadings(content) {
20336
20697
  }
20337
20698
  async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
20338
20699
  const candidates = [
20339
- { path: join20(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
20340
- { path: join20(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
20341
- { path: join20(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
20342
- { path: join20(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
20343
- { path: join20(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" },
20344
- { path: join20(home, ".codex", "AGENTS.md"), display: "~/.codex/AGENTS.md", scope: "global" },
20345
- { path: join20(home, ".gemini", "GEMINI.md"), display: "~/.gemini/GEMINI.md", scope: "global" }
20700
+ { path: join21(cwd, "CLAUDE.md"), display: "CLAUDE.md", scope: "project" },
20701
+ { path: join21(cwd, "AGENTS.md"), display: "AGENTS.md", scope: "project" },
20702
+ { path: join21(cwd, "GEMINI.md"), display: "GEMINI.md", scope: "project" },
20703
+ { path: join21(cwd, ".claude", "CLAUDE.md"), display: ".claude/CLAUDE.md", scope: "project" },
20704
+ { path: join21(home, ".claude", "CLAUDE.md"), display: "~/.claude/CLAUDE.md", scope: "global" },
20705
+ { path: join21(home, ".codex", "AGENTS.md"), display: "~/.codex/AGENTS.md", scope: "global" },
20706
+ { path: join21(home, ".gemini", "GEMINI.md"), display: "~/.gemini/GEMINI.md", scope: "global" }
20346
20707
  ];
20347
20708
  const files2 = [];
20348
20709
  let totalBytes = 0;
@@ -20747,8 +21108,8 @@ function parseLimit2(raw) {
20747
21108
  import { Command as Command30 } from "commander";
20748
21109
  import prompts3 from "prompts";
20749
21110
  import { existsSync, lstatSync, realpathSync } from "fs";
20750
- import { mkdir as mkdir14, readFile as readFile23, stat as stat5, writeFile as writeFile12 } from "fs/promises";
20751
- import { dirname as dirname7, join as join22, resolve as resolve12, sep as sep3 } from "path";
21111
+ import { mkdir as mkdir15, readFile as readFile23, stat as stat6, writeFile as writeFile13 } from "fs/promises";
21112
+ import { dirname as dirname7, join as join23, resolve as resolve12, sep as sep3 } from "path";
20752
21113
 
20753
21114
  // ../../packages/translators/src/skill/identity.ts
20754
21115
  function buildSkillFile(input, outputPath) {
@@ -21329,17 +21690,17 @@ init_ui();
21329
21690
 
21330
21691
  // src/lib/lockfile.ts
21331
21692
  init_brand();
21332
- import { copyFile, mkdir as mkdir13, readFile as readFile22, writeFile as writeFile11, constants as fsConstants } from "fs/promises";
21693
+ import { copyFile, mkdir as mkdir14, readFile as readFile22, writeFile as writeFile12, constants as fsConstants } from "fs/promises";
21333
21694
  import { homedir as homedir4 } from "os";
21334
- import { join as join21 } from "path";
21695
+ import { join as join22 } from "path";
21335
21696
  function effectiveRange(entry) {
21336
21697
  return entry.requestedRange ?? `^${entry.version}`;
21337
21698
  }
21338
21699
  function lockfilePath() {
21339
- return join21(brandHome(), "installed.json");
21700
+ return join22(brandHome(), "installed.json");
21340
21701
  }
21341
21702
  function kordisLockfilePath() {
21342
- return join21(process.env["KORDIS_HOME"] ?? join21(homedir4(), ".kordis"), "installed.json");
21703
+ return join22(process.env["KORDIS_HOME"] ?? join22(homedir4(), ".kordis"), "installed.json");
21343
21704
  }
21344
21705
  function lockfileDisplayPath() {
21345
21706
  const path = lockfilePath();
@@ -21350,7 +21711,7 @@ async function migrateFromKordis() {
21350
21711
  const source = kordisLockfilePath();
21351
21712
  const destination = lockfilePath();
21352
21713
  try {
21353
- await mkdir13(brandHome(), { recursive: true });
21714
+ await mkdir14(brandHome(), { recursive: true });
21354
21715
  await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
21355
21716
  } catch {
21356
21717
  return;
@@ -21378,15 +21739,15 @@ async function readLockfile() {
21378
21739
  }
21379
21740
  }
21380
21741
  async function writeLockfile(lf) {
21381
- await mkdir13(brandHome(), { recursive: true });
21382
- await writeFile11(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
21742
+ await mkdir14(brandHome(), { recursive: true });
21743
+ await writeFile12(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
21383
21744
  encoding: "utf8",
21384
21745
  mode: 384
21385
21746
  });
21386
21747
  }
21387
21748
  async function recordInstalled(fullSlug, entry) {
21388
21749
  const home = brandHome();
21389
- await withFileLock(join21(home, "installed.lock"), home, async () => {
21750
+ await withFileLock(join22(home, "installed.lock"), home, async () => {
21390
21751
  const lf = await readLockfile();
21391
21752
  lf.packages[fullSlug] = entry;
21392
21753
  await writeLockfile(lf);
@@ -21654,7 +22015,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
21654
22015
  );
21655
22016
  for (const f of translated.files) {
21656
22017
  const abs = safeInstallPath(installRoot, f.path);
21657
- await mkdir14(dirname7(abs), { recursive: true });
22018
+ await mkdir15(dirname7(abs), { recursive: true });
21658
22019
  await applyEmittedFile(abs, f, fullSlug, notes);
21659
22020
  written.push(abs);
21660
22021
  }
@@ -21810,7 +22171,7 @@ var TOOL_MARKERS = [
21810
22171
  async function detectTool(root) {
21811
22172
  for (const [path, tool] of TOOL_MARKERS) {
21812
22173
  try {
21813
- await stat5(join22(root, path));
22174
+ await stat6(join23(root, path));
21814
22175
  return tool;
21815
22176
  } catch {
21816
22177
  }
@@ -21826,7 +22187,7 @@ function safeInstallPath(installRoot, relPath) {
21826
22187
  "Nothing was written. The package emits an absolute file path. Tell the author to publish it with relative paths only; if you did not publish it, treat it as an attempt to write outside your project."
21827
22188
  );
21828
22189
  }
21829
- const abs = join22(installRoot, relPath);
22190
+ const abs = join23(installRoot, relPath);
21830
22191
  const resolvedRoot = resolve12(installRoot);
21831
22192
  const resolvedAbs = resolve12(abs);
21832
22193
  if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep3)) {
@@ -21880,9 +22241,9 @@ function safeInstallPath(installRoot, relPath) {
21880
22241
  }
21881
22242
  async function writeEmittedFile(abs, content, encoding) {
21882
22243
  if (encoding === "base64") {
21883
- await writeFile12(abs, Buffer.from(content, "base64"));
22244
+ await writeFile13(abs, Buffer.from(content, "base64"));
21884
22245
  } else {
21885
- await writeFile12(abs, content, "utf8");
22246
+ await writeFile13(abs, content, "utf8");
21886
22247
  }
21887
22248
  }
21888
22249
  async function applyEmittedFile(abs, f, fullSlug, notes) {
@@ -21903,10 +22264,10 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
21903
22264
  `merged into ${f.path} \u2014 comments/formatting in the existing file are not preserved`
21904
22265
  );
21905
22266
  }
21906
- await writeFile12(abs, merged, "utf8");
22267
+ await writeFile13(abs, merged, "utf8");
21907
22268
  return;
21908
22269
  }
21909
- await writeFile12(abs, mergeFencedFile(existingRaw, fullSlug, f.content), "utf8");
22270
+ await writeFile13(abs, mergeFencedFile(existingRaw, fullSlug, f.content), "utf8");
21910
22271
  }
21911
22272
 
21912
22273
  // src/commands/registry/update.ts
@@ -22957,15 +23318,17 @@ function isScopeSpread(value) {
22957
23318
  }
22958
23319
 
22959
23320
  // src/commands/resurface.ts
23321
+ init_setup();
23322
+ init_ui();
22960
23323
  init_deadline();
22961
23324
  init_src();
22962
- import { Command as Command38 } from "commander";
23325
+ import { Command as Command39 } from "commander";
22963
23326
  init_index_build();
22964
23327
  init_cloud_brain();
22965
23328
  init_vault();
22966
23329
  init_ui();
22967
23330
  function resurfaceCommand() {
22968
- return new Command38("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
23331
+ return new Command39("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
22969
23332
  "--brain <name>",
22970
23333
  "read one existing hosted brain by name or id, without changing the pointer"
22971
23334
  ).option(
@@ -22976,6 +23339,11 @@ function resurfaceCommand() {
22976
23339
  async (opts) => {
22977
23340
  const bounded = (work) => opts.deadline !== void 0 && Number.isFinite(opts.deadline) ? withDeadline(opts.deadline, work) : work();
22978
23341
  await bounded(async () => {
23342
+ await refreshOwnHooksAndTell(
23343
+ (text) => (isJsonMode() ? process.stderr : process.stdout).write(`${text}
23344
+
23345
+ `)
23346
+ );
22979
23347
  const contextDir = opts.contextDir ?? process.cwd();
22980
23348
  const where = await resolveTarget({
22981
23349
  ...opts.brain === void 0 ? {} : { brain: opts.brain },
@@ -23187,11 +23555,11 @@ rank by project yet, so that is not one of the signals above.`
23187
23555
  }
23188
23556
 
23189
23557
  // src/commands/rules.ts
23190
- import { Command as Command39 } from "commander";
23558
+ import { Command as Command40 } from "commander";
23191
23559
  init_cloud_brain();
23192
23560
  init_ui();
23193
23561
  function rulesCommand() {
23194
- return new Command39("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
23562
+ return new Command40("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
23195
23563
  "--brain <name>",
23196
23564
  "read one existing hosted brain by name or id, without changing the pointer"
23197
23565
  ).action(async (opts) => {
@@ -23358,16 +23726,16 @@ init_next_step();
23358
23726
  init_provenance();
23359
23727
  import { spawn as spawn4 } from "child_process";
23360
23728
  import { randomBytes as randomBytes2 } from "crypto";
23361
- import { mkdir as mkdir18, open, readFile as readFile27, rename as rename4, rm as rm8, stat as stat7, unlink as unlink4, writeFile as writeFile15 } from "fs/promises";
23362
- import { join as join28 } from "path";
23729
+ import { mkdir as mkdir19, open, readFile as readFile28, rename as rename5, rm as rm8, stat as stat9, unlink as unlink6, writeFile as writeFile17 } from "fs/promises";
23730
+ import { join as join30 } from "path";
23363
23731
  import { Command as Command41 } from "commander";
23364
23732
 
23365
23733
  // src/lib/update-check.ts
23366
23734
  init_atomic();
23367
23735
  init_brand();
23368
23736
  import { execFile as execFile2 } from "child_process";
23369
- import { mkdir as mkdir17, readFile as readFile26, writeFile as writeFile14 } from "fs/promises";
23370
- import { join as join27 } from "path";
23737
+ import { mkdir as mkdir18, readFile as readFile27, writeFile as writeFile16 } from "fs/promises";
23738
+ import { join as join29 } from "path";
23371
23739
  import { promisify as promisify2 } from "util";
23372
23740
  import semver4 from "semver";
23373
23741
  import { z as z33 } from "zod";
@@ -23448,11 +23816,11 @@ var UpdateStateSchema = z33.object({
23448
23816
  message: z33.string().nullable()
23449
23817
  });
23450
23818
  function statePath() {
23451
- return join27(brandHome(), "update-state.json");
23819
+ return join29(brandHome(), "update-state.json");
23452
23820
  }
23453
23821
  async function readUpdateState() {
23454
23822
  try {
23455
- const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile26(statePath(), "utf8")));
23823
+ const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile27(statePath(), "utf8")));
23456
23824
  return parsed.success ? parsed.data : null;
23457
23825
  } catch {
23458
23826
  return null;
@@ -23460,7 +23828,7 @@ async function readUpdateState() {
23460
23828
  }
23461
23829
  async function writeUpdateState(state) {
23462
23830
  try {
23463
- await mkdir17(brandHome(), { recursive: true });
23831
+ await mkdir18(brandHome(), { recursive: true });
23464
23832
  await writeThenRename(statePath(), `${JSON.stringify(state, null, 2)}
23465
23833
  `);
23466
23834
  } catch {
@@ -23572,7 +23940,7 @@ async function runUpdate(opts, inherited) {
23572
23940
  }
23573
23941
  const held = await holdUpdateSlot(inherited);
23574
23942
  if (held === null && await blockedBy() === "stuck") {
23575
- const lock = join28(brandHome(), RECLAIM_LOCK);
23943
+ const lock = join30(brandHome(), RECLAIM_LOCK);
23576
23944
  const hint = `A previous update did not finish, and clearing what it left is blocked by ${lock}. If no memgineering update is running, delete that folder and run \`memgineering update\` again.`;
23577
23945
  printDual({
23578
23946
  json: { ...status, provenance: provenance.kind, updated: false, reason: "update_claim_stuck", hint },
@@ -23687,14 +24055,14 @@ function newClaimToken() {
23687
24055
  }
23688
24056
  async function claimOwner() {
23689
24057
  try {
23690
- return (await readFile27(join28(brandHome(), "update-running"), "utf8")).trim() || null;
24058
+ return (await readFile28(join30(brandHome(), "update-running"), "utf8")).trim() || null;
23691
24059
  } catch {
23692
24060
  return null;
23693
24061
  }
23694
24062
  }
23695
24063
  async function releaseUpdateSlot(token) {
23696
24064
  if (await claimOwner() !== token) return;
23697
- await unlink4(join28(brandHome(), "update-running")).catch(() => void 0);
24065
+ await unlink6(join30(brandHome(), "update-running")).catch(() => void 0);
23698
24066
  }
23699
24067
  async function holdUpdateSlot(inherited) {
23700
24068
  if (inherited !== void 0 && await claimOwner() === inherited) return inherited;
@@ -23706,9 +24074,9 @@ async function holdUpdateSlot(inherited) {
23706
24074
  var HOOK_VERBS = /* @__PURE__ */ new Set(["guard", "resurface"]);
23707
24075
  var UPDATE_CLAIM_MS = 15 * 6e4;
23708
24076
  async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
23709
- const path = join28(brandHome(), "update-running");
24077
+ const path = join30(brandHome(), "update-running");
23710
24078
  try {
23711
- await mkdir18(brandHome(), { recursive: true });
24079
+ await mkdir19(brandHome(), { recursive: true });
23712
24080
  const handle = await open(path, "wx");
23713
24081
  await handle.writeFile(`${token}
23714
24082
  `);
@@ -23718,12 +24086,12 @@ async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
23718
24086
  if (err?.code !== "EEXIST") return false;
23719
24087
  }
23720
24088
  try {
23721
- if (Date.now() - (await stat7(path)).mtimeMs >= UPDATE_CLAIM_MS) {
24089
+ if (Date.now() - (await stat9(path)).mtimeMs >= UPDATE_CLAIM_MS) {
23722
24090
  await hooks.afterObserve?.();
23723
24091
  await withReclaimLock(async () => {
23724
- if (Date.now() - (await stat7(path)).mtimeMs < UPDATE_CLAIM_MS) return;
24092
+ if (Date.now() - (await stat9(path)).mtimeMs < UPDATE_CLAIM_MS) return;
23725
24093
  await hooks.afterRecheck?.();
23726
- await unlink4(path);
24094
+ await unlink6(path);
23727
24095
  });
23728
24096
  }
23729
24097
  } catch {
@@ -23734,13 +24102,13 @@ var RECLAIM_LOCK = "update-running.reclaim";
23734
24102
  var RECLAIM_LOCK_MAX_MS = 10 * 6e4;
23735
24103
  async function withReclaimLock(fn) {
23736
24104
  const home = brandHome();
23737
- const lock = join28(home, RECLAIM_LOCK);
23738
- const staging = join28(home, `${RECLAIM_LOCK}.${process.pid}.${randomBytes2(4).toString("hex")}`);
23739
- await mkdir18(staging, { recursive: true });
23740
- await writeFile15(join28(staging, "owner"), `${process.pid} ${Date.now()}
24105
+ const lock = join30(home, RECLAIM_LOCK);
24106
+ const staging = join30(home, `${RECLAIM_LOCK}.${process.pid}.${randomBytes2(4).toString("hex")}`);
24107
+ await mkdir19(staging, { recursive: true });
24108
+ await writeFile17(join30(staging, "owner"), `${process.pid} ${Date.now()}
23741
24109
  `);
23742
24110
  try {
23743
- await rename4(staging, lock);
24111
+ await rename5(staging, lock);
23744
24112
  } catch {
23745
24113
  await rm8(staging, { recursive: true, force: true }).catch(() => void 0);
23746
24114
  await breakAbandonedLock(lock);
@@ -23750,18 +24118,18 @@ async function withReclaimLock(fn) {
23750
24118
  return await fn();
23751
24119
  } finally {
23752
24120
  const aside = `${lock}.done.${randomBytes2(4).toString("hex")}`;
23753
- await rename4(lock, aside).then(() => rm8(aside, { recursive: true, force: true })).catch(() => void 0);
24121
+ await rename5(lock, aside).then(() => rm8(aside, { recursive: true, force: true })).catch(() => void 0);
23754
24122
  }
23755
24123
  }
23756
24124
  async function breakAbandonedLock(lock) {
23757
24125
  try {
23758
- const seen = (await readFile27(join28(lock, "owner"), "utf8")).trim();
24126
+ const seen = (await readFile28(join30(lock, "owner"), "utf8")).trim();
23759
24127
  if (!lockAbandoned(seen)) return;
23760
24128
  const aside = `${lock}.dead.${randomBytes2(4).toString("hex")}`;
23761
- await rename4(lock, aside);
23762
- const moved = (await readFile27(join28(aside, "owner"), "utf8").catch(() => "")).trim();
24129
+ await rename5(lock, aside);
24130
+ const moved = (await readFile28(join30(aside, "owner"), "utf8").catch(() => "")).trim();
23763
24131
  if (moved !== seen) {
23764
- await rename4(aside, lock).catch(() => void 0);
24132
+ await rename5(aside, lock).catch(() => void 0);
23765
24133
  return;
23766
24134
  }
23767
24135
  await rm8(aside, { recursive: true, force: true });
@@ -23783,9 +24151,9 @@ function lockAbandoned(owner, now = Date.now()) {
23783
24151
  }
23784
24152
  async function blockedBy() {
23785
24153
  try {
23786
- const old = Date.now() - (await stat7(join28(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
24154
+ const old = Date.now() - (await stat9(join30(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
23787
24155
  if (old) {
23788
- await stat7(join28(brandHome(), RECLAIM_LOCK));
24156
+ await stat9(join30(brandHome(), RECLAIM_LOCK));
23789
24157
  return "stuck";
23790
24158
  }
23791
24159
  } catch {
@@ -23817,8 +24185,8 @@ function reportBelowMinimum(below) {
23817
24185
 
23818
24186
  // src/commands/undo.ts
23819
24187
  init_src();
23820
- import { readFile as readFile28, rm as rm9 } from "fs/promises";
23821
- import { join as join29 } from "path";
24188
+ import { readFile as readFile29, rm as rm9 } from "fs/promises";
24189
+ import { join as join31 } from "path";
23822
24190
  import { Command as Command42 } from "commander";
23823
24191
  init_atomic();
23824
24192
  init_errors();
@@ -23915,8 +24283,8 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
23915
24283
  );
23916
24284
  }
23917
24285
  const record = await withWriteLock(brain.root, async () => {
23918
- const absolute = join29(brain.root, target.target);
23919
- const current = await readFile28(absolute, "utf8").catch(() => null);
24286
+ const absolute = join31(brain.root, target.target);
24287
+ const current = await readFile29(absolute, "utf8").catch(() => null);
23920
24288
  const actualHash = current === null ? null : hashContent(current);
23921
24289
  if (actualHash !== target.hash_after) {
23922
24290
  throw memgError(
@@ -23978,7 +24346,7 @@ function minutesSince(iso) {
23978
24346
  return Math.max(0, Math.floor((Date.now() - at) / 6e4));
23979
24347
  }
23980
24348
  async function reverse(brain, op, current) {
23981
- const absolute = join29(brain.root, op.target);
24349
+ const absolute = join31(brain.root, op.target);
23982
24350
  if (op.verb === "remember") {
23983
24351
  if (current === null) {
23984
24352
  throw memgError(
@@ -24035,7 +24403,7 @@ async function conflictHint(vaultRoot, op) {
24035
24403
  Read what it says now, then decide what should stay.`;
24036
24404
  const snapshot = await readSnapshot(vaultRoot, op.op_id);
24037
24405
  return snapshot === null ? base2 : `${base2}
24038
- The text from before that change is still on this machine: ${join29(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
24406
+ The text from before that change is still on this machine: ${join31(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
24039
24407
  }
24040
24408
 
24041
24409
  // src/commands/brains.ts
@@ -24720,6 +25088,10 @@ async function run3(argv) {
24720
25088
  });
24721
25089
  await program.parseAsync(cleaned);
24722
25090
  if (ran !== "update") await backgroundUpdateNotice(ran === void 0 ? {} : { verb: ran });
25091
+ if (ran !== void 0 && !["guard", "resurface", "setup"].includes(ran)) {
25092
+ await refreshOwnHooksAndTell((text) => process.stderr.write(`${text}
25093
+ `));
25094
+ }
24723
25095
  }
24724
25096
  async function handleError(err) {
24725
25097
  const failure = adaptRegistryError(err) ?? err;