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/CHANGELOG.md +44 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-setup/SKILL.md +14 -5
- package/dist/index.js +513 -141
- package/package.json +1 -1
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 = (
|
|
1329
|
-
const b = fold(
|
|
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
|
|
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(
|
|
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
|
|
7903
|
-
import { join as
|
|
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
|
|
8137
|
+
return join25(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
|
|
7909
8138
|
}
|
|
7910
8139
|
async function connectedNoticeShown(at) {
|
|
7911
|
-
return
|
|
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
|
|
7919
|
-
await
|
|
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 =
|
|
8154
|
+
const marker = join25(brandHome(), "setup-notice-shown");
|
|
7926
8155
|
try {
|
|
7927
|
-
await
|
|
8156
|
+
await readFile25(marker, "utf8");
|
|
7928
8157
|
return false;
|
|
7929
8158
|
} catch {
|
|
7930
8159
|
}
|
|
7931
8160
|
try {
|
|
7932
|
-
await
|
|
7933
|
-
await
|
|
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
|
|
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(
|
|
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(
|
|
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" ?
|
|
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
|
|
8503
|
+
import { readdir as readdir9, stat as stat8 } from "fs/promises";
|
|
8275
8504
|
import { homedir as homedir5 } from "os";
|
|
8276
|
-
import { basename as
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
8875
|
+
const parent = dirname10(existing);
|
|
8647
8876
|
if (parent === existing) break;
|
|
8648
|
-
rest.unshift(
|
|
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
|
|
8696
|
-
import { dirname as
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
9079
|
-
const path =
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
9538
|
+
const existing = await readFile26(filePath, "utf8").catch(() => null);
|
|
9303
9539
|
if (existing === null) {
|
|
9304
9540
|
if (!dryRun) {
|
|
9305
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
9361
|
-
const raw = await
|
|
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
|
|
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 =
|
|
9411
|
-
const raw = await
|
|
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
|
|
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 =
|
|
9444
|
-
const bundled =
|
|
9445
|
-
if (await exists2(
|
|
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 =
|
|
9449
|
-
if (await exists2(
|
|
9450
|
-
const parent =
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
17416
|
-
import { dirname as dirname5, isAbsolute as isAbsolute3, join as
|
|
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))
|
|
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)
|
|
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)
|
|
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 = () =>
|
|
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
|
|
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
|
|
17652
|
-
await
|
|
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
|
|
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
|
|
17668
|
-
const path =
|
|
18027
|
+
for (const name of await readdir7(dir)) {
|
|
18028
|
+
const path = join19(dir, name);
|
|
17669
18029
|
if (!isAbsolute3(path)) continue;
|
|
17670
|
-
const info = await
|
|
17671
|
-
if (info && now - info.mtimeMs > MARKER_TTL_MS) await
|
|
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
|
|
18691
|
-
import { dirname as dirname6, isAbsolute as isAbsolute4, join as
|
|
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
|
|
18760
|
-
await
|
|
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(
|
|
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
|
|
20157
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
19797
20158
|
function makeUntrustedSentinel() {
|
|
19798
|
-
return `KORDIS-UNTRUSTED-${
|
|
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
|
|
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:
|
|
20340
|
-
{ path:
|
|
20341
|
-
{ path:
|
|
20342
|
-
{ path:
|
|
20343
|
-
{ path:
|
|
20344
|
-
{ path:
|
|
20345
|
-
{ path:
|
|
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
|
|
20751
|
-
import { dirname as dirname7, join as
|
|
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
|
|
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
|
|
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
|
|
21700
|
+
return join22(brandHome(), "installed.json");
|
|
21340
21701
|
}
|
|
21341
21702
|
function kordisLockfilePath() {
|
|
21342
|
-
return
|
|
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
|
|
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
|
|
21382
|
-
await
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
22244
|
+
await writeFile13(abs, Buffer.from(content, "base64"));
|
|
21884
22245
|
} else {
|
|
21885
|
-
await
|
|
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
|
|
22267
|
+
await writeFile13(abs, merged, "utf8");
|
|
21907
22268
|
return;
|
|
21908
22269
|
}
|
|
21909
|
-
await
|
|
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
|
|
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
|
|
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
|
|
23558
|
+
import { Command as Command40 } from "commander";
|
|
23191
23559
|
init_cloud_brain();
|
|
23192
23560
|
init_ui();
|
|
23193
23561
|
function rulesCommand() {
|
|
23194
|
-
return new
|
|
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
|
|
23362
|
-
import { join as
|
|
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
|
|
23370
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
24077
|
+
const path = join30(brandHome(), "update-running");
|
|
23710
24078
|
try {
|
|
23711
|
-
await
|
|
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
|
|
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
|
|
24092
|
+
if (Date.now() - (await stat9(path)).mtimeMs < UPDATE_CLAIM_MS) return;
|
|
23725
24093
|
await hooks.afterRecheck?.();
|
|
23726
|
-
await
|
|
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 =
|
|
23738
|
-
const staging =
|
|
23739
|
-
await
|
|
23740
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
23762
|
-
const moved = (await
|
|
24129
|
+
await rename5(lock, aside);
|
|
24130
|
+
const moved = (await readFile28(join30(aside, "owner"), "utf8").catch(() => "")).trim();
|
|
23763
24131
|
if (moved !== seen) {
|
|
23764
|
-
await
|
|
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
|
|
24154
|
+
const old = Date.now() - (await stat9(join30(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
|
|
23787
24155
|
if (old) {
|
|
23788
|
-
await
|
|
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
|
|
23821
|
-
import { join as
|
|
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 =
|
|
23919
|
-
const current = await
|
|
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 =
|
|
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: ${
|
|
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;
|