memgineering 0.25.2 → 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
|
@@ -1065,22 +1065,6 @@ function mapNoteToMemory(input) {
|
|
|
1065
1065
|
issues
|
|
1066
1066
|
};
|
|
1067
1067
|
}
|
|
1068
|
-
function isUntouchedBase(path, content) {
|
|
1069
|
-
if (!/^01_BASE\//.test(path)) return false;
|
|
1070
|
-
const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/, "");
|
|
1071
|
-
const lines2 = body.split(/\r?\n/).map((l) => l.trimEnd());
|
|
1072
|
-
let seenHeading = false;
|
|
1073
|
-
for (const line of lines2) {
|
|
1074
|
-
if (line.trim() === "") continue;
|
|
1075
|
-
if (!seenHeading && line.startsWith("# ")) {
|
|
1076
|
-
seenHeading = true;
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
if (/^-\s*$/.test(line)) continue;
|
|
1080
|
-
if (/^[-*+]\s+\S/.test(line) || /^#{1,6}\s/.test(line)) return false;
|
|
1081
|
-
}
|
|
1082
|
-
return seenHeading;
|
|
1083
|
-
}
|
|
1084
1068
|
var NAMESPACE, isRecord, asString, asStringArray, AUTHORABLE;
|
|
1085
1069
|
var init_markdown_mapping = __esm({
|
|
1086
1070
|
"../../packages/memory-engine/src/markdown-mapping.ts"() {
|
|
@@ -1341,8 +1325,8 @@ var init_exclude = __esm({
|
|
|
1341
1325
|
[...ALWAYS_SKIP_DIRS].map((d) => fold(d))
|
|
1342
1326
|
);
|
|
1343
1327
|
ALWAYS_SKIP_SEGMENTS_FOLDED = ALWAYS_SKIP_SEGMENTS.map((s) => fold(s));
|
|
1344
|
-
ALWAYS_SKIP_FILE = (
|
|
1345
|
-
const b = fold(
|
|
1328
|
+
ALWAYS_SKIP_FILE = (basename5) => {
|
|
1329
|
+
const b = fold(basename5);
|
|
1346
1330
|
return b.startsWith(".env") || b === ".ds_store" || b === ".memgdeny" || b === ".memgallow" || b === ".memgignore";
|
|
1347
1331
|
};
|
|
1348
1332
|
EMPTY_IGNORE_SET = { rules: [], unsupported: [] };
|
|
@@ -2441,6 +2425,21 @@ var init_planner = __esm({
|
|
|
2441
2425
|
}
|
|
2442
2426
|
});
|
|
2443
2427
|
|
|
2428
|
+
// ../../packages/memory-engine/src/note-body.ts
|
|
2429
|
+
function bodyOf(content) {
|
|
2430
|
+
const stripped = content.startsWith(BOM) ? content.slice(BOM.length) : content;
|
|
2431
|
+
const fence2 = FENCE.exec(stripped);
|
|
2432
|
+
return fence2 ? stripped.slice(fence2[0].length) : stripped;
|
|
2433
|
+
}
|
|
2434
|
+
var BOM, FENCE;
|
|
2435
|
+
var init_note_body = __esm({
|
|
2436
|
+
"../../packages/memory-engine/src/note-body.ts"() {
|
|
2437
|
+
"use strict";
|
|
2438
|
+
BOM = String.fromCharCode(65279);
|
|
2439
|
+
FENCE = /^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/;
|
|
2440
|
+
}
|
|
2441
|
+
});
|
|
2442
|
+
|
|
2444
2443
|
// ../../packages/memory-engine/src/base-templates.ts
|
|
2445
2444
|
function base(id, title, summary, body) {
|
|
2446
2445
|
return [
|
|
@@ -2461,10 +2460,31 @@ function base(id, title, summary, body) {
|
|
|
2461
2460
|
function ownEntry(record, key2) {
|
|
2462
2461
|
return Object.hasOwn(record, key2) ? record[key2] : void 0;
|
|
2463
2462
|
}
|
|
2464
|
-
|
|
2463
|
+
function wordingsFor(path) {
|
|
2464
|
+
const current = ownEntry(BASE_TEMPLATES, path);
|
|
2465
|
+
const retired = ownEntry(RETIRED_BASE_TEMPLATES, path) ?? [];
|
|
2466
|
+
return current === void 0 ? retired : [current, ...retired];
|
|
2467
|
+
}
|
|
2468
|
+
function isExactUntouchedBase(path, content) {
|
|
2469
|
+
const saved = asSaved(content);
|
|
2470
|
+
return wordingsFor(path).some((t) => asSaved(t) === saved);
|
|
2471
|
+
}
|
|
2472
|
+
function isTemplateBody(path, content) {
|
|
2473
|
+
const lines2 = bodyLines(content).join("\n");
|
|
2474
|
+
return wordingsFor(path).some((t) => bodyLines(t).join("\n") === lines2);
|
|
2475
|
+
}
|
|
2476
|
+
function isUnwrittenBody(path, content) {
|
|
2477
|
+
if (isTemplateBody(path, content)) return true;
|
|
2478
|
+
const [first, ...rest] = bodyLines(content);
|
|
2479
|
+
if (first === void 0) return false;
|
|
2480
|
+
const headings = wordingsFor(path).map((t) => bodyLines(t)[0]);
|
|
2481
|
+
return headings.includes(first) && rest.every((line) => /^-\s*$/.test(line));
|
|
2482
|
+
}
|
|
2483
|
+
var BASE_TEMPLATES, RETIRED_BASE_TEMPLATES, BOM2, asSaved, bodyLines;
|
|
2465
2484
|
var init_base_templates = __esm({
|
|
2466
2485
|
"../../packages/memory-engine/src/base-templates.ts"() {
|
|
2467
2486
|
"use strict";
|
|
2487
|
+
init_note_body();
|
|
2468
2488
|
BASE_TEMPLATES = {
|
|
2469
2489
|
"01_BASE/CRITICAL_FACTS.md": base(
|
|
2470
2490
|
"critical-facts",
|
|
@@ -2545,6 +2565,9 @@ var init_base_templates = __esm({
|
|
|
2545
2565
|
].join("\n")
|
|
2546
2566
|
]
|
|
2547
2567
|
};
|
|
2568
|
+
BOM2 = String.fromCharCode(65279);
|
|
2569
|
+
asSaved = (text) => (text.startsWith(BOM2) ? text.slice(BOM2.length) : text).replace(/\r\n/g, "\n").trimEnd();
|
|
2570
|
+
bodyLines = (content) => bodyOf(content).split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "");
|
|
2548
2571
|
}
|
|
2549
2572
|
});
|
|
2550
2573
|
|
|
@@ -4050,11 +4073,6 @@ function bodyStillAsserts(content, oldClaim, newClaim, normalize) {
|
|
|
4050
4073
|
if (line === normalize(newClaim?.split("\n")[0] ?? "").trim()) return null;
|
|
4051
4074
|
return bodyOf(content).includes(line) ? line : null;
|
|
4052
4075
|
}
|
|
4053
|
-
function bodyOf(content) {
|
|
4054
|
-
const stripped = content.replace(/^/, "");
|
|
4055
|
-
const fence2 = /^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/.exec(stripped);
|
|
4056
|
-
return fence2 ? stripped.slice(fence2[0].length) : stripped;
|
|
4057
|
-
}
|
|
4058
4076
|
function previousClaim(entry) {
|
|
4059
4077
|
const current = entry.claims.find((claim) => claim.id === entry.memory.current_claim) ?? entry.claims[0];
|
|
4060
4078
|
return current?.text ?? null;
|
|
@@ -4072,6 +4090,7 @@ var init_curate = __esm({
|
|
|
4072
4090
|
init_planner();
|
|
4073
4091
|
init_schema();
|
|
4074
4092
|
init_rule_budget();
|
|
4093
|
+
init_note_body();
|
|
4075
4094
|
NO_REASON_GIVEN = "revised directly";
|
|
4076
4095
|
noLifecycleReason = (action) => `${action}d directly`;
|
|
4077
4096
|
}
|
|
@@ -5522,6 +5541,13 @@ var init_config = __esm({
|
|
|
5522
5541
|
* Absent on a machine set up before it was recorded.
|
|
5523
5542
|
*/
|
|
5524
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),
|
|
5525
5551
|
/**
|
|
5526
5552
|
* Notes `setup --web` was asked to upload, not yet sent.
|
|
5527
5553
|
*
|
|
@@ -6714,7 +6740,7 @@ var init_cloud_pointer = __esm({
|
|
|
6714
6740
|
|
|
6715
6741
|
// src/commands/link.ts
|
|
6716
6742
|
import { readFile as readFile18 } from "fs/promises";
|
|
6717
|
-
import { join as
|
|
6743
|
+
import { join as join18 } from "path";
|
|
6718
6744
|
import { Command as Command13 } from "commander";
|
|
6719
6745
|
import prompts2 from "prompts";
|
|
6720
6746
|
function sameBasename(all, entry) {
|
|
@@ -6918,7 +6944,7 @@ async function recordLink(adapter, observationsDir) {
|
|
|
6918
6944
|
}
|
|
6919
6945
|
async function refuseSilentRules(path, denied, excluded, unsupported) {
|
|
6920
6946
|
const denyEntries = (await LocalFileReadAdapter.readDenyFile(path, ".memgdeny")).entries;
|
|
6921
|
-
const rawDenyLines = await readFile18(
|
|
6947
|
+
const rawDenyLines = await readFile18(join18(path, ".memgdeny"), "utf8").then(
|
|
6922
6948
|
(t) => t.split("\n").map((l) => l.trim()).filter((l) => l !== "" && !l.startsWith("#"))
|
|
6923
6949
|
).catch(() => []);
|
|
6924
6950
|
const asWritten = (folded) => rawDenyLines.find(
|
|
@@ -7546,6 +7572,228 @@ var init_push = __esm({
|
|
|
7546
7572
|
}
|
|
7547
7573
|
});
|
|
7548
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
|
+
|
|
7549
7797
|
// src/commands/setup-brain.ts
|
|
7550
7798
|
function migration(localBrains) {
|
|
7551
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.`;
|
|
@@ -7880,38 +8128,38 @@ var init_setup_signin = __esm({
|
|
|
7880
8128
|
});
|
|
7881
8129
|
|
|
7882
8130
|
// src/lib/next-step.ts
|
|
7883
|
-
import { mkdir as
|
|
7884
|
-
import { join as
|
|
8131
|
+
import { mkdir as mkdir16, readFile as readFile25, writeFile as writeFile15 } from "fs/promises";
|
|
8132
|
+
import { join as join25 } from "path";
|
|
7885
8133
|
function isSetUp(cfg) {
|
|
7886
8134
|
return Object.keys(cfg.system.installed).length > 0;
|
|
7887
8135
|
}
|
|
7888
8136
|
function connectedMarker(at) {
|
|
7889
|
-
return
|
|
8137
|
+
return join25(brandHome(), `setup-connected-shown-${at.replace(/[^0-9A-Za-z]/g, "")}`);
|
|
7890
8138
|
}
|
|
7891
8139
|
async function connectedNoticeShown(at) {
|
|
7892
|
-
return
|
|
8140
|
+
return readFile25(connectedMarker(at), "utf8").then(
|
|
7893
8141
|
() => true,
|
|
7894
8142
|
() => false
|
|
7895
8143
|
);
|
|
7896
8144
|
}
|
|
7897
8145
|
async function markConnectedNoticeShown(at) {
|
|
7898
8146
|
try {
|
|
7899
|
-
await
|
|
7900
|
-
await
|
|
8147
|
+
await mkdir16(brandHome(), { recursive: true });
|
|
8148
|
+
await writeFile15(connectedMarker(at), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7901
8149
|
`);
|
|
7902
8150
|
} catch {
|
|
7903
8151
|
}
|
|
7904
8152
|
}
|
|
7905
8153
|
async function claimFullSetupNotice() {
|
|
7906
|
-
const marker =
|
|
8154
|
+
const marker = join25(brandHome(), "setup-notice-shown");
|
|
7907
8155
|
try {
|
|
7908
|
-
await
|
|
8156
|
+
await readFile25(marker, "utf8");
|
|
7909
8157
|
return false;
|
|
7910
8158
|
} catch {
|
|
7911
8159
|
}
|
|
7912
8160
|
try {
|
|
7913
|
-
await
|
|
7914
|
-
await
|
|
8161
|
+
await mkdir16(brandHome(), { recursive: true });
|
|
8162
|
+
await writeFile15(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
7915
8163
|
`);
|
|
7916
8164
|
} catch {
|
|
7917
8165
|
}
|
|
@@ -8077,12 +8325,12 @@ Synced instead? \`memgineering login\`, then \`memgineering create <name>\`.`;
|
|
|
8077
8325
|
// src/lib/provenance.ts
|
|
8078
8326
|
import { execFile, spawn as spawn2 } from "child_process";
|
|
8079
8327
|
import { realpath as realpath3 } from "fs/promises";
|
|
8080
|
-
import { dirname as
|
|
8328
|
+
import { dirname as dirname9, join as join26, resolve as resolve14 } from "path";
|
|
8081
8329
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8082
8330
|
import { promisify } from "util";
|
|
8083
8331
|
async function detectProvenance(selfPath, opts = {}) {
|
|
8084
8332
|
const entry = selfPath ?? fileURLToPath2(import.meta.url);
|
|
8085
|
-
const selfDir = await realpath3(
|
|
8333
|
+
const selfDir = await realpath3(dirname9(entry)).catch(() => dirname9(entry));
|
|
8086
8334
|
if (/[\\/]_npx[\\/]|[\\/]\.npm[\\/]_cacache[\\/]/.test(selfDir)) {
|
|
8087
8335
|
return {
|
|
8088
8336
|
kind: "npx",
|
|
@@ -8200,7 +8448,7 @@ function isInside2(child, parent) {
|
|
|
8200
8448
|
}
|
|
8201
8449
|
async function unreachableByName(selfPath) {
|
|
8202
8450
|
const entry = selfPath ?? fileURLToPath2(import.meta.url);
|
|
8203
|
-
const selfDir = await realpath3(
|
|
8451
|
+
const selfDir = await realpath3(dirname9(entry)).catch(() => dirname9(entry));
|
|
8204
8452
|
if (!/[\\/](node_modules|_npx|_cacache)[\\/]/.test(`${selfDir}/`)) return null;
|
|
8205
8453
|
const p = await detectProvenance(entry);
|
|
8206
8454
|
return p.kind === "local" || p.kind === "npx" ? p.reason : null;
|
|
@@ -8215,7 +8463,7 @@ var init_provenance = __esm({
|
|
|
8215
8463
|
kind: "npm",
|
|
8216
8464
|
bin: "npm",
|
|
8217
8465
|
prefixArgs: ["prefix", "-g"],
|
|
8218
|
-
moduleRoot: (prefix) => process.platform === "win32" ?
|
|
8466
|
+
moduleRoot: (prefix) => process.platform === "win32" ? join26(prefix, "node_modules") : join26(prefix, "lib", "node_modules"),
|
|
8219
8467
|
install: "npm i -g memgineering@latest"
|
|
8220
8468
|
},
|
|
8221
8469
|
{
|
|
@@ -8252,9 +8500,9 @@ __export(setup_cloud_exports, {
|
|
|
8252
8500
|
});
|
|
8253
8501
|
import { spawn as spawn3 } from "child_process";
|
|
8254
8502
|
import { realpathSync as realpathSync2 } from "fs";
|
|
8255
|
-
import { readdir as
|
|
8503
|
+
import { readdir as readdir9, stat as stat8 } from "fs/promises";
|
|
8256
8504
|
import { homedir as homedir5 } from "os";
|
|
8257
|
-
import { basename as
|
|
8505
|
+
import { basename as basename4, dirname as dirname10, join as join27, resolve as resolve15, sep as sep4 } from "path";
|
|
8258
8506
|
async function report2(setupToken, state, detail, code) {
|
|
8259
8507
|
try {
|
|
8260
8508
|
await apiRequest({
|
|
@@ -8272,16 +8520,16 @@ async function report2(setupToken, state, detail, code) {
|
|
|
8272
8520
|
}
|
|
8273
8521
|
async function folderCandidates() {
|
|
8274
8522
|
const home = homedir5();
|
|
8275
|
-
const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) =>
|
|
8523
|
+
const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join27(home, d));
|
|
8276
8524
|
const found = [];
|
|
8277
8525
|
const countNotes = async (dir) => {
|
|
8278
8526
|
let count = 0;
|
|
8279
|
-
const entries = await
|
|
8527
|
+
const entries = await readdir9(dir, { withFileTypes: true }).catch(() => []);
|
|
8280
8528
|
for (const entry of entries) {
|
|
8281
8529
|
if (entry.name.startsWith(".")) continue;
|
|
8282
8530
|
if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) count += 1;
|
|
8283
8531
|
else if (entry.isDirectory()) {
|
|
8284
|
-
const inner = await
|
|
8532
|
+
const inner = await readdir9(join27(dir, entry.name)).catch(() => []);
|
|
8285
8533
|
count += inner.filter((n) => /\.(md|markdown)$/i.test(n)).length;
|
|
8286
8534
|
}
|
|
8287
8535
|
if (count > 999) break;
|
|
@@ -8289,7 +8537,7 @@ async function folderCandidates() {
|
|
|
8289
8537
|
return count;
|
|
8290
8538
|
};
|
|
8291
8539
|
for (const root of roots) {
|
|
8292
|
-
const info = await
|
|
8540
|
+
const info = await stat8(root).catch(() => null);
|
|
8293
8541
|
if (!info?.isDirectory()) continue;
|
|
8294
8542
|
const notes = await countNotes(root);
|
|
8295
8543
|
if (notes > 0) found.push({ path: root, note_count: notes });
|
|
@@ -8624,9 +8872,9 @@ function insideFolder(target, folder) {
|
|
|
8624
8872
|
realpathSync2(existing);
|
|
8625
8873
|
break;
|
|
8626
8874
|
} catch {
|
|
8627
|
-
const parent =
|
|
8875
|
+
const parent = dirname10(existing);
|
|
8628
8876
|
if (parent === existing) break;
|
|
8629
|
-
rest.unshift(
|
|
8877
|
+
rest.unshift(basename4(existing));
|
|
8630
8878
|
existing = parent;
|
|
8631
8879
|
}
|
|
8632
8880
|
}
|
|
@@ -8673,14 +8921,14 @@ var init_setup_cloud = __esm({
|
|
|
8673
8921
|
|
|
8674
8922
|
// src/commands/setup.ts
|
|
8675
8923
|
import { createHash as createHash9 } from "crypto";
|
|
8676
|
-
import { access as access2, mkdir as
|
|
8677
|
-
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";
|
|
8678
8926
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8679
|
-
import { Command as
|
|
8927
|
+
import { Command as Command38 } from "commander";
|
|
8680
8928
|
import prompts5 from "prompts";
|
|
8681
8929
|
import { homedir as homedir6 } from "os";
|
|
8682
8930
|
function setupCommand() {
|
|
8683
|
-
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(
|
|
8684
8932
|
"--wait",
|
|
8685
8933
|
"with --web: block until the screen is finished, even with nobody watching the terminal"
|
|
8686
8934
|
).option(
|
|
@@ -9041,7 +9289,7 @@ async function applySetup(choice, dryRun) {
|
|
|
9041
9289
|
}
|
|
9042
9290
|
async function write2() {
|
|
9043
9291
|
for (const tool of choice.tools) {
|
|
9044
|
-
const hubPath =
|
|
9292
|
+
const hubPath = join28(agentHome(), tool.hubFile);
|
|
9045
9293
|
const written = await injectBlock(
|
|
9046
9294
|
hubPath,
|
|
9047
9295
|
hub + tailFor(tool, choice.hook),
|
|
@@ -9056,8 +9304,8 @@ async function applySetup(choice, dryRun) {
|
|
|
9056
9304
|
});
|
|
9057
9305
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
9058
9306
|
for (const skill of BRAND.subSkills) {
|
|
9059
|
-
const content = await loadAsset(
|
|
9060
|
-
const path =
|
|
9307
|
+
const content = await loadAsset(join28(skill, "SKILL.md"));
|
|
9308
|
+
const path = join28(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
9061
9309
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
9062
9310
|
results.push({
|
|
9063
9311
|
tool: tool.label,
|
|
@@ -9073,7 +9321,14 @@ async function applySetup(choice, dryRun) {
|
|
|
9073
9321
|
results.push({
|
|
9074
9322
|
tool: tool.label,
|
|
9075
9323
|
what: hook.what,
|
|
9076
|
-
|
|
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
|
+
})()
|
|
9077
9332
|
});
|
|
9078
9333
|
}
|
|
9079
9334
|
}
|
|
@@ -9140,7 +9395,7 @@ async function hooksArmed(tools) {
|
|
|
9140
9395
|
const capable = tools.filter((t) => t.hooks && t.hooksFile);
|
|
9141
9396
|
if (capable.length === 0) return void 0;
|
|
9142
9397
|
for (const tool of capable) {
|
|
9143
|
-
const raw = await
|
|
9398
|
+
const raw = await readFile26(join28(agentHome(), ...(tool.hooksFile ?? "").split("/")), "utf8").catch(
|
|
9144
9399
|
() => ""
|
|
9145
9400
|
);
|
|
9146
9401
|
if (hooksFor([tool]).some((spec) => raw.includes(spec.signature))) return "on";
|
|
@@ -9180,7 +9435,7 @@ async function detectTools() {
|
|
|
9180
9435
|
const home = agentHome();
|
|
9181
9436
|
const found = [];
|
|
9182
9437
|
for (const tool of TOOLS) {
|
|
9183
|
-
if (await exists2(
|
|
9438
|
+
if (await exists2(join28(home, tool.dir))) found.push(tool);
|
|
9184
9439
|
}
|
|
9185
9440
|
return found;
|
|
9186
9441
|
}
|
|
@@ -9280,10 +9535,10 @@ async function injectBlock(filePath, content, dryRun, weWroteThis) {
|
|
|
9280
9535
|
const block = `${BRAND.hubMarkerStart}
|
|
9281
9536
|
${content.trimEnd()}
|
|
9282
9537
|
${BRAND.hubMarkerEnd}`;
|
|
9283
|
-
const existing = await
|
|
9538
|
+
const existing = await readFile26(filePath, "utf8").catch(() => null);
|
|
9284
9539
|
if (existing === null) {
|
|
9285
9540
|
if (!dryRun) {
|
|
9286
|
-
await
|
|
9541
|
+
await mkdir17(dirname11(filePath), { recursive: true });
|
|
9287
9542
|
await writeThenRename(filePath, `${block}
|
|
9288
9543
|
`);
|
|
9289
9544
|
}
|
|
@@ -9323,23 +9578,56 @@ function looksLikeOurGuide(text) {
|
|
|
9323
9578
|
return text.includes("memgineering recall") && text.includes("memgineering remember");
|
|
9324
9579
|
}
|
|
9325
9580
|
async function writeIfDifferent(filePath, content, dryRun) {
|
|
9326
|
-
const existing = await
|
|
9581
|
+
const existing = await readFile26(filePath, "utf8").catch(() => null);
|
|
9327
9582
|
const after = fingerprint(content);
|
|
9328
9583
|
if (existing === content) return { outcome: "unchanged", before: existing, after };
|
|
9329
9584
|
if (!dryRun) {
|
|
9330
|
-
await
|
|
9585
|
+
await mkdir17(dirname11(filePath), { recursive: true });
|
|
9331
9586
|
await writeThenRename(filePath, content);
|
|
9332
9587
|
}
|
|
9333
9588
|
return { outcome: existing === null ? "injected" : "updated", before: existing, after };
|
|
9334
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
|
+
}
|
|
9335
9623
|
function hooksFor(tools) {
|
|
9336
9624
|
return HOOKS.filter(
|
|
9337
9625
|
(h) => tools.some((t) => t.hooks && (h.tools ?? DEFAULT_HOOK_TOOLS).includes(t.id))
|
|
9338
9626
|
);
|
|
9339
9627
|
}
|
|
9340
9628
|
async function installHook(spec, tool, dryRun) {
|
|
9341
|
-
const settingsPath =
|
|
9342
|
-
const raw = await
|
|
9629
|
+
const settingsPath = join28(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9630
|
+
const raw = await readFile26(settingsPath, "utf8").catch(() => null);
|
|
9343
9631
|
let settings = {};
|
|
9344
9632
|
if (raw !== null) {
|
|
9345
9633
|
try {
|
|
@@ -9381,15 +9669,15 @@ async function installHook(spec, tool, dryRun) {
|
|
|
9381
9669
|
}
|
|
9382
9670
|
const next = { ...settings, hooks: { ...hooks, [spec.event]: existingGroups } };
|
|
9383
9671
|
if (!dryRun) {
|
|
9384
|
-
await
|
|
9672
|
+
await mkdir17(dirname11(settingsPath), { recursive: true });
|
|
9385
9673
|
await writeThenRename(settingsPath, `${JSON.stringify(next, null, 2)}
|
|
9386
9674
|
`);
|
|
9387
9675
|
}
|
|
9388
9676
|
return raw === null ? "injected" : "updated";
|
|
9389
9677
|
}
|
|
9390
9678
|
async function removeHook(spec, tool, dryRun) {
|
|
9391
|
-
const settingsPath =
|
|
9392
|
-
const raw = await
|
|
9679
|
+
const settingsPath = join28(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
9680
|
+
const raw = await readFile26(settingsPath, "utf8").catch(() => null);
|
|
9393
9681
|
if (raw === null) return "unchanged";
|
|
9394
9682
|
let parsed;
|
|
9395
9683
|
try {
|
|
@@ -9413,7 +9701,7 @@ async function removeHook(spec, tool, dryRun) {
|
|
|
9413
9701
|
return "removed";
|
|
9414
9702
|
}
|
|
9415
9703
|
async function loadAsset(relPath) {
|
|
9416
|
-
const raw = await
|
|
9704
|
+
const raw = await readFile26(join28(await assetRoot(), relPath), "utf8");
|
|
9417
9705
|
if (relPath === BRAND.hubFileName && raw.startsWith("---")) {
|
|
9418
9706
|
const end = raw.indexOf("\n---", 4);
|
|
9419
9707
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -9421,14 +9709,14 @@ async function loadAsset(relPath) {
|
|
|
9421
9709
|
return raw;
|
|
9422
9710
|
}
|
|
9423
9711
|
async function assetRoot() {
|
|
9424
|
-
const here =
|
|
9425
|
-
const bundled =
|
|
9426
|
-
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;
|
|
9427
9715
|
let dir = here;
|
|
9428
9716
|
for (; ; ) {
|
|
9429
|
-
const candidate =
|
|
9430
|
-
if (await exists2(
|
|
9431
|
-
const parent =
|
|
9717
|
+
const candidate = join28(dir, "packages", "skills");
|
|
9718
|
+
if (await exists2(join28(candidate, BRAND.hubFileName))) return candidate;
|
|
9719
|
+
const parent = dirname11(dir);
|
|
9432
9720
|
if (parent === dir) break;
|
|
9433
9721
|
dir = parent;
|
|
9434
9722
|
}
|
|
@@ -9446,13 +9734,15 @@ async function exists2(path) {
|
|
|
9446
9734
|
return false;
|
|
9447
9735
|
}
|
|
9448
9736
|
}
|
|
9449
|
-
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;
|
|
9450
9738
|
var init_setup = __esm({
|
|
9451
9739
|
"src/commands/setup.ts"() {
|
|
9452
9740
|
"use strict";
|
|
9453
9741
|
init_atomic();
|
|
9454
9742
|
init_brand();
|
|
9455
9743
|
init_deadline();
|
|
9744
|
+
init_guard_shell();
|
|
9745
|
+
init_hook_refresh();
|
|
9456
9746
|
init_errors();
|
|
9457
9747
|
init_setup_brain();
|
|
9458
9748
|
init_setup_signin();
|
|
@@ -9580,7 +9870,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
9580
9870
|
// here would mean a session that reads and answers never hears a rule.
|
|
9581
9871
|
codex: "apply_patch|Bash"
|
|
9582
9872
|
},
|
|
9583
|
-
command:
|
|
9873
|
+
command: guardCommandFor(process.platform),
|
|
9584
9874
|
signature: "memgineering guard"
|
|
9585
9875
|
},
|
|
9586
9876
|
{
|
|
@@ -9606,6 +9896,13 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
9606
9896
|
tools: ["grok"]
|
|
9607
9897
|
}
|
|
9608
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
|
+
};
|
|
9609
9906
|
}
|
|
9610
9907
|
});
|
|
9611
9908
|
|
|
@@ -13372,24 +13669,11 @@ function capTemplates(cards, isTemplate, inConflict = () => false) {
|
|
|
13372
13669
|
}
|
|
13373
13670
|
return { kept, withheld };
|
|
13374
13671
|
}
|
|
13375
|
-
function bodyOf2(content) {
|
|
13376
|
-
const stripped = content.replace(/^/, "");
|
|
13377
|
-
const fence2 = /^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/.exec(stripped);
|
|
13378
|
-
return (fence2 ? stripped.slice(fence2[0].length) : stripped).trim();
|
|
13379
|
-
}
|
|
13380
|
-
function templatesFor(path, current) {
|
|
13381
|
-
const retired = ownEntry(RETIRED_BASE_TEMPLATES, path) ?? [];
|
|
13382
|
-
return current === void 0 ? retired : [current, ...retired];
|
|
13383
|
-
}
|
|
13384
13672
|
function isBaseFilePath(path) {
|
|
13385
13673
|
return path.startsWith("01_BASE/");
|
|
13386
13674
|
}
|
|
13387
13675
|
function bodyStillTemplate(path, content) {
|
|
13388
|
-
|
|
13389
|
-
if (template === void 0) return false;
|
|
13390
|
-
const body = bodyOf2(content);
|
|
13391
|
-
if (body === "") return false;
|
|
13392
|
-
return templatesFor(path, template).some((t) => body === bodyOf2(t));
|
|
13676
|
+
return isTemplateBody(path, content);
|
|
13393
13677
|
}
|
|
13394
13678
|
async function scaffoldingAmong(adapter, notes) {
|
|
13395
13679
|
const basePaths = new Set(BASE_FILES.map((f) => f.path));
|
|
@@ -13404,19 +13688,18 @@ async function scaffoldingAmong(adapter, notes) {
|
|
|
13404
13688
|
async function untouchedHub(adapter, title) {
|
|
13405
13689
|
const actual = await adapter.read(HUB_PATH).then((r) => r.content).catch(() => null);
|
|
13406
13690
|
if (actual === null) return false;
|
|
13407
|
-
return actual
|
|
13691
|
+
return asSaved2(actual) === asSaved2(files(title)[HUB_PATH] ?? "");
|
|
13692
|
+
}
|
|
13693
|
+
function asSaved2(text) {
|
|
13694
|
+
const BOM3 = String.fromCharCode(65279);
|
|
13695
|
+
return (text.startsWith(BOM3) ? text.slice(BOM3.length) : text).replace(/\r\n/g, "\n").trimEnd();
|
|
13408
13696
|
}
|
|
13409
13697
|
async function untouchedBase(adapter, paths) {
|
|
13410
|
-
const templates = files("brain");
|
|
13411
13698
|
const out = /* @__PURE__ */ new Set();
|
|
13412
13699
|
await Promise.all(
|
|
13413
13700
|
paths.map(async (path) => {
|
|
13414
|
-
const template = ownEntry(templates, path);
|
|
13415
|
-
if (template === void 0) return;
|
|
13416
13701
|
const actual = await adapter.read(path).then((r) => r.content).catch(() => null);
|
|
13417
|
-
if (actual
|
|
13418
|
-
const trimmed = actual.trimEnd();
|
|
13419
|
-
if (templatesFor(path, template).some((t) => trimmed === t.trimEnd())) out.add(path);
|
|
13702
|
+
if (actual !== null && isExactUntouchedBase(path, actual)) out.add(path);
|
|
13420
13703
|
})
|
|
13421
13704
|
);
|
|
13422
13705
|
return out;
|
|
@@ -14067,6 +14350,69 @@ init_errors();
|
|
|
14067
14350
|
init_index_build();
|
|
14068
14351
|
init_cloud_brain();
|
|
14069
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
|
|
14070
14416
|
init_ui();
|
|
14071
14417
|
function rememberCommand() {
|
|
14072
14418
|
const said = { scope: false, noScope: false };
|
|
@@ -14140,6 +14486,7 @@ function rememberCommand() {
|
|
|
14140
14486
|
const kindLanded = kind === void 0 || written.kind === kind;
|
|
14141
14487
|
const boundaryLanded = neverStore.length === 0 || Array.isArray(written.never_store) && neverStore.every((term) => written.never_store?.includes(term) === true);
|
|
14142
14488
|
const overrideLanded = opts.overrideBoundary === void 0 || "boundary_override" in written;
|
|
14489
|
+
if (binding) await forgetEmptyChecks();
|
|
14143
14490
|
printDual({
|
|
14144
14491
|
json: {
|
|
14145
14492
|
id: written.id,
|
|
@@ -14774,6 +15121,7 @@ async function reviseHosted(where, ref, opts, added) {
|
|
|
14774
15121
|
}
|
|
14775
15122
|
}
|
|
14776
15123
|
}
|
|
15124
|
+
if (opts.rule === true) await forgetEmptyChecks();
|
|
14777
15125
|
printDual({
|
|
14778
15126
|
json: {
|
|
14779
15127
|
brain: where.name,
|
|
@@ -16517,7 +16865,7 @@ async function deleteCloudBrain(args) {
|
|
|
16517
16865
|
|
|
16518
16866
|
// src/commands/curation.ts
|
|
16519
16867
|
init_src();
|
|
16520
|
-
import { stat as
|
|
16868
|
+
import { stat as stat4 } from "fs/promises";
|
|
16521
16869
|
import { resolve as resolve8 } from "path";
|
|
16522
16870
|
import { Command as Command14 } from "commander";
|
|
16523
16871
|
|
|
@@ -16833,7 +17181,7 @@ async function mustMatchAFile(root, adapter, path) {
|
|
|
16833
17181
|
const wanted = path.normalize("NFC").replace(/^\/+/, "");
|
|
16834
17182
|
const absolute = resolve8(root, wanted);
|
|
16835
17183
|
if (isInside(absolute, root)) {
|
|
16836
|
-
const found = await
|
|
17184
|
+
const found = await stat4(absolute).catch(() => null);
|
|
16837
17185
|
if (found?.isFile()) return wanted;
|
|
16838
17186
|
}
|
|
16839
17187
|
const hits = sameBasename(await adapter.list(""), path);
|
|
@@ -17407,8 +17755,8 @@ function ago(iso, now) {
|
|
|
17407
17755
|
init_brand();
|
|
17408
17756
|
init_config();
|
|
17409
17757
|
import { createHash as createHash7 } from "crypto";
|
|
17410
|
-
import { mkdir as
|
|
17411
|
-
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";
|
|
17412
17760
|
import { Command as Command17 } from "commander";
|
|
17413
17761
|
|
|
17414
17762
|
// src/lib/rules.ts
|
|
@@ -17479,6 +17827,7 @@ function renderRuleGroups(rules, project) {
|
|
|
17479
17827
|
init_src();
|
|
17480
17828
|
init_deadline();
|
|
17481
17829
|
init_cloud_brain();
|
|
17830
|
+
init_vault();
|
|
17482
17831
|
function guardCommand() {
|
|
17483
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) => {
|
|
17484
17833
|
try {
|
|
@@ -17543,22 +17892,35 @@ async function runStopGuard(payload, contextDirFlag) {
|
|
|
17543
17892
|
});
|
|
17544
17893
|
}
|
|
17545
17894
|
async function claimRules(sessionId, contextDir) {
|
|
17895
|
+
const asOf = Date.now();
|
|
17546
17896
|
const where = await resolveTarget({}).catch(() => null);
|
|
17547
17897
|
if (where?.kind === "cloud") {
|
|
17548
|
-
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
|
+
}
|
|
17549
17902
|
const hosted = await hostedRuleLines(where, contextDir).catch(() => null);
|
|
17550
17903
|
if (hosted !== null) {
|
|
17551
|
-
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
|
+
}
|
|
17552
17908
|
await releaseSession(sessionId, where.brainId);
|
|
17909
|
+
await markEmpty(sessionId, asOf);
|
|
17553
17910
|
return null;
|
|
17554
17911
|
}
|
|
17555
17912
|
await releaseSession(sessionId, where.brainId);
|
|
17556
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;
|
|
17557
17918
|
const collected = await collectRules(void 0, contextDir).catch(() => null);
|
|
17558
|
-
if (collected === null)
|
|
17919
|
+
if (collected === null || collected.rules.length === 0) {
|
|
17920
|
+
await releaseSession(sessionId, root);
|
|
17921
|
+
return null;
|
|
17922
|
+
}
|
|
17559
17923
|
const { brainRoot, rules, total } = collected;
|
|
17560
|
-
if (rules.length === 0) return null;
|
|
17561
|
-
if (!await claimSession(sessionId, brainRoot)) return null;
|
|
17562
17924
|
await recordEvent(brainRoot, {
|
|
17563
17925
|
verb: "guard",
|
|
17564
17926
|
contextDir,
|
|
@@ -17614,7 +17976,7 @@ async function readPayload() {
|
|
|
17614
17976
|
return null;
|
|
17615
17977
|
}
|
|
17616
17978
|
}
|
|
17617
|
-
var guardDir = () =>
|
|
17979
|
+
var guardDir = () => join19(brandHome(), "guard");
|
|
17618
17980
|
var WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
17619
17981
|
"Write",
|
|
17620
17982
|
"Edit",
|
|
@@ -17635,7 +17997,7 @@ async function misdeliveryNotice(toolName, filePath, sessionId) {
|
|
|
17635
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.';
|
|
17636
17998
|
}
|
|
17637
17999
|
function markerFor(sessionId, brain) {
|
|
17638
|
-
return
|
|
18000
|
+
return join19(
|
|
17639
18001
|
guardDir(),
|
|
17640
18002
|
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
17641
18003
|
);
|
|
@@ -17643,8 +18005,8 @@ function markerFor(sessionId, brain) {
|
|
|
17643
18005
|
async function claimSession(sessionId, brain) {
|
|
17644
18006
|
const marker = markerFor(sessionId, brain);
|
|
17645
18007
|
try {
|
|
17646
|
-
await
|
|
17647
|
-
await
|
|
18008
|
+
await mkdir12(guardDir(), { recursive: true, mode: 448 });
|
|
18009
|
+
await writeFile10(marker, "", { flag: "wx", mode: 384 });
|
|
17648
18010
|
} catch {
|
|
17649
18011
|
return false;
|
|
17650
18012
|
}
|
|
@@ -17652,19 +18014,23 @@ async function claimSession(sessionId, brain) {
|
|
|
17652
18014
|
return true;
|
|
17653
18015
|
}
|
|
17654
18016
|
async function releaseSession(sessionId, brain) {
|
|
17655
|
-
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);
|
|
17656
18021
|
}
|
|
17657
18022
|
var MARKER_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
17658
18023
|
async function pruneOldMarkers() {
|
|
17659
18024
|
try {
|
|
17660
18025
|
const dir = guardDir();
|
|
17661
18026
|
const now = Date.now();
|
|
17662
|
-
for (const name of await
|
|
17663
|
-
const path =
|
|
18027
|
+
for (const name of await readdir7(dir)) {
|
|
18028
|
+
const path = join19(dir, name);
|
|
17664
18029
|
if (!isAbsolute3(path)) continue;
|
|
17665
|
-
const info = await
|
|
17666
|
-
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);
|
|
17667
18032
|
}
|
|
18033
|
+
await pruneFast(now);
|
|
17668
18034
|
} catch {
|
|
17669
18035
|
}
|
|
17670
18036
|
}
|
|
@@ -18561,7 +18927,7 @@ function onboardCommand() {
|
|
|
18561
18927
|
...survey.missing.length === 0 ? {} : { missing: survey.missing, missing_hint: MISSING_HINT },
|
|
18562
18928
|
// Spelled out in the machine-readable form too, because an agent
|
|
18563
18929
|
// parsing this is the one that has to do the asking.
|
|
18564
|
-
how_to_write: 'Write the body with your own file tools, then `memgineering revise <path> --action reinforce --claim "<one line>" --summary "<what recall should show>"`. This is the one place editing a brain file directly is right, and only while
|
|
18930
|
+
how_to_write: 'Write the body with your own file tools, then `memgineering revise <path> --action reinforce --claim "<one line>" --summary "<what recall should show>"`. This is the one place editing a brain file directly is right, and only while its body has not been written yet.'
|
|
18565
18931
|
},
|
|
18566
18932
|
human: () => {
|
|
18567
18933
|
if (survey.unfilled.length === 0 && survey.missing.length === 0) {
|
|
@@ -18575,7 +18941,7 @@ function onboardCommand() {
|
|
|
18575
18941
|
);
|
|
18576
18942
|
return;
|
|
18577
18943
|
}
|
|
18578
|
-
printHuman(`${survey.heading} (${survey.unfilled.length} of 5 still
|
|
18944
|
+
printHuman(`${survey.heading} (${survey.unfilled.length} of 5 still to write)
|
|
18579
18945
|
`);
|
|
18580
18946
|
if (survey.unfilled.length > 0) {
|
|
18581
18947
|
printHuman(
|
|
@@ -18616,21 +18982,23 @@ function onboardCommand() {
|
|
|
18616
18982
|
var MISSING_HINT = "A note that does not exist cannot be revised into \u2014 `revise` answers `ref_not_found`. `memgineering create <name>` writes the base notes into a brain that holds nothing but its own scaffolding. A brain that already holds other notes takes them from a folder: `memgineering init <folder>` then `memgineering push --brain <name>`, which skips every path already up there.";
|
|
18617
18983
|
async function localSurvey(where) {
|
|
18618
18984
|
const adapter = await openBrainOrExplain(where.brain.root);
|
|
18619
|
-
const
|
|
18620
|
-
|
|
18621
|
-
BASE_FILES.map((f) =>
|
|
18985
|
+
const unwritten = /* @__PURE__ */ new Set();
|
|
18986
|
+
await Promise.all(
|
|
18987
|
+
BASE_FILES.map(async (f) => {
|
|
18988
|
+
const content = await adapter.read(f.path).then((r) => r.content).catch(() => null);
|
|
18989
|
+
if (content !== null && isUnwrittenBody(f.path, content)) unwritten.add(f.path);
|
|
18990
|
+
})
|
|
18622
18991
|
);
|
|
18623
18992
|
return {
|
|
18624
18993
|
identity: { brain: where.brain.root },
|
|
18625
18994
|
heading: "## onboard",
|
|
18626
|
-
unfilled: BASE_FILES.filter((f) =>
|
|
18627
|
-
// A file that is not there reads as written here
|
|
18628
|
-
//
|
|
18629
|
-
//
|
|
18630
|
-
//
|
|
18631
|
-
//
|
|
18632
|
-
|
|
18633
|
-
filled: BASE_FILES.filter((f) => !untouched.has(f.path)).map((f) => f.path),
|
|
18995
|
+
unfilled: BASE_FILES.filter((f) => unwritten.has(f.path)),
|
|
18996
|
+
// A file that is not there reads as written here. That is a choice kept
|
|
18997
|
+
// from before, not something this side cannot tell: `init` writes all
|
|
18998
|
+
// five, so the state does not arise on a folder brain, and asking about a
|
|
18999
|
+
// file that is gone would send an agent to revise a ref that does not
|
|
19000
|
+
// exist. The hosted half CAN arrive without them, and says so (`missing`).
|
|
19001
|
+
filled: BASE_FILES.filter((f) => !unwritten.has(f.path)).map((f) => f.path),
|
|
18634
19002
|
missing: []
|
|
18635
19003
|
};
|
|
18636
19004
|
}
|
|
@@ -18639,7 +19007,7 @@ async function hostedSurvey(where) {
|
|
|
18639
19007
|
const state = (file) => {
|
|
18640
19008
|
const content = contents.get(file.path);
|
|
18641
19009
|
if (content === void 0) return "missing";
|
|
18642
|
-
return
|
|
19010
|
+
return isUnwrittenBody(file.path, content) ? "unfilled" : "filled";
|
|
18643
19011
|
};
|
|
18644
19012
|
return {
|
|
18645
19013
|
identity: { brain: where.name, brain_id: where.brainId },
|
|
@@ -18680,8 +19048,8 @@ init_api_client();
|
|
|
18680
19048
|
init_config();
|
|
18681
19049
|
init_cloud_brain();
|
|
18682
19050
|
init_ui();
|
|
18683
|
-
import { mkdir as
|
|
18684
|
-
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";
|
|
18685
19053
|
import { Command as Command24 } from "commander";
|
|
18686
19054
|
function pullCommand() {
|
|
18687
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) => {
|
|
@@ -18749,8 +19117,8 @@ function pullCommand() {
|
|
|
18749
19117
|
already.push(note.path);
|
|
18750
19118
|
continue;
|
|
18751
19119
|
}
|
|
18752
|
-
await
|
|
18753
|
-
await
|
|
19120
|
+
await mkdir13(dirname6(file), { recursive: true });
|
|
19121
|
+
await writeFile11(file, note.content ?? "", "utf8");
|
|
18754
19122
|
written.push(note.path);
|
|
18755
19123
|
if (note.excluded === true) excluded.push(note.path);
|
|
18756
19124
|
}
|
|
@@ -18864,7 +19232,7 @@ function safeJoin(root, relative3) {
|
|
|
18864
19232
|
"Nothing was written for it. This should not happen \u2014 please report it."
|
|
18865
19233
|
);
|
|
18866
19234
|
}
|
|
18867
|
-
const full = resolve11(
|
|
19235
|
+
const full = resolve11(join20(root, relative3));
|
|
18868
19236
|
if (full !== root && !full.startsWith(root + sep2)) {
|
|
18869
19237
|
throw memgError(
|
|
18870
19238
|
"server_refused",
|
|
@@ -19786,9 +20154,9 @@ init_errors();
|
|
|
19786
20154
|
init_credentials();
|
|
19787
20155
|
|
|
19788
20156
|
// src/lib/untrusted-fence.ts
|
|
19789
|
-
import { randomUUID as
|
|
20157
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
19790
20158
|
function makeUntrustedSentinel() {
|
|
19791
|
-
return `KORDIS-UNTRUSTED-${
|
|
20159
|
+
return `KORDIS-UNTRUSTED-${randomUUID3()}`;
|
|
19792
20160
|
}
|
|
19793
20161
|
function fenceUntrusted(sentinel, text) {
|
|
19794
20162
|
const neutralized = text.replace(/KORDIS-UNTRUSTED-/g, "KORDIS-UNTRUSTED\u200B-");
|
|
@@ -20284,7 +20652,7 @@ init_errors();
|
|
|
20284
20652
|
// src/lib/harness-context.ts
|
|
20285
20653
|
init_brand();
|
|
20286
20654
|
import { access, readFile as readFile21 } from "fs/promises";
|
|
20287
|
-
import { join as
|
|
20655
|
+
import { join as join21 } from "path";
|
|
20288
20656
|
var PER_FILE_CAP = 16 * 1024;
|
|
20289
20657
|
var TOTAL_CAP = 48 * 1024;
|
|
20290
20658
|
var TRUNCATION_RESERVE = 64;
|
|
@@ -20329,13 +20697,13 @@ function extractHeadings(content) {
|
|
|
20329
20697
|
}
|
|
20330
20698
|
async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
20331
20699
|
const candidates = [
|
|
20332
|
-
{ path:
|
|
20333
|
-
{ path:
|
|
20334
|
-
{ path:
|
|
20335
|
-
{ path:
|
|
20336
|
-
{ path:
|
|
20337
|
-
{ path:
|
|
20338
|
-
{ 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" }
|
|
20339
20707
|
];
|
|
20340
20708
|
const files2 = [];
|
|
20341
20709
|
let totalBytes = 0;
|
|
@@ -20740,8 +21108,8 @@ function parseLimit2(raw) {
|
|
|
20740
21108
|
import { Command as Command30 } from "commander";
|
|
20741
21109
|
import prompts3 from "prompts";
|
|
20742
21110
|
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
20743
|
-
import { mkdir as
|
|
20744
|
-
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";
|
|
20745
21113
|
|
|
20746
21114
|
// ../../packages/translators/src/skill/identity.ts
|
|
20747
21115
|
function buildSkillFile(input, outputPath) {
|
|
@@ -21322,17 +21690,17 @@ init_ui();
|
|
|
21322
21690
|
|
|
21323
21691
|
// src/lib/lockfile.ts
|
|
21324
21692
|
init_brand();
|
|
21325
|
-
import { copyFile, mkdir as
|
|
21693
|
+
import { copyFile, mkdir as mkdir14, readFile as readFile22, writeFile as writeFile12, constants as fsConstants } from "fs/promises";
|
|
21326
21694
|
import { homedir as homedir4 } from "os";
|
|
21327
|
-
import { join as
|
|
21695
|
+
import { join as join22 } from "path";
|
|
21328
21696
|
function effectiveRange(entry) {
|
|
21329
21697
|
return entry.requestedRange ?? `^${entry.version}`;
|
|
21330
21698
|
}
|
|
21331
21699
|
function lockfilePath() {
|
|
21332
|
-
return
|
|
21700
|
+
return join22(brandHome(), "installed.json");
|
|
21333
21701
|
}
|
|
21334
21702
|
function kordisLockfilePath() {
|
|
21335
|
-
return
|
|
21703
|
+
return join22(process.env["KORDIS_HOME"] ?? join22(homedir4(), ".kordis"), "installed.json");
|
|
21336
21704
|
}
|
|
21337
21705
|
function lockfileDisplayPath() {
|
|
21338
21706
|
const path = lockfilePath();
|
|
@@ -21343,7 +21711,7 @@ async function migrateFromKordis() {
|
|
|
21343
21711
|
const source = kordisLockfilePath();
|
|
21344
21712
|
const destination = lockfilePath();
|
|
21345
21713
|
try {
|
|
21346
|
-
await
|
|
21714
|
+
await mkdir14(brandHome(), { recursive: true });
|
|
21347
21715
|
await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
|
|
21348
21716
|
} catch {
|
|
21349
21717
|
return;
|
|
@@ -21371,15 +21739,15 @@ async function readLockfile() {
|
|
|
21371
21739
|
}
|
|
21372
21740
|
}
|
|
21373
21741
|
async function writeLockfile(lf) {
|
|
21374
|
-
await
|
|
21375
|
-
await
|
|
21742
|
+
await mkdir14(brandHome(), { recursive: true });
|
|
21743
|
+
await writeFile12(lockfilePath(), JSON.stringify(lf, null, 2) + "\n", {
|
|
21376
21744
|
encoding: "utf8",
|
|
21377
21745
|
mode: 384
|
|
21378
21746
|
});
|
|
21379
21747
|
}
|
|
21380
21748
|
async function recordInstalled(fullSlug, entry) {
|
|
21381
21749
|
const home = brandHome();
|
|
21382
|
-
await withFileLock(
|
|
21750
|
+
await withFileLock(join22(home, "installed.lock"), home, async () => {
|
|
21383
21751
|
const lf = await readLockfile();
|
|
21384
21752
|
lf.packages[fullSlug] = entry;
|
|
21385
21753
|
await writeLockfile(lf);
|
|
@@ -21647,7 +22015,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
21647
22015
|
);
|
|
21648
22016
|
for (const f of translated.files) {
|
|
21649
22017
|
const abs = safeInstallPath(installRoot, f.path);
|
|
21650
|
-
await
|
|
22018
|
+
await mkdir15(dirname7(abs), { recursive: true });
|
|
21651
22019
|
await applyEmittedFile(abs, f, fullSlug, notes);
|
|
21652
22020
|
written.push(abs);
|
|
21653
22021
|
}
|
|
@@ -21803,7 +22171,7 @@ var TOOL_MARKERS = [
|
|
|
21803
22171
|
async function detectTool(root) {
|
|
21804
22172
|
for (const [path, tool] of TOOL_MARKERS) {
|
|
21805
22173
|
try {
|
|
21806
|
-
await
|
|
22174
|
+
await stat6(join23(root, path));
|
|
21807
22175
|
return tool;
|
|
21808
22176
|
} catch {
|
|
21809
22177
|
}
|
|
@@ -21819,7 +22187,7 @@ function safeInstallPath(installRoot, relPath) {
|
|
|
21819
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."
|
|
21820
22188
|
);
|
|
21821
22189
|
}
|
|
21822
|
-
const abs =
|
|
22190
|
+
const abs = join23(installRoot, relPath);
|
|
21823
22191
|
const resolvedRoot = resolve12(installRoot);
|
|
21824
22192
|
const resolvedAbs = resolve12(abs);
|
|
21825
22193
|
if (resolvedAbs !== resolvedRoot && !resolvedAbs.startsWith(resolvedRoot + sep3)) {
|
|
@@ -21873,9 +22241,9 @@ function safeInstallPath(installRoot, relPath) {
|
|
|
21873
22241
|
}
|
|
21874
22242
|
async function writeEmittedFile(abs, content, encoding) {
|
|
21875
22243
|
if (encoding === "base64") {
|
|
21876
|
-
await
|
|
22244
|
+
await writeFile13(abs, Buffer.from(content, "base64"));
|
|
21877
22245
|
} else {
|
|
21878
|
-
await
|
|
22246
|
+
await writeFile13(abs, content, "utf8");
|
|
21879
22247
|
}
|
|
21880
22248
|
}
|
|
21881
22249
|
async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
@@ -21896,10 +22264,10 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
21896
22264
|
`merged into ${f.path} \u2014 comments/formatting in the existing file are not preserved`
|
|
21897
22265
|
);
|
|
21898
22266
|
}
|
|
21899
|
-
await
|
|
22267
|
+
await writeFile13(abs, merged, "utf8");
|
|
21900
22268
|
return;
|
|
21901
22269
|
}
|
|
21902
|
-
await
|
|
22270
|
+
await writeFile13(abs, mergeFencedFile(existingRaw, fullSlug, f.content), "utf8");
|
|
21903
22271
|
}
|
|
21904
22272
|
|
|
21905
22273
|
// src/commands/registry/update.ts
|
|
@@ -22950,15 +23318,17 @@ function isScopeSpread(value) {
|
|
|
22950
23318
|
}
|
|
22951
23319
|
|
|
22952
23320
|
// src/commands/resurface.ts
|
|
23321
|
+
init_setup();
|
|
23322
|
+
init_ui();
|
|
22953
23323
|
init_deadline();
|
|
22954
23324
|
init_src();
|
|
22955
|
-
import { Command as
|
|
23325
|
+
import { Command as Command39 } from "commander";
|
|
22956
23326
|
init_index_build();
|
|
22957
23327
|
init_cloud_brain();
|
|
22958
23328
|
init_vault();
|
|
22959
23329
|
init_ui();
|
|
22960
23330
|
function resurfaceCommand() {
|
|
22961
|
-
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(
|
|
22962
23332
|
"--brain <name>",
|
|
22963
23333
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
22964
23334
|
).option(
|
|
@@ -22969,6 +23339,11 @@ function resurfaceCommand() {
|
|
|
22969
23339
|
async (opts) => {
|
|
22970
23340
|
const bounded = (work) => opts.deadline !== void 0 && Number.isFinite(opts.deadline) ? withDeadline(opts.deadline, work) : work();
|
|
22971
23341
|
await bounded(async () => {
|
|
23342
|
+
await refreshOwnHooksAndTell(
|
|
23343
|
+
(text) => (isJsonMode() ? process.stderr : process.stdout).write(`${text}
|
|
23344
|
+
|
|
23345
|
+
`)
|
|
23346
|
+
);
|
|
22972
23347
|
const contextDir = opts.contextDir ?? process.cwd();
|
|
22973
23348
|
const where = await resolveTarget({
|
|
22974
23349
|
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
@@ -23180,11 +23555,11 @@ rank by project yet, so that is not one of the signals above.`
|
|
|
23180
23555
|
}
|
|
23181
23556
|
|
|
23182
23557
|
// src/commands/rules.ts
|
|
23183
|
-
import { Command as
|
|
23558
|
+
import { Command as Command40 } from "commander";
|
|
23184
23559
|
init_cloud_brain();
|
|
23185
23560
|
init_ui();
|
|
23186
23561
|
function rulesCommand() {
|
|
23187
|
-
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(
|
|
23188
23563
|
"--brain <name>",
|
|
23189
23564
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
23190
23565
|
).action(async (opts) => {
|
|
@@ -23351,16 +23726,16 @@ init_next_step();
|
|
|
23351
23726
|
init_provenance();
|
|
23352
23727
|
import { spawn as spawn4 } from "child_process";
|
|
23353
23728
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23354
|
-
import { mkdir as
|
|
23355
|
-
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";
|
|
23356
23731
|
import { Command as Command41 } from "commander";
|
|
23357
23732
|
|
|
23358
23733
|
// src/lib/update-check.ts
|
|
23359
23734
|
init_atomic();
|
|
23360
23735
|
init_brand();
|
|
23361
23736
|
import { execFile as execFile2 } from "child_process";
|
|
23362
|
-
import { mkdir as
|
|
23363
|
-
import { join as
|
|
23737
|
+
import { mkdir as mkdir18, readFile as readFile27, writeFile as writeFile16 } from "fs/promises";
|
|
23738
|
+
import { join as join29 } from "path";
|
|
23364
23739
|
import { promisify as promisify2 } from "util";
|
|
23365
23740
|
import semver4 from "semver";
|
|
23366
23741
|
import { z as z33 } from "zod";
|
|
@@ -23441,11 +23816,11 @@ var UpdateStateSchema = z33.object({
|
|
|
23441
23816
|
message: z33.string().nullable()
|
|
23442
23817
|
});
|
|
23443
23818
|
function statePath() {
|
|
23444
|
-
return
|
|
23819
|
+
return join29(brandHome(), "update-state.json");
|
|
23445
23820
|
}
|
|
23446
23821
|
async function readUpdateState() {
|
|
23447
23822
|
try {
|
|
23448
|
-
const parsed = UpdateStateSchema.safeParse(JSON.parse(await
|
|
23823
|
+
const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile27(statePath(), "utf8")));
|
|
23449
23824
|
return parsed.success ? parsed.data : null;
|
|
23450
23825
|
} catch {
|
|
23451
23826
|
return null;
|
|
@@ -23453,7 +23828,7 @@ async function readUpdateState() {
|
|
|
23453
23828
|
}
|
|
23454
23829
|
async function writeUpdateState(state) {
|
|
23455
23830
|
try {
|
|
23456
|
-
await
|
|
23831
|
+
await mkdir18(brandHome(), { recursive: true });
|
|
23457
23832
|
await writeThenRename(statePath(), `${JSON.stringify(state, null, 2)}
|
|
23458
23833
|
`);
|
|
23459
23834
|
} catch {
|
|
@@ -23565,7 +23940,7 @@ async function runUpdate(opts, inherited) {
|
|
|
23565
23940
|
}
|
|
23566
23941
|
const held = await holdUpdateSlot(inherited);
|
|
23567
23942
|
if (held === null && await blockedBy() === "stuck") {
|
|
23568
|
-
const lock =
|
|
23943
|
+
const lock = join30(brandHome(), RECLAIM_LOCK);
|
|
23569
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.`;
|
|
23570
23945
|
printDual({
|
|
23571
23946
|
json: { ...status, provenance: provenance.kind, updated: false, reason: "update_claim_stuck", hint },
|
|
@@ -23680,14 +24055,14 @@ function newClaimToken() {
|
|
|
23680
24055
|
}
|
|
23681
24056
|
async function claimOwner() {
|
|
23682
24057
|
try {
|
|
23683
|
-
return (await
|
|
24058
|
+
return (await readFile28(join30(brandHome(), "update-running"), "utf8")).trim() || null;
|
|
23684
24059
|
} catch {
|
|
23685
24060
|
return null;
|
|
23686
24061
|
}
|
|
23687
24062
|
}
|
|
23688
24063
|
async function releaseUpdateSlot(token) {
|
|
23689
24064
|
if (await claimOwner() !== token) return;
|
|
23690
|
-
await
|
|
24065
|
+
await unlink6(join30(brandHome(), "update-running")).catch(() => void 0);
|
|
23691
24066
|
}
|
|
23692
24067
|
async function holdUpdateSlot(inherited) {
|
|
23693
24068
|
if (inherited !== void 0 && await claimOwner() === inherited) return inherited;
|
|
@@ -23699,9 +24074,9 @@ async function holdUpdateSlot(inherited) {
|
|
|
23699
24074
|
var HOOK_VERBS = /* @__PURE__ */ new Set(["guard", "resurface"]);
|
|
23700
24075
|
var UPDATE_CLAIM_MS = 15 * 6e4;
|
|
23701
24076
|
async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
|
|
23702
|
-
const path =
|
|
24077
|
+
const path = join30(brandHome(), "update-running");
|
|
23703
24078
|
try {
|
|
23704
|
-
await
|
|
24079
|
+
await mkdir19(brandHome(), { recursive: true });
|
|
23705
24080
|
const handle = await open(path, "wx");
|
|
23706
24081
|
await handle.writeFile(`${token}
|
|
23707
24082
|
`);
|
|
@@ -23711,12 +24086,12 @@ async function claimUpdateSlot(token = newClaimToken(), hooks = {}) {
|
|
|
23711
24086
|
if (err?.code !== "EEXIST") return false;
|
|
23712
24087
|
}
|
|
23713
24088
|
try {
|
|
23714
|
-
if (Date.now() - (await
|
|
24089
|
+
if (Date.now() - (await stat9(path)).mtimeMs >= UPDATE_CLAIM_MS) {
|
|
23715
24090
|
await hooks.afterObserve?.();
|
|
23716
24091
|
await withReclaimLock(async () => {
|
|
23717
|
-
if (Date.now() - (await
|
|
24092
|
+
if (Date.now() - (await stat9(path)).mtimeMs < UPDATE_CLAIM_MS) return;
|
|
23718
24093
|
await hooks.afterRecheck?.();
|
|
23719
|
-
await
|
|
24094
|
+
await unlink6(path);
|
|
23720
24095
|
});
|
|
23721
24096
|
}
|
|
23722
24097
|
} catch {
|
|
@@ -23727,13 +24102,13 @@ var RECLAIM_LOCK = "update-running.reclaim";
|
|
|
23727
24102
|
var RECLAIM_LOCK_MAX_MS = 10 * 6e4;
|
|
23728
24103
|
async function withReclaimLock(fn) {
|
|
23729
24104
|
const home = brandHome();
|
|
23730
|
-
const lock =
|
|
23731
|
-
const staging =
|
|
23732
|
-
await
|
|
23733
|
-
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()}
|
|
23734
24109
|
`);
|
|
23735
24110
|
try {
|
|
23736
|
-
await
|
|
24111
|
+
await rename5(staging, lock);
|
|
23737
24112
|
} catch {
|
|
23738
24113
|
await rm8(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
23739
24114
|
await breakAbandonedLock(lock);
|
|
@@ -23743,18 +24118,18 @@ async function withReclaimLock(fn) {
|
|
|
23743
24118
|
return await fn();
|
|
23744
24119
|
} finally {
|
|
23745
24120
|
const aside = `${lock}.done.${randomBytes2(4).toString("hex")}`;
|
|
23746
|
-
await
|
|
24121
|
+
await rename5(lock, aside).then(() => rm8(aside, { recursive: true, force: true })).catch(() => void 0);
|
|
23747
24122
|
}
|
|
23748
24123
|
}
|
|
23749
24124
|
async function breakAbandonedLock(lock) {
|
|
23750
24125
|
try {
|
|
23751
|
-
const seen = (await
|
|
24126
|
+
const seen = (await readFile28(join30(lock, "owner"), "utf8")).trim();
|
|
23752
24127
|
if (!lockAbandoned(seen)) return;
|
|
23753
24128
|
const aside = `${lock}.dead.${randomBytes2(4).toString("hex")}`;
|
|
23754
|
-
await
|
|
23755
|
-
const moved = (await
|
|
24129
|
+
await rename5(lock, aside);
|
|
24130
|
+
const moved = (await readFile28(join30(aside, "owner"), "utf8").catch(() => "")).trim();
|
|
23756
24131
|
if (moved !== seen) {
|
|
23757
|
-
await
|
|
24132
|
+
await rename5(aside, lock).catch(() => void 0);
|
|
23758
24133
|
return;
|
|
23759
24134
|
}
|
|
23760
24135
|
await rm8(aside, { recursive: true, force: true });
|
|
@@ -23776,9 +24151,9 @@ function lockAbandoned(owner, now = Date.now()) {
|
|
|
23776
24151
|
}
|
|
23777
24152
|
async function blockedBy() {
|
|
23778
24153
|
try {
|
|
23779
|
-
const old = Date.now() - (await
|
|
24154
|
+
const old = Date.now() - (await stat9(join30(brandHome(), "update-running"))).mtimeMs >= UPDATE_CLAIM_MS;
|
|
23780
24155
|
if (old) {
|
|
23781
|
-
await
|
|
24156
|
+
await stat9(join30(brandHome(), RECLAIM_LOCK));
|
|
23782
24157
|
return "stuck";
|
|
23783
24158
|
}
|
|
23784
24159
|
} catch {
|
|
@@ -23810,8 +24185,8 @@ function reportBelowMinimum(below) {
|
|
|
23810
24185
|
|
|
23811
24186
|
// src/commands/undo.ts
|
|
23812
24187
|
init_src();
|
|
23813
|
-
import { readFile as
|
|
23814
|
-
import { join as
|
|
24188
|
+
import { readFile as readFile29, rm as rm9 } from "fs/promises";
|
|
24189
|
+
import { join as join31 } from "path";
|
|
23815
24190
|
import { Command as Command42 } from "commander";
|
|
23816
24191
|
init_atomic();
|
|
23817
24192
|
init_errors();
|
|
@@ -23908,8 +24283,8 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
23908
24283
|
);
|
|
23909
24284
|
}
|
|
23910
24285
|
const record = await withWriteLock(brain.root, async () => {
|
|
23911
|
-
const absolute =
|
|
23912
|
-
const current = await
|
|
24286
|
+
const absolute = join31(brain.root, target.target);
|
|
24287
|
+
const current = await readFile29(absolute, "utf8").catch(() => null);
|
|
23913
24288
|
const actualHash = current === null ? null : hashContent(current);
|
|
23914
24289
|
if (actualHash !== target.hash_after) {
|
|
23915
24290
|
throw memgError(
|
|
@@ -23971,7 +24346,7 @@ function minutesSince(iso) {
|
|
|
23971
24346
|
return Math.max(0, Math.floor((Date.now() - at) / 6e4));
|
|
23972
24347
|
}
|
|
23973
24348
|
async function reverse(brain, op, current) {
|
|
23974
|
-
const absolute =
|
|
24349
|
+
const absolute = join31(brain.root, op.target);
|
|
23975
24350
|
if (op.verb === "remember") {
|
|
23976
24351
|
if (current === null) {
|
|
23977
24352
|
throw memgError(
|
|
@@ -24028,7 +24403,7 @@ async function conflictHint(vaultRoot, op) {
|
|
|
24028
24403
|
Read what it says now, then decide what should stay.`;
|
|
24029
24404
|
const snapshot = await readSnapshot(vaultRoot, op.op_id);
|
|
24030
24405
|
return snapshot === null ? base2 : `${base2}
|
|
24031
|
-
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`)}`;
|
|
24032
24407
|
}
|
|
24033
24408
|
|
|
24034
24409
|
// src/commands/brains.ts
|
|
@@ -24713,6 +25088,10 @@ async function run3(argv) {
|
|
|
24713
25088
|
});
|
|
24714
25089
|
await program.parseAsync(cleaned);
|
|
24715
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
|
+
}
|
|
24716
25095
|
}
|
|
24717
25096
|
async function handleError(err) {
|
|
24718
25097
|
const failure = adaptRegistryError(err) ?? err;
|