memgineering 0.23.1 → 0.25.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 +138 -0
- package/assets/MEMGINEERING.md +3 -1
- package/assets/memgineering-recall/SKILL.md +19 -3
- package/assets/memgineering-rules/SKILL.md +15 -4
- package/assets/memgineering-setup/SKILL.md +40 -13
- package/assets/memgineering-writing/SKILL.md +19 -7
- package/dist/index.js +1993 -707
- package/package.json +1 -1
- package/scripts/postinstall.mjs +21 -24
package/dist/index.js
CHANGED
|
@@ -367,6 +367,26 @@ var init_schema = __esm({
|
|
|
367
367
|
scope: z.string().min(1).nullable(),
|
|
368
368
|
/** Id of the claim that is current for this memory, when one is declared. */
|
|
369
369
|
current_claim: z.string().min(1).nullable(),
|
|
370
|
+
/**
|
|
371
|
+
* The end date the NOTE declares, for the notes that state no claim.
|
|
372
|
+
*
|
|
373
|
+
* `valid_until` lived only on `Claim`, and the mapper mints a claim only for
|
|
374
|
+
* a note carrying a `claim:` key — which `remember` has never written. So on
|
|
375
|
+
* a real brain most notes could not carry an end date that anything read:
|
|
376
|
+
* sampled six binding rules on a production brain and four had no claim at
|
|
377
|
+
* all, the two that did having been through `revise`. A date in the
|
|
378
|
+
* frontmatter of the other four was simply ignored.
|
|
379
|
+
*
|
|
380
|
+
* Optional rather than nullable, like `kind` and `binding` above and for the
|
|
381
|
+
* same reason: a note written before this existed must map to exactly the
|
|
382
|
+
* `Memory` it always did, or the regeneration test's byte comparison rewrites
|
|
383
|
+
* every cache in existence on upgrade.
|
|
384
|
+
*
|
|
385
|
+
* The claim's copy WINS where both exist — it is the more specific statement,
|
|
386
|
+
* and a note whose conclusion carries its own dates is saying something about
|
|
387
|
+
* that conclusion rather than about the file.
|
|
388
|
+
*/
|
|
389
|
+
valid_until: z.string().min(1).optional(),
|
|
370
390
|
status: MemoryStatusSchema,
|
|
371
391
|
/**
|
|
372
392
|
* Whether this memory binds what an agent does, rather than informing it.
|
|
@@ -849,8 +869,8 @@ function scopeFromPath(path) {
|
|
|
849
869
|
return parts.length >= 2 ? parts[parts.length - 2] ?? null : null;
|
|
850
870
|
}
|
|
851
871
|
function titleFromPath(path) {
|
|
852
|
-
const
|
|
853
|
-
return
|
|
872
|
+
const base2 = path.split("/").pop() ?? path;
|
|
873
|
+
return base2.replace(/\.[^.]+$/, "") || base2;
|
|
854
874
|
}
|
|
855
875
|
function h1Of(intro) {
|
|
856
876
|
for (const line of intro.split("\n")) {
|
|
@@ -964,8 +984,8 @@ function mapNoteToMemory(input) {
|
|
|
964
984
|
const ns = explicit ? nsRaw : {};
|
|
965
985
|
const id = asString(ns["id"]) ?? path;
|
|
966
986
|
const title = asString(ns["title"]) ?? asString(frontmatter?.["title"]) ?? h1Of(outline.intro) ?? titleFromPath(path);
|
|
967
|
-
const
|
|
968
|
-
const scope =
|
|
987
|
+
const declaredScope2 = asString(ns["scope"]);
|
|
988
|
+
const scope = declaredScope2 ?? scopeFromPath(path);
|
|
969
989
|
const summary = asString(ns["summary"]) ?? outline.description ?? firstParagraph(outline.intro) ?? firstSectionParagraph(outline.sections) ?? null;
|
|
970
990
|
const memory = {
|
|
971
991
|
id,
|
|
@@ -1009,11 +1029,17 @@ function mapNoteToMemory(input) {
|
|
|
1009
1029
|
aliases: asStringArray(ns["aliases"]),
|
|
1010
1030
|
entities: asStringArray(ns["entities"]),
|
|
1011
1031
|
updated_at: asString(ns["updated_at"]) ?? asString(frontmatter?.["updated_at"]),
|
|
1032
|
+
// Carried onto the memory as well as onto the claim below, because a note
|
|
1033
|
+
// that states no claim gets no claim object at all — and `remember` has
|
|
1034
|
+
// never written a `claim:` key, so that is most of a real brain. Written
|
|
1035
|
+
// only when present, so a note without one maps byte-identically to what
|
|
1036
|
+
// it always did.
|
|
1037
|
+
...asString(ns["valid_until"]) === null ? {} : { valid_until: asString(ns["valid_until"]) },
|
|
1012
1038
|
provenance: {
|
|
1013
1039
|
sourcePath: path,
|
|
1014
1040
|
sourceRevision: revision,
|
|
1015
1041
|
mapping: explicit ? "explicit" : "heuristic",
|
|
1016
|
-
scopeSource:
|
|
1042
|
+
scopeSource: declaredScope2 !== null ? "explicit" : scope === null ? "none" : "path"
|
|
1017
1043
|
}
|
|
1018
1044
|
};
|
|
1019
1045
|
const claims = [];
|
|
@@ -1144,7 +1170,7 @@ var init_cache_io = __esm({
|
|
|
1144
1170
|
"../../packages/memory-engine/src/view/cache-io.ts"() {
|
|
1145
1171
|
"use strict";
|
|
1146
1172
|
init_schema();
|
|
1147
|
-
SCHEMA_VERSION =
|
|
1173
|
+
SCHEMA_VERSION = 7;
|
|
1148
1174
|
POLICY_VERSION = 1;
|
|
1149
1175
|
CacheEntrySchema = z2.object({
|
|
1150
1176
|
path: z2.string().min(1),
|
|
@@ -1759,6 +1785,12 @@ function terms(text) {
|
|
|
1759
1785
|
(match) => splitMixedScript(match[0])
|
|
1760
1786
|
);
|
|
1761
1787
|
}
|
|
1788
|
+
function contentTerms(text) {
|
|
1789
|
+
const all = terms(text);
|
|
1790
|
+
if (all.some((t) => HANGUL.test(t))) return all;
|
|
1791
|
+
const content = all.filter((t) => !LATIN_STOPWORDS.has(t));
|
|
1792
|
+
return content.length > 0 ? content : all;
|
|
1793
|
+
}
|
|
1762
1794
|
function indexTerms(text) {
|
|
1763
1795
|
return [...text.normalize("NFC").toLowerCase().matchAll(TOKEN)].flatMap((match) => {
|
|
1764
1796
|
const token = match[0];
|
|
@@ -1831,8 +1863,9 @@ function fieldScore(queryTerms, field, weight, allowLeadingOneSyllable = false)
|
|
|
1831
1863
|
return score;
|
|
1832
1864
|
}
|
|
1833
1865
|
function searchMemories(memories, query) {
|
|
1834
|
-
const q =
|
|
1866
|
+
const q = contentTerms(query);
|
|
1835
1867
|
if (q.length === 0) return [];
|
|
1868
|
+
const scale = terms(query).length;
|
|
1836
1869
|
const scored = [];
|
|
1837
1870
|
for (const memory of memories) {
|
|
1838
1871
|
let score = fieldScore(q, memory.title, WEIGHTS.title, true);
|
|
@@ -1840,13 +1873,13 @@ function searchMemories(memories, query) {
|
|
|
1840
1873
|
for (const entity of memory.entities) score += fieldScore(q, entity, WEIGHTS.entity);
|
|
1841
1874
|
score += fieldScore(q, memory.summary, WEIGHTS.summary);
|
|
1842
1875
|
score += fieldScore(q, memory.scope, WEIGHTS.scope);
|
|
1843
|
-
if (score > 0) scored.push({ memory, score: score /
|
|
1876
|
+
if (score > 0) scored.push({ memory, score: score / scale });
|
|
1844
1877
|
}
|
|
1845
1878
|
return scored.sort(
|
|
1846
1879
|
(a, b) => b.score !== a.score ? b.score - a.score : a.memory.id < b.memory.id ? -1 : 1
|
|
1847
1880
|
);
|
|
1848
1881
|
}
|
|
1849
|
-
var WEIGHTS, TOKEN, COMPONENT_SEPARATOR, HAS_COMPONENT_SEPARATOR, LATIN_HANGUL_BOUNDARY, SUBSTRING_MIN, PARTICLES, HANGUL, ONE_SYLLABLE_TOPIC_TERMS;
|
|
1882
|
+
var WEIGHTS, TOKEN, COMPONENT_SEPARATOR, HAS_COMPONENT_SEPARATOR, LATIN_HANGUL_BOUNDARY, LATIN_STOPWORDS, SUBSTRING_MIN, PARTICLES, HANGUL, ONE_SYLLABLE_TOPIC_TERMS;
|
|
1850
1883
|
var init_search = __esm({
|
|
1851
1884
|
"../../packages/memory-engine/src/recall/search.ts"() {
|
|
1852
1885
|
"use strict";
|
|
@@ -1855,6 +1888,120 @@ var init_search = __esm({
|
|
|
1855
1888
|
COMPONENT_SEPARATOR = /[._/-]+/u;
|
|
1856
1889
|
HAS_COMPONENT_SEPARATOR = /[._/-]/u;
|
|
1857
1890
|
LATIN_HANGUL_BOUNDARY = new RegExp("(?<=\\p{Script=Latin})(?=\\p{Script=Hangul})|(?<=\\p{Script=Hangul})(?=\\p{Script=Latin})", "u");
|
|
1891
|
+
LATIN_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1892
|
+
// pronouns and possessives
|
|
1893
|
+
"i",
|
|
1894
|
+
"me",
|
|
1895
|
+
"my",
|
|
1896
|
+
"mine",
|
|
1897
|
+
"myself",
|
|
1898
|
+
"we",
|
|
1899
|
+
"us",
|
|
1900
|
+
"our",
|
|
1901
|
+
"ours",
|
|
1902
|
+
"ourselves",
|
|
1903
|
+
"you",
|
|
1904
|
+
"your",
|
|
1905
|
+
"yours",
|
|
1906
|
+
"yourself",
|
|
1907
|
+
"yourselves",
|
|
1908
|
+
"he",
|
|
1909
|
+
"him",
|
|
1910
|
+
"his",
|
|
1911
|
+
"himself",
|
|
1912
|
+
"she",
|
|
1913
|
+
"her",
|
|
1914
|
+
"hers",
|
|
1915
|
+
"herself",
|
|
1916
|
+
"it",
|
|
1917
|
+
"its",
|
|
1918
|
+
"itself",
|
|
1919
|
+
"they",
|
|
1920
|
+
"them",
|
|
1921
|
+
"their",
|
|
1922
|
+
"theirs",
|
|
1923
|
+
"themselves",
|
|
1924
|
+
// determiners
|
|
1925
|
+
"a",
|
|
1926
|
+
"an",
|
|
1927
|
+
"the",
|
|
1928
|
+
"this",
|
|
1929
|
+
"that",
|
|
1930
|
+
"these",
|
|
1931
|
+
"those",
|
|
1932
|
+
"some",
|
|
1933
|
+
"any",
|
|
1934
|
+
"each",
|
|
1935
|
+
"every",
|
|
1936
|
+
// auxiliaries and modals
|
|
1937
|
+
"am",
|
|
1938
|
+
"is",
|
|
1939
|
+
"are",
|
|
1940
|
+
"was",
|
|
1941
|
+
"were",
|
|
1942
|
+
"be",
|
|
1943
|
+
"been",
|
|
1944
|
+
"being",
|
|
1945
|
+
"do",
|
|
1946
|
+
"does",
|
|
1947
|
+
"did",
|
|
1948
|
+
"have",
|
|
1949
|
+
"has",
|
|
1950
|
+
"had",
|
|
1951
|
+
"will",
|
|
1952
|
+
"would",
|
|
1953
|
+
"shall",
|
|
1954
|
+
"should",
|
|
1955
|
+
"can",
|
|
1956
|
+
"could",
|
|
1957
|
+
"may",
|
|
1958
|
+
"might",
|
|
1959
|
+
"must",
|
|
1960
|
+
// question words
|
|
1961
|
+
"what",
|
|
1962
|
+
"which",
|
|
1963
|
+
"who",
|
|
1964
|
+
"whom",
|
|
1965
|
+
"whose",
|
|
1966
|
+
"when",
|
|
1967
|
+
"where",
|
|
1968
|
+
"why",
|
|
1969
|
+
"how",
|
|
1970
|
+
// prepositions
|
|
1971
|
+
"about",
|
|
1972
|
+
"above",
|
|
1973
|
+
"after",
|
|
1974
|
+
"at",
|
|
1975
|
+
"before",
|
|
1976
|
+
"by",
|
|
1977
|
+
"for",
|
|
1978
|
+
"from",
|
|
1979
|
+
"in",
|
|
1980
|
+
"into",
|
|
1981
|
+
"of",
|
|
1982
|
+
"off",
|
|
1983
|
+
"on",
|
|
1984
|
+
"onto",
|
|
1985
|
+
"over",
|
|
1986
|
+
"to",
|
|
1987
|
+
"under",
|
|
1988
|
+
"with",
|
|
1989
|
+
"without",
|
|
1990
|
+
"through",
|
|
1991
|
+
"during",
|
|
1992
|
+
// conjunctions and particles
|
|
1993
|
+
"and",
|
|
1994
|
+
"but",
|
|
1995
|
+
"or",
|
|
1996
|
+
"nor",
|
|
1997
|
+
"so",
|
|
1998
|
+
"if",
|
|
1999
|
+
"then",
|
|
2000
|
+
"than",
|
|
2001
|
+
"as",
|
|
2002
|
+
"there",
|
|
2003
|
+
"here"
|
|
2004
|
+
]);
|
|
1858
2005
|
SUBSTRING_MIN = 2;
|
|
1859
2006
|
PARTICLES = [
|
|
1860
2007
|
"\uC774\uB77C\uB3C4",
|
|
@@ -2005,7 +2152,7 @@ function freshnessOf(memory, claims, now) {
|
|
|
2005
2152
|
if (memory.status === "superseded") return "superseded";
|
|
2006
2153
|
if (memory.status === "retired") return "retired";
|
|
2007
2154
|
const current = claims.find((c2) => c2.id === memory.current_claim) ?? claims[0];
|
|
2008
|
-
const until = current?.valid_until;
|
|
2155
|
+
const until = current?.valid_until ?? memory.valid_until ?? null;
|
|
2009
2156
|
if (until != null) {
|
|
2010
2157
|
const t = Date.parse(until);
|
|
2011
2158
|
if (Number.isFinite(t) && t < now.getTime()) return "expired";
|
|
@@ -2035,7 +2182,23 @@ function recall(entries, query, opts = {}) {
|
|
|
2035
2182
|
(e) => (e.chunks ?? []).map((c2) => ({ memoryId: e.memory.id, label: c2.label, text: c2.text }))
|
|
2036
2183
|
)
|
|
2037
2184
|
).search(queryShape.lexical);
|
|
2038
|
-
const
|
|
2185
|
+
const offeredSemantic = depth === "exact" ? [] : opts.semantic ?? [];
|
|
2186
|
+
const reachedByWords = /* @__PURE__ */ new Set([
|
|
2187
|
+
...cardHits.map((hit) => hit.memory.id),
|
|
2188
|
+
...chunkHits.map((hit) => hit.memoryId)
|
|
2189
|
+
]);
|
|
2190
|
+
const floor = opts.meaningFloor;
|
|
2191
|
+
const bestSimilarity = offeredSemantic.reduce(
|
|
2192
|
+
(best, hit) => Math.max(best, Math.min(1, Math.max(0, hit.similarity))),
|
|
2193
|
+
0
|
|
2194
|
+
);
|
|
2195
|
+
const semanticHits = floor === void 0 ? offeredSemantic : offeredSemantic.filter((hit) => {
|
|
2196
|
+
if (reachedByWords.has(hit.id)) return true;
|
|
2197
|
+
const s = Math.min(1, Math.max(0, hit.similarity));
|
|
2198
|
+
if (floor.absolute !== void 0 && s < floor.absolute) return false;
|
|
2199
|
+
if (floor.gap !== void 0 && bestSimilarity - s > floor.gap) return false;
|
|
2200
|
+
return true;
|
|
2201
|
+
});
|
|
2039
2202
|
const scoreById = /* @__PURE__ */ new Map();
|
|
2040
2203
|
for (const hit of cardHits) scoreById.set(hit.memory.id, hit.score);
|
|
2041
2204
|
for (const hit of chunkHits) {
|
|
@@ -2045,6 +2208,14 @@ function recall(entries, query, opts = {}) {
|
|
|
2045
2208
|
const similarity = Math.min(1, Math.max(0, hit.similarity));
|
|
2046
2209
|
scoreById.set(hit.id, (scoreById.get(hit.id) ?? 0) + similarity * SEMANTIC_WEIGHT);
|
|
2047
2210
|
}
|
|
2211
|
+
const wordIds = reachedByWords;
|
|
2212
|
+
const meaningIds = new Set(semanticHits.map((hit) => hit.id));
|
|
2213
|
+
const matchedOf = (id) => {
|
|
2214
|
+
if (opts.semantic === void 0 || depth === "exact") return void 0;
|
|
2215
|
+
const words = wordIds.has(id);
|
|
2216
|
+
const meaning = meaningIds.has(id);
|
|
2217
|
+
return words && meaning ? "both" : words ? "words" : meaning ? "meaning" : void 0;
|
|
2218
|
+
};
|
|
2048
2219
|
const memoryById = new Map(allMemories.map((m) => [m.id, m]));
|
|
2049
2220
|
const hits = [...scoreById.entries()].flatMap(([id, score]) => {
|
|
2050
2221
|
const memory = memoryById.get(id);
|
|
@@ -2145,7 +2316,11 @@ function recall(entries, query, opts = {}) {
|
|
|
2145
2316
|
type: memory.type,
|
|
2146
2317
|
// The handle IS the progressive part. No claim text here, by
|
|
2147
2318
|
// construction rather than by remembering to strip it.
|
|
2148
|
-
detail_handle: `recall://${memory.id}
|
|
2319
|
+
detail_handle: `recall://${memory.id}`,
|
|
2320
|
+
...(() => {
|
|
2321
|
+
const matched = matchedOf(memory.id);
|
|
2322
|
+
return matched === void 0 ? {} : { matched };
|
|
2323
|
+
})()
|
|
2149
2324
|
};
|
|
2150
2325
|
}
|
|
2151
2326
|
}
|
|
@@ -2171,7 +2346,7 @@ function depthOf(value) {
|
|
|
2171
2346
|
function boostFor(memory) {
|
|
2172
2347
|
return (memory.binding === true ? BINDING_BOOST : 0) + (memory.kind === void 0 ? 0 : KIND_BOOST[memory.kind] ?? 0);
|
|
2173
2348
|
}
|
|
2174
|
-
var RECALL_DEPTHS, RecallOptionError, DEFAULTS, BODY_WEIGHT, SEMANTIC_WEIGHT, BINDING_BOOST, KIND_BOOST, DECISION_NOUNS, DECISION_CONTEXT, DECISION_FILLERS, CURRENCY_RANK, WIDEST_ESTIMATE;
|
|
2349
|
+
var MEANING_ONLY_NOTE, RECALL_DEPTHS, RecallOptionError, DEFAULTS, BODY_WEIGHT, SEMANTIC_WEIGHT, BINDING_BOOST, KIND_BOOST, DECISION_NOUNS, DECISION_CONTEXT, DECISION_FILLERS, CURRENCY_RANK, WIDEST_ESTIMATE;
|
|
2175
2350
|
var init_planner = __esm({
|
|
2176
2351
|
"../../packages/memory-engine/src/recall/planner.ts"() {
|
|
2177
2352
|
"use strict";
|
|
@@ -2180,6 +2355,7 @@ var init_planner = __esm({
|
|
|
2180
2355
|
init_conflicts();
|
|
2181
2356
|
init_chunks();
|
|
2182
2357
|
init_search();
|
|
2358
|
+
MEANING_ONLY_NOTE = "matched by meaning only \u2014 none of the question\u2019s words are in this note";
|
|
2183
2359
|
RECALL_DEPTHS = ["exact", "standard"];
|
|
2184
2360
|
RecallOptionError = class extends Error {
|
|
2185
2361
|
hint;
|
|
@@ -2273,6 +2449,170 @@ var init_semantic = __esm({
|
|
|
2273
2449
|
}
|
|
2274
2450
|
});
|
|
2275
2451
|
|
|
2452
|
+
// ../../packages/memory-engine/src/base-templates.ts
|
|
2453
|
+
function base(id, title, summary, body) {
|
|
2454
|
+
return [
|
|
2455
|
+
"---",
|
|
2456
|
+
"memgineering:",
|
|
2457
|
+
` id: ${id}`,
|
|
2458
|
+
` title: ${title}`,
|
|
2459
|
+
` summary: ${summary}`,
|
|
2460
|
+
" scope: base",
|
|
2461
|
+
"---",
|
|
2462
|
+
"",
|
|
2463
|
+
`# ${title}`,
|
|
2464
|
+
"",
|
|
2465
|
+
body,
|
|
2466
|
+
""
|
|
2467
|
+
].join("\n");
|
|
2468
|
+
}
|
|
2469
|
+
function ownEntry(record, key2) {
|
|
2470
|
+
return Object.hasOwn(record, key2) ? record[key2] : void 0;
|
|
2471
|
+
}
|
|
2472
|
+
var BASE_TEMPLATES, RETIRED_BASE_TEMPLATES;
|
|
2473
|
+
var init_base_templates = __esm({
|
|
2474
|
+
"../../packages/memory-engine/src/base-templates.ts"() {
|
|
2475
|
+
"use strict";
|
|
2476
|
+
BASE_TEMPLATES = {
|
|
2477
|
+
"01_BASE/CRITICAL_FACTS.md": base(
|
|
2478
|
+
"critical-facts",
|
|
2479
|
+
"Critical facts",
|
|
2480
|
+
"The few things that must never be wrong.",
|
|
2481
|
+
[
|
|
2482
|
+
"Keep this short enough to read every session. Anything that can be",
|
|
2483
|
+
"looked up when needed belongs in a note instead.",
|
|
2484
|
+
"",
|
|
2485
|
+
"- "
|
|
2486
|
+
].join("\n")
|
|
2487
|
+
),
|
|
2488
|
+
"01_BASE/USER.md": base(
|
|
2489
|
+
"user",
|
|
2490
|
+
"About me",
|
|
2491
|
+
"Who I am and what context I work in.",
|
|
2492
|
+
[
|
|
2493
|
+
"Background, role, what you are working on, what an agent should assume",
|
|
2494
|
+
"you already know.",
|
|
2495
|
+
"",
|
|
2496
|
+
"- "
|
|
2497
|
+
].join("\n")
|
|
2498
|
+
),
|
|
2499
|
+
"01_BASE/PREFERENCES.md": base(
|
|
2500
|
+
"preferences",
|
|
2501
|
+
"Preferences",
|
|
2502
|
+
"How I like answers, code and decisions handled.",
|
|
2503
|
+
["Style, verbosity, how much to check in, how you like code written.", "", "- "].join("\n")
|
|
2504
|
+
),
|
|
2505
|
+
"01_BASE/BOUNDARIES.md": base(
|
|
2506
|
+
"boundaries",
|
|
2507
|
+
"Boundaries",
|
|
2508
|
+
"What an agent must not do without asking me first.",
|
|
2509
|
+
[
|
|
2510
|
+
"Privacy, spending, anything irreversible, anything that leaves this",
|
|
2511
|
+
"machine. Be specific \u2014 a boundary that has to be interpreted is one",
|
|
2512
|
+
"that will be interpreted generously.",
|
|
2513
|
+
"",
|
|
2514
|
+
"Storage boundaries belong here too: topics you never want written into",
|
|
2515
|
+
"this brain \u2014 health, beliefs, relationships, anything you name. An",
|
|
2516
|
+
"agent checks this file before it records anything.",
|
|
2517
|
+
"",
|
|
2518
|
+
"- "
|
|
2519
|
+
].join("\n")
|
|
2520
|
+
),
|
|
2521
|
+
"01_BASE/TOOLING.md": base(
|
|
2522
|
+
"tooling",
|
|
2523
|
+
"Tooling",
|
|
2524
|
+
"The tools, machines and commands I actually use.",
|
|
2525
|
+
[
|
|
2526
|
+
"Editors, languages, deploy commands, where things run. Enough that an",
|
|
2527
|
+
"agent does not have to guess or discover it again each time.",
|
|
2528
|
+
"",
|
|
2529
|
+
"- "
|
|
2530
|
+
].join("\n")
|
|
2531
|
+
)
|
|
2532
|
+
};
|
|
2533
|
+
RETIRED_BASE_TEMPLATES = {
|
|
2534
|
+
// Retired 0.11.1, when the prompt grew a storage-boundary invitation.
|
|
2535
|
+
"01_BASE/BOUNDARIES.md": [
|
|
2536
|
+
[
|
|
2537
|
+
"---",
|
|
2538
|
+
"memgineering:",
|
|
2539
|
+
" id: boundaries",
|
|
2540
|
+
" title: Boundaries",
|
|
2541
|
+
" summary: What an agent must not do without asking me first.",
|
|
2542
|
+
" scope: base",
|
|
2543
|
+
"---",
|
|
2544
|
+
"",
|
|
2545
|
+
"# Boundaries",
|
|
2546
|
+
"",
|
|
2547
|
+
"Privacy, spending, anything irreversible, anything that leaves this",
|
|
2548
|
+
"machine. Be specific \u2014 a boundary that has to be interpreted is one",
|
|
2549
|
+
"that will be interpreted generously.",
|
|
2550
|
+
"",
|
|
2551
|
+
"- ",
|
|
2552
|
+
""
|
|
2553
|
+
].join("\n")
|
|
2554
|
+
]
|
|
2555
|
+
};
|
|
2556
|
+
}
|
|
2557
|
+
});
|
|
2558
|
+
|
|
2559
|
+
// ../../packages/memory-engine/src/recall/unfilled.ts
|
|
2560
|
+
var UNFILLED_FIELDS;
|
|
2561
|
+
var init_unfilled = __esm({
|
|
2562
|
+
"../../packages/memory-engine/src/recall/unfilled.ts"() {
|
|
2563
|
+
"use strict";
|
|
2564
|
+
init_base_templates();
|
|
2565
|
+
UNFILLED_FIELDS = {
|
|
2566
|
+
/** On every card from a server that has this: still the starter file, nothing written in it. */
|
|
2567
|
+
card: "unfilled",
|
|
2568
|
+
/**
|
|
2569
|
+
* In the request: how many such cards the caller will take. Absent means no
|
|
2570
|
+
* cap, so a client older than the field gets the whole ranking it always got —
|
|
2571
|
+
* the same boundary `budget` draws: the client states the capability.
|
|
2572
|
+
*/
|
|
2573
|
+
slots: "template_slots",
|
|
2574
|
+
/** In the response, only when the cap left something out: how many. */
|
|
2575
|
+
withheld: "templates_withheld"
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
});
|
|
2579
|
+
|
|
2580
|
+
// ../../packages/memory-engine/src/recall/query-prefix.ts
|
|
2581
|
+
var init_query_prefix = __esm({
|
|
2582
|
+
"../../packages/memory-engine/src/recall/query-prefix.ts"() {
|
|
2583
|
+
"use strict";
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2586
|
+
|
|
2587
|
+
// ../../packages/memory-engine/src/recall/scope-spread.ts
|
|
2588
|
+
function declaredScope(memory) {
|
|
2589
|
+
return memory.provenance?.scopeSource === "explicit" ? memory.scope ?? null : null;
|
|
2590
|
+
}
|
|
2591
|
+
function scopeSpread(returned, brain) {
|
|
2592
|
+
const scoped = returned.filter((s) => typeof s === "string" && s !== "");
|
|
2593
|
+
const distinct = new Set(scoped);
|
|
2594
|
+
if (distinct.size !== 1) return null;
|
|
2595
|
+
const only = [...distinct][0];
|
|
2596
|
+
const others = /* @__PURE__ */ new Set();
|
|
2597
|
+
for (const scope of brain) if (scope && scope !== only) others.add(scope);
|
|
2598
|
+
if (others.size === 0) return null;
|
|
2599
|
+
const sorted = [...others].sort((a, b) => a.localeCompare(b));
|
|
2600
|
+
return {
|
|
2601
|
+
scope: only,
|
|
2602
|
+
cards: scoped.length,
|
|
2603
|
+
of: returned.length,
|
|
2604
|
+
elsewhere: sorted.slice(0, ELSEWHERE_SHOWN),
|
|
2605
|
+
elsewhere_total: sorted.length
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
var ELSEWHERE_SHOWN;
|
|
2609
|
+
var init_scope_spread = __esm({
|
|
2610
|
+
"../../packages/memory-engine/src/recall/scope-spread.ts"() {
|
|
2611
|
+
"use strict";
|
|
2612
|
+
ELSEWHERE_SHOWN = 5;
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
|
|
2276
2616
|
// ../../packages/memory-engine/src/proposal/schema.ts
|
|
2277
2617
|
import { z as z3 } from "zod";
|
|
2278
2618
|
function isLifecycleAction(action) {
|
|
@@ -2284,8 +2624,14 @@ function isClassifyAction(action) {
|
|
|
2284
2624
|
function isBindingAction(action) {
|
|
2285
2625
|
return action === "unbind";
|
|
2286
2626
|
}
|
|
2627
|
+
function isScopeAction(action) {
|
|
2628
|
+
return action === "rescope";
|
|
2629
|
+
}
|
|
2630
|
+
function isValidityAction(action) {
|
|
2631
|
+
return action === "expire";
|
|
2632
|
+
}
|
|
2287
2633
|
function carriesNoClaim(action) {
|
|
2288
|
-
return isLifecycleAction(action) || isClassifyAction(action) || isBindingAction(action);
|
|
2634
|
+
return isLifecycleAction(action) || isClassifyAction(action) || isBindingAction(action) || isScopeAction(action) || isValidityAction(action);
|
|
2289
2635
|
}
|
|
2290
2636
|
function isAccepted(value) {
|
|
2291
2637
|
return typeof value === "object" && value !== null && value[ACCEPTED] === true;
|
|
@@ -2329,6 +2675,32 @@ var init_schema2 = __esm({
|
|
|
2329
2675
|
* (`remember --rule`, or a `revise` that is genuinely saying something new).
|
|
2330
2676
|
*/
|
|
2331
2677
|
"unbind",
|
|
2678
|
+
/**
|
|
2679
|
+
* Where a memory binds changes. Nothing it SAYS changes.
|
|
2680
|
+
*
|
|
2681
|
+
* `scope` is read by `declaredScopeOf` and decides which of the two rule
|
|
2682
|
+
* budgets a decision is drawn from, so it is the field that answers "does
|
|
2683
|
+
* this bind me here". Nothing could write it. The one live consequence:
|
|
2684
|
+
* folding a project-scoped rule into an unscoped one required superseding
|
|
2685
|
+
* it, which retires the loser — so the rule would have vanished everywhere
|
|
2686
|
+
* except the project it was scoped to.
|
|
2687
|
+
*
|
|
2688
|
+
* The value is a scope NAME, or the reserved word `global`. Never null: see
|
|
2689
|
+
* the refinement below for why "absent" and "widen" must not be the same
|
|
2690
|
+
* value on the wire.
|
|
2691
|
+
*/
|
|
2692
|
+
"rescope",
|
|
2693
|
+
/**
|
|
2694
|
+
* A memory gets an end date. It is not retired, and it still recalls.
|
|
2695
|
+
*
|
|
2696
|
+
* `freshnessOf` has always returned `expired` for a claim whose
|
|
2697
|
+
* `valid_until` has passed, and `CURRENCY_RANK` has always ranked that just
|
|
2698
|
+
* below `current` — read, ranked, and labelled, with nothing able to write
|
|
2699
|
+
* it. A date-stamped fact ("Phase 1 lands by the end of October") therefore
|
|
2700
|
+
* had two options, both wrong: retire it, which says it no longer holds, or
|
|
2701
|
+
* leave it, which says it holds forever.
|
|
2702
|
+
*/
|
|
2703
|
+
"expire",
|
|
2332
2704
|
"ignore"
|
|
2333
2705
|
]);
|
|
2334
2706
|
StorableActionSchema = ProposalActionSchema.exclude(["ignore"]);
|
|
@@ -2488,6 +2860,37 @@ var init_schema2 = __esm({
|
|
|
2488
2860
|
* reclassify is pending — approve, reject, or let it expire first.
|
|
2489
2861
|
*/
|
|
2490
2862
|
proposed_kind: MemoryKindSchema.nullable().default(null),
|
|
2863
|
+
/**
|
|
2864
|
+
* Where `rescope` moves the memory to, and null for everything else.
|
|
2865
|
+
*
|
|
2866
|
+
* NEVER null to mean "everywhere". That was the first design and it is the
|
|
2867
|
+
* one thing this field must not do: "the caller sent no scope" and "the
|
|
2868
|
+
* caller asked to widen this rule" would be the same bytes, so the schema
|
|
2869
|
+
* could not require a scope on a rescope — and a request that LOST the
|
|
2870
|
+
* field, an older client, a hand-built body, a proposal rebuilt from disk,
|
|
2871
|
+
* would silently widen what a rule binds instead of failing.
|
|
2872
|
+
*
|
|
2873
|
+
* `global` is the word instead, and it is not invented here: `GLOBAL_SCOPE`
|
|
2874
|
+
* is already the reserved value a rule write uses to say it holds
|
|
2875
|
+
* everywhere, and `remember` already stores it as no scope at all. So the
|
|
2876
|
+
* widening is spelled, the refinement below can demand it, and the patcher
|
|
2877
|
+
* still writes what the thirty global rules already on a real brain look
|
|
2878
|
+
* like — no key.
|
|
2879
|
+
*
|
|
2880
|
+
* COMPATIBILITY BOUNDARY, as for `proposed_kind`: a proposal file carrying
|
|
2881
|
+
* `action: "rescope"` does not parse on a CLI built before this, and
|
|
2882
|
+
* `loadProposal` throws rather than silently dropping it.
|
|
2883
|
+
*/
|
|
2884
|
+
proposed_scope: z3.string().min(1).max(SCOPE_LIMIT).nullable().default(null),
|
|
2885
|
+
/**
|
|
2886
|
+
* The end date `expire` writes onto the current claim, null elsewhere.
|
|
2887
|
+
*
|
|
2888
|
+
* A date rather than a status: `freshnessOf` reads `valid_until` off the
|
|
2889
|
+
* claim and returns `expired`, which recall already labels and ranks below
|
|
2890
|
+
* `current`. Nothing here decides what expiry MEANS — that was decided
|
|
2891
|
+
* years of code ago and only the writer was missing.
|
|
2892
|
+
*/
|
|
2893
|
+
proposed_valid_until: z3.string().min(1).nullable().default(null),
|
|
2491
2894
|
relations: ProposedRelationsSchema,
|
|
2492
2895
|
/** Why the AI thinks this change is right. Shown at approval time. */
|
|
2493
2896
|
reason: z3.string().min(1),
|
|
@@ -2524,7 +2927,7 @@ var init_schema2 = __esm({
|
|
|
2524
2927
|
path: ["source_excerpt"]
|
|
2525
2928
|
}
|
|
2526
2929
|
).refine((p) => carriesNoClaim(p.action) || p.claim !== null, {
|
|
2527
|
-
message: "only retire, unretire, reclassify and
|
|
2930
|
+
message: "only retire, unretire, reclassify, unbind, rescope and expire may omit the claim",
|
|
2528
2931
|
path: ["claim"]
|
|
2529
2932
|
}).refine((p) => isClassifyAction(p.action) === (p.proposed_kind !== null), {
|
|
2530
2933
|
message: "proposed_kind belongs to reclassify, and reclassify requires one",
|
|
@@ -2532,6 +2935,18 @@ var init_schema2 = __esm({
|
|
|
2532
2935
|
}).refine((p) => !isClassifyAction(p.action) || p.claim === null, {
|
|
2533
2936
|
message: "reclassify says what sort of memory this is; it does not take a claim",
|
|
2534
2937
|
path: ["claim"]
|
|
2938
|
+
}).refine((p) => isScopeAction(p.action) === (p.proposed_scope !== null), {
|
|
2939
|
+
message: "proposed_scope belongs to rescope, and rescope requires one",
|
|
2940
|
+
path: ["proposed_scope"]
|
|
2941
|
+
}).refine((p) => isValidityAction(p.action) === (p.proposed_valid_until !== null), {
|
|
2942
|
+
message: "proposed_valid_until belongs to expire, and expire requires one",
|
|
2943
|
+
path: ["proposed_valid_until"]
|
|
2944
|
+
}).refine((p) => !isScopeAction(p.action) || p.claim === null, {
|
|
2945
|
+
message: "rescope says where a memory binds; it does not take a claim",
|
|
2946
|
+
path: ["claim"]
|
|
2947
|
+
}).refine((p) => !isValidityAction(p.action) || p.claim === null, {
|
|
2948
|
+
message: "expire says when a memory stops being current; it does not take a claim",
|
|
2949
|
+
path: ["claim"]
|
|
2535
2950
|
});
|
|
2536
2951
|
ACCEPTED = /* @__PURE__ */ Symbol("memgineering.approved");
|
|
2537
2952
|
}
|
|
@@ -3347,12 +3762,12 @@ function commit(prepared, input, opts = {}) {
|
|
|
3347
3762
|
if (carriesNoClaim(parsed.action)) {
|
|
3348
3763
|
if (parsed.claim !== null) {
|
|
3349
3764
|
throw Object.assign(new Error(`${parsed.action} does not take a claim`), {
|
|
3350
|
-
hint: isClassifyAction(parsed.action) ? `${parsed.action} says what sort of memory this is, not what it says. To change the conclusion too, use supersede.` : isBindingAction(parsed.action) ? `${parsed.action} takes a marking back and leaves the conclusion exactly as it is \u2014 that is the whole reason it exists. To change what the note says as well, use supersede.` : `${parsed.action} changes whether a memory is current, not what it says. To change the conclusion too, use supersede.`
|
|
3765
|
+
hint: isClassifyAction(parsed.action) ? `${parsed.action} says what sort of memory this is, not what it says. To change the conclusion too, use supersede.` : isBindingAction(parsed.action) ? `${parsed.action} takes a marking back and leaves the conclusion exactly as it is \u2014 that is the whole reason it exists. To change what the note says as well, use supersede.` : isScopeAction(parsed.action) ? `${parsed.action} says where a memory binds, not what it says. To change the conclusion too, use supersede.` : isValidityAction(parsed.action) ? `${parsed.action} gives a memory an end date and leaves the conclusion exactly as it is. To change what the note says as well, use supersede.` : `${parsed.action} changes whether a memory is current, not what it says. To change the conclusion too, use supersede.`
|
|
3351
3766
|
});
|
|
3352
3767
|
}
|
|
3353
3768
|
} else if (parsed.claim === null) {
|
|
3354
3769
|
throw Object.assign(new Error(`${parsed.action} needs a claim`), {
|
|
3355
|
-
hint: "Only retire, unretire, reclassify and
|
|
3770
|
+
hint: "Only retire, unretire, reclassify, unbind, rescope and expire may omit it. They change a memory's standing, its classification, whether it binds, where it binds or when it stops being current \u2014 rather than its content."
|
|
3356
3771
|
});
|
|
3357
3772
|
} else if ((parsed.action === "supersede" || parsed.action === "create") && parsed.claim.summary === null) {
|
|
3358
3773
|
throw Object.assign(new Error(`${parsed.action} needs a summary as well as a claim`), {
|
|
@@ -3369,6 +3784,26 @@ function commit(prepared, input, opts = {}) {
|
|
|
3369
3784
|
hint: "Only `reclassify` moves a memory's kind, and it does nothing else. Propose it separately, with its own reason."
|
|
3370
3785
|
});
|
|
3371
3786
|
}
|
|
3787
|
+
if (isScopeAction(parsed.action) && parsed.proposed_scope === null) {
|
|
3788
|
+
throw Object.assign(new Error(`${parsed.action} needs the scope it is proposing`), {
|
|
3789
|
+
hint: `Pass a project name, or \`${GLOBAL_SCOPE}\` to say the memory holds everywhere. Widening is spelled out because it cannot be taken back by accident.`
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
if (!isScopeAction(parsed.action) && parsed.proposed_scope !== null) {
|
|
3793
|
+
throw Object.assign(new Error(`${parsed.action} does not set the scope`), {
|
|
3794
|
+
hint: "Only `rescope` moves where a memory binds, and it does nothing else. Propose it separately, with its own reason."
|
|
3795
|
+
});
|
|
3796
|
+
}
|
|
3797
|
+
if (isValidityAction(parsed.action) && parsed.proposed_valid_until === null) {
|
|
3798
|
+
throw Object.assign(new Error(`${parsed.action} needs the date it is proposing`), {
|
|
3799
|
+
hint: "Pass an ISO date \u2014 the day the memory stops being current. It is not retired by this: recall still returns it, ranked below what is current and labelled."
|
|
3800
|
+
});
|
|
3801
|
+
}
|
|
3802
|
+
if (!isValidityAction(parsed.action) && parsed.proposed_valid_until !== null) {
|
|
3803
|
+
throw Object.assign(new Error(`${parsed.action} does not set an end date`), {
|
|
3804
|
+
hint: "Only `expire` gives a memory an end date, and it does nothing else. Propose it separately, with its own reason."
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3372
3807
|
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
3373
3808
|
const at = now.toISOString();
|
|
3374
3809
|
const draft = {
|
|
@@ -3378,6 +3813,8 @@ function commit(prepared, input, opts = {}) {
|
|
|
3378
3813
|
memory_id: parsed.memory_id,
|
|
3379
3814
|
claim: parsed.claim,
|
|
3380
3815
|
proposed_kind: parsed.proposed_kind,
|
|
3816
|
+
proposed_scope: parsed.proposed_scope,
|
|
3817
|
+
proposed_valid_until: parsed.proposed_valid_until,
|
|
3381
3818
|
relations: parsed.relations,
|
|
3382
3819
|
reason: parsed.reason,
|
|
3383
3820
|
source_excerpt: parsed.source_excerpt,
|
|
@@ -3416,6 +3853,14 @@ var init_commit = __esm({
|
|
|
3416
3853
|
claim: ProposedClaimSchema.nullable().default(null),
|
|
3417
3854
|
/** What `reclassify` is reclassifying this memory AS. Only that action takes one. */
|
|
3418
3855
|
proposed_kind: MemoryKindSchema.nullable().default(null),
|
|
3856
|
+
/**
|
|
3857
|
+
* Where `rescope` moves this memory to — a scope name, or the reserved word
|
|
3858
|
+
* `global`. Only that action takes one, and it may not be null: see
|
|
3859
|
+
* `ProposalSchema.proposed_scope` for why "absent" must not spell "widen".
|
|
3860
|
+
*/
|
|
3861
|
+
proposed_scope: z5.string().min(1).max(SCOPE_LIMIT).nullable().default(null),
|
|
3862
|
+
/** The end date `expire` writes onto the current claim. Only that action takes one. */
|
|
3863
|
+
proposed_valid_until: z5.string().min(1).nullable().default(null),
|
|
3419
3864
|
relations: ProposedRelationsSchema.default({}),
|
|
3420
3865
|
reason: z5.string().min(1),
|
|
3421
3866
|
/** The passage from the note that justifies this. Required to submit. */
|
|
@@ -3526,6 +3971,58 @@ function unbindInput(entry, opts = {}) {
|
|
|
3526
3971
|
target_path: entry.path
|
|
3527
3972
|
};
|
|
3528
3973
|
}
|
|
3974
|
+
function rescopeInput(entry, scope, opts = {}) {
|
|
3975
|
+
const wanted = scope.trim();
|
|
3976
|
+
if (wanted === "") {
|
|
3977
|
+
throw Object.assign(new Error("a scope cannot be empty"), {
|
|
3978
|
+
hint: `Name the project this memory binds in, or \`${GLOBAL_SCOPE}\` to say it holds everywhere.`
|
|
3979
|
+
});
|
|
3980
|
+
}
|
|
3981
|
+
if ([...wanted].length > SCOPE_LIMIT) {
|
|
3982
|
+
throw Object.assign(new Error(`a scope may be at most ${SCOPE_LIMIT} characters`), {
|
|
3983
|
+
hint: "A scope names a project. If it needs a sentence, it belongs in the note."
|
|
3984
|
+
});
|
|
3985
|
+
}
|
|
3986
|
+
const declared = declaredScopeOf(entry.memory);
|
|
3987
|
+
const same = isGlobalScope(wanted) ? declared === null : declared === wanted;
|
|
3988
|
+
if (same) {
|
|
3989
|
+
throw Object.assign(new Error(`${entry.memory.title} already binds there`), {
|
|
3990
|
+
hint: "Nothing was written, and nothing needed to be."
|
|
3991
|
+
});
|
|
3992
|
+
}
|
|
3993
|
+
return {
|
|
3994
|
+
action: "rescope",
|
|
3995
|
+
memory_id: entry.memory.id,
|
|
3996
|
+
claim: null,
|
|
3997
|
+
proposed_scope: wanted,
|
|
3998
|
+
reason: opts.reason ?? `binds in ${wanted}`,
|
|
3999
|
+
source_excerpt: opts.because ?? entry.memory.summary ?? entry.memory.title,
|
|
4000
|
+
target_path: entry.path
|
|
4001
|
+
};
|
|
4002
|
+
}
|
|
4003
|
+
function expireInput(entry, until, opts = {}) {
|
|
4004
|
+
const wanted = until.trim();
|
|
4005
|
+
const at = Date.parse(wanted);
|
|
4006
|
+
if (!Number.isFinite(at)) {
|
|
4007
|
+
throw Object.assign(new Error(`${until} is not a date`), {
|
|
4008
|
+
hint: "Pass an ISO date, such as 2026-10-31 or 2026-10-31T00:00:00Z."
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
if ((entry.claims[0]?.valid_until ?? entry.memory.valid_until ?? null) === wanted) {
|
|
4012
|
+
throw Object.assign(new Error(`${entry.memory.title} already ends then`), {
|
|
4013
|
+
hint: "Nothing was written, and nothing needed to be."
|
|
4014
|
+
});
|
|
4015
|
+
}
|
|
4016
|
+
return {
|
|
4017
|
+
action: "expire",
|
|
4018
|
+
memory_id: entry.memory.id,
|
|
4019
|
+
claim: null,
|
|
4020
|
+
proposed_valid_until: wanted,
|
|
4021
|
+
reason: opts.reason ?? `holds until ${wanted}`,
|
|
4022
|
+
source_excerpt: opts.because ?? entry.memory.summary ?? entry.memory.title,
|
|
4023
|
+
target_path: entry.path
|
|
4024
|
+
};
|
|
4025
|
+
}
|
|
3529
4026
|
function saysNothingNew(entry, input, normalize) {
|
|
3530
4027
|
const norm = (v) => normalize(v ?? "").trim();
|
|
3531
4028
|
const claim = input.claim;
|
|
@@ -3565,6 +4062,8 @@ var init_curate = __esm({
|
|
|
3565
4062
|
"../../packages/memory-engine/src/capture/curate.ts"() {
|
|
3566
4063
|
"use strict";
|
|
3567
4064
|
init_planner();
|
|
4065
|
+
init_schema();
|
|
4066
|
+
init_rule_budget();
|
|
3568
4067
|
NO_REASON_GIVEN = "revised directly";
|
|
3569
4068
|
noLifecycleReason = (action) => `${action}d directly`;
|
|
3570
4069
|
}
|
|
@@ -3619,6 +4118,21 @@ function desiredFields(proposal, existing) {
|
|
|
3619
4118
|
delete out["binding"];
|
|
3620
4119
|
return out;
|
|
3621
4120
|
}
|
|
4121
|
+
if (isScopeAction(proposal.action)) {
|
|
4122
|
+
if (proposal.proposed_scope === null) {
|
|
4123
|
+
throw new Error(`${proposal.action} reached the patcher with no scope`);
|
|
4124
|
+
}
|
|
4125
|
+
if (isGlobalScope(proposal.proposed_scope)) delete out["scope"];
|
|
4126
|
+
else out["scope"] = proposal.proposed_scope;
|
|
4127
|
+
return out;
|
|
4128
|
+
}
|
|
4129
|
+
if (isValidityAction(proposal.action)) {
|
|
4130
|
+
if (proposal.proposed_valid_until === null) {
|
|
4131
|
+
throw new Error(`${proposal.action} reached the patcher with no date`);
|
|
4132
|
+
}
|
|
4133
|
+
out["valid_until"] = proposal.proposed_valid_until;
|
|
4134
|
+
return out;
|
|
4135
|
+
}
|
|
3622
4136
|
const claim = proposal.claim;
|
|
3623
4137
|
if (claim === null) {
|
|
3624
4138
|
throw new Error(`${proposal.action} reached the patcher with no claim`);
|
|
@@ -3956,6 +4470,68 @@ function verifyPatch(patched, proposal, before) {
|
|
|
3956
4470
|
}
|
|
3957
4471
|
return;
|
|
3958
4472
|
}
|
|
4473
|
+
if (isScopeAction(proposal.action) || isValidityAction(proposal.action)) {
|
|
4474
|
+
if (before === void 0) {
|
|
4475
|
+
throw new PatchError(
|
|
4476
|
+
`${proposal.action} cannot be verified without the note it started from`,
|
|
4477
|
+
"This is a defect in the caller, not in your note. Nothing was written."
|
|
4478
|
+
);
|
|
4479
|
+
}
|
|
4480
|
+
if (isScopeAction(proposal.action)) {
|
|
4481
|
+
const want = proposal.proposed_scope;
|
|
4482
|
+
const got = declaredScopeOf(mapped.memory);
|
|
4483
|
+
const landed = isGlobalScope(want) ? got === null : got === want;
|
|
4484
|
+
if (!landed) {
|
|
4485
|
+
throw new PatchError(
|
|
4486
|
+
"the patched note does not declare the scope that was asked for",
|
|
4487
|
+
"This is a defect in the patcher, not in your note. Nothing was written."
|
|
4488
|
+
);
|
|
4489
|
+
}
|
|
4490
|
+
} else if (
|
|
4491
|
+
// Read back the way `freshnessOf` reads it: the claim's date when the
|
|
4492
|
+
// note states a claim, the note's own otherwise. Asserting only on the
|
|
4493
|
+
// claim would reject every note `remember` wrote, since it has never
|
|
4494
|
+
// written a `claim:` key.
|
|
4495
|
+
(mapped.claims[0]?.valid_until ?? mapped.memory.valid_until ?? null) !== proposal.proposed_valid_until
|
|
4496
|
+
) {
|
|
4497
|
+
throw new PatchError(
|
|
4498
|
+
"the patched note does not carry the end date that was asked for",
|
|
4499
|
+
"This is a defect in the patcher, not in your note. Nothing was written."
|
|
4500
|
+
);
|
|
4501
|
+
}
|
|
4502
|
+
const was = mapNoteToMemory({ path: proposal.target.path, content: before, revision: null });
|
|
4503
|
+
if (was.claims[0]?.text !== mapped.claims[0]?.text) {
|
|
4504
|
+
throw new PatchError(
|
|
4505
|
+
`${proposal.action} changed the conclusion, which it must never do`,
|
|
4506
|
+
"It changes one field and leaves what the note says exactly as it was. Nothing was written."
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
4509
|
+
if (was.memory.summary !== mapped.memory.summary) {
|
|
4510
|
+
throw new PatchError(
|
|
4511
|
+
`${proposal.action} changed the summary, which it must never do`,
|
|
4512
|
+
"The summary is what recall matches on, and this action does not touch what the note says. Nothing was written."
|
|
4513
|
+
);
|
|
4514
|
+
}
|
|
4515
|
+
if (was.memory.title !== mapped.memory.title) {
|
|
4516
|
+
throw new PatchError(
|
|
4517
|
+
`${proposal.action} changed the title, which it must never do`,
|
|
4518
|
+
"It changes one field and leaves what the note says exactly as it was. Nothing was written."
|
|
4519
|
+
);
|
|
4520
|
+
}
|
|
4521
|
+
if (was.memory.status !== mapped.memory.status) {
|
|
4522
|
+
throw new PatchError(
|
|
4523
|
+
`${proposal.action} changed the status, which it must never do`,
|
|
4524
|
+
"Moving where a memory binds, or saying when it stops being current, does not retire it. To take it out of the answers, use retire. Nothing was written."
|
|
4525
|
+
);
|
|
4526
|
+
}
|
|
4527
|
+
if (was.memory.binding !== mapped.memory.binding) {
|
|
4528
|
+
throw new PatchError(
|
|
4529
|
+
`${proposal.action} changed whether the memory binds, which it must never do`,
|
|
4530
|
+
"To take a marking back, use `revise --no-rule`. Nothing was written."
|
|
4531
|
+
);
|
|
4532
|
+
}
|
|
4533
|
+
return;
|
|
4534
|
+
}
|
|
3959
4535
|
const supersedes = new Set(
|
|
3960
4536
|
mapped.relations.filter((r) => r.type === "supersedes").map((r) => r.to)
|
|
3961
4537
|
);
|
|
@@ -4031,6 +4607,8 @@ var init_patch = __esm({
|
|
|
4031
4607
|
"../../packages/memory-engine/src/proposal/patch.ts"() {
|
|
4032
4608
|
"use strict";
|
|
4033
4609
|
init_markdown_mapping();
|
|
4610
|
+
init_rule_budget();
|
|
4611
|
+
init_schema();
|
|
4034
4612
|
init_schema2();
|
|
4035
4613
|
NAMESPACE2 = "memgineering";
|
|
4036
4614
|
FENCE_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(\r?\n|$)/;
|
|
@@ -4073,15 +4651,15 @@ async function applyProposal(adapter, accepted) {
|
|
|
4073
4651
|
const patch = computePatch(current.content, proposal);
|
|
4074
4652
|
verifyPatch(patch.content, proposal, current.content);
|
|
4075
4653
|
if (patch.content === current.content) {
|
|
4076
|
-
const
|
|
4077
|
-
if (
|
|
4654
|
+
const base2 = proposal.target.baseRevision;
|
|
4655
|
+
if (base2 === null || base2 === void 0) {
|
|
4078
4656
|
return {
|
|
4079
4657
|
ok: false,
|
|
4080
4658
|
reason: "revision_conflict",
|
|
4081
4659
|
proposal: needsRebase(proposal, current.revision)
|
|
4082
4660
|
};
|
|
4083
4661
|
}
|
|
4084
|
-
if (adapter.compareRevision(
|
|
4662
|
+
if (adapter.compareRevision(base2, current.revision) === "diverged") {
|
|
4085
4663
|
return {
|
|
4086
4664
|
ok: false,
|
|
4087
4665
|
reason: "revision_conflict",
|
|
@@ -4148,23 +4726,36 @@ function rank(entries, events, opts) {
|
|
|
4148
4726
|
const recalls = events.filter((e) => !AUTOMATIC.has(e.verb));
|
|
4149
4727
|
const here = opts.here ? recalls.filter((e) => opts.here(e)) : [];
|
|
4150
4728
|
const hereLabel = opts.hereLabel ?? "in this folder";
|
|
4151
|
-
const
|
|
4152
|
-
const
|
|
4729
|
+
const usageIn = (source) => {
|
|
4730
|
+
const usage = /* @__PURE__ */ new Map();
|
|
4731
|
+
const at = (id) => {
|
|
4732
|
+
let u = usage.get(id);
|
|
4733
|
+
if (!u) usage.set(id, u = { opened: 0, surfaced: 0, credit: 0 });
|
|
4734
|
+
return u;
|
|
4735
|
+
};
|
|
4153
4736
|
for (const event of source) {
|
|
4154
|
-
|
|
4155
|
-
|
|
4737
|
+
if (event.verb === "recall") {
|
|
4738
|
+
event.returned_ids.forEach((id, i) => {
|
|
4739
|
+
const u = at(id);
|
|
4740
|
+
u.surfaced += 1;
|
|
4741
|
+
if (i === 0) u.credit += SURFACED_SHARE;
|
|
4742
|
+
});
|
|
4743
|
+
} else if (DELIBERATE.has(event.verb)) {
|
|
4744
|
+
const ids = event.opened_id ? [event.opened_id] : event.returned_ids;
|
|
4745
|
+
for (const id of new Set(ids)) at(id).opened += 1;
|
|
4746
|
+
}
|
|
4156
4747
|
}
|
|
4157
|
-
return
|
|
4748
|
+
return usage;
|
|
4158
4749
|
};
|
|
4159
|
-
const
|
|
4160
|
-
const
|
|
4750
|
+
const evidence = (u) => u ? u.opened + u.credit : 0;
|
|
4751
|
+
const hereUsage = usageIn(here);
|
|
4752
|
+
const anyUsage = usageIn(recalls);
|
|
4161
4753
|
const lastTouched = /* @__PURE__ */ new Map();
|
|
4162
4754
|
for (const event of recalls) {
|
|
4163
4755
|
const t = Date.parse(event.ts);
|
|
4164
4756
|
if (!Number.isFinite(t)) continue;
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
}
|
|
4757
|
+
const touched = event.verb === "recall" ? event.returned_ids.slice(0, 1) : DELIBERATE.has(event.verb) ? [event.opened_id ?? "", ...event.returned_ids].filter(Boolean) : [];
|
|
4758
|
+
for (const id of touched) lastTouched.set(id, Math.max(lastTouched.get(id) ?? 0, t));
|
|
4168
4759
|
}
|
|
4169
4760
|
const hasBase = entries.some((e) => e.path.startsWith("01_BASE/"));
|
|
4170
4761
|
const scored = [];
|
|
@@ -4174,16 +4765,30 @@ function rank(entries, events, opts) {
|
|
|
4174
4765
|
if (freshness === "retired") continue;
|
|
4175
4766
|
const why = [];
|
|
4176
4767
|
let score = 0;
|
|
4177
|
-
const
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4768
|
+
const hereU = hereUsage.get(id);
|
|
4769
|
+
const anyU = anyUsage.get(id);
|
|
4770
|
+
const hereEvidence = evidence(hereU);
|
|
4771
|
+
const elsewhereEvidence = evidence(anyU) - hereEvidence;
|
|
4772
|
+
if (hereEvidence > 0) {
|
|
4773
|
+
score += WEIGHT.contextRecall * Math.log2(1 + hereEvidence);
|
|
4774
|
+
why.push(usageLine(hereU, hereLabel));
|
|
4775
|
+
}
|
|
4776
|
+
if (elsewhereEvidence > 1e-9) {
|
|
4777
|
+
score += WEIGHT.anyRecall * Math.log2(1 + elsewhereEvidence);
|
|
4778
|
+
if (hereEvidence === 0) {
|
|
4779
|
+
why.push(
|
|
4780
|
+
usageLine(
|
|
4781
|
+
{
|
|
4782
|
+
opened: anyU.opened - (hereU?.opened ?? 0),
|
|
4783
|
+
surfaced: anyU.surfaced - (hereU?.surfaced ?? 0)
|
|
4784
|
+
},
|
|
4785
|
+
"elsewhere"
|
|
4786
|
+
)
|
|
4787
|
+
);
|
|
4788
|
+
}
|
|
4186
4789
|
}
|
|
4790
|
+
const hereCount = (hereU?.opened ?? 0) + (hereU?.surfaced ?? 0);
|
|
4791
|
+
const anyCount = (anyU?.opened ?? 0) + (anyU?.surfaced ?? 0) - hereCount;
|
|
4187
4792
|
const touched = lastTouched.get(id);
|
|
4188
4793
|
if (touched !== void 0) {
|
|
4189
4794
|
const days = (opts.now.getTime() - touched) / 864e5;
|
|
@@ -4204,16 +4809,28 @@ function rank(entries, events, opts) {
|
|
|
4204
4809
|
}
|
|
4205
4810
|
if (score === 0 && !entry.path.startsWith("01_BASE/")) {
|
|
4206
4811
|
score += WEIGHT.neverReached;
|
|
4207
|
-
why.push(
|
|
4812
|
+
why.push(
|
|
4813
|
+
anyU && anyU.surfaced > 0 ? usageLine(
|
|
4814
|
+
{ opened: 0, surfaced: anyU.surfaced },
|
|
4815
|
+
// Labelled by where it happened: all here, none here, or both —
|
|
4816
|
+
// a total said "in this project" would claim the whole count.
|
|
4817
|
+
!hereU?.surfaced ? "elsewhere" : hereU.surfaced === anyU.surfaced ? hereLabel : `(${hereU.surfaced}\xD7 ${hereLabel})`
|
|
4818
|
+
) : "never surfaced yet"
|
|
4819
|
+
);
|
|
4208
4820
|
}
|
|
4209
4821
|
if (freshness !== "current") why.push(freshness);
|
|
4210
|
-
if (score > 0) scored.push({ entry, score, why });
|
|
4822
|
+
if (score > 0) scored.push({ entry, score, why, used: hereCount > 0 || anyCount > 0 });
|
|
4211
4823
|
}
|
|
4212
4824
|
return scored.sort(
|
|
4213
4825
|
(a, b) => b.score - a.score || a.entry.memory.id.localeCompare(b.entry.memory.id)
|
|
4214
4826
|
);
|
|
4215
4827
|
}
|
|
4216
|
-
|
|
4828
|
+
function usageLine(u, where) {
|
|
4829
|
+
if (u.opened === 0) return `surfaced ${u.surfaced}\xD7 ${where}, never opened`;
|
|
4830
|
+
if (u.surfaced === 0) return `opened ${u.opened}\xD7 ${where}`;
|
|
4831
|
+
return `opened ${u.opened}\xD7 \xB7 surfaced ${u.surfaced}\xD7 ${where}`;
|
|
4832
|
+
}
|
|
4833
|
+
var WEIGHT, STALE_BASE_DAYS, SURFACED_SHARE, DELIBERATE;
|
|
4217
4834
|
var init_rank = __esm({
|
|
4218
4835
|
"../../packages/memory-engine/src/resurface/rank.ts"() {
|
|
4219
4836
|
"use strict";
|
|
@@ -4266,6 +4883,8 @@ var init_rank = __esm({
|
|
|
4266
4883
|
binding: 5
|
|
4267
4884
|
};
|
|
4268
4885
|
STALE_BASE_DAYS = 30;
|
|
4886
|
+
SURFACED_SHARE = 0.5;
|
|
4887
|
+
DELIBERATE = /* @__PURE__ */ new Set(["open", "publish", "adapt", "read-public"]);
|
|
4269
4888
|
}
|
|
4270
4889
|
});
|
|
4271
4890
|
|
|
@@ -4284,6 +4903,10 @@ var init_src = __esm({
|
|
|
4284
4903
|
init_reconcile();
|
|
4285
4904
|
init_planner();
|
|
4286
4905
|
init_semantic();
|
|
4906
|
+
init_unfilled();
|
|
4907
|
+
init_query_prefix();
|
|
4908
|
+
init_base_templates();
|
|
4909
|
+
init_scope_spread();
|
|
4287
4910
|
init_conflicts();
|
|
4288
4911
|
init_search();
|
|
4289
4912
|
init_schema2();
|
|
@@ -4299,6 +4922,27 @@ var init_src = __esm({
|
|
|
4299
4922
|
}
|
|
4300
4923
|
});
|
|
4301
4924
|
|
|
4925
|
+
// src/version.ts
|
|
4926
|
+
import { readFileSync } from "fs";
|
|
4927
|
+
import { fileURLToPath } from "url";
|
|
4928
|
+
import { dirname, join as join4 } from "path";
|
|
4929
|
+
var __dir, pkgPath, VERSION;
|
|
4930
|
+
var init_version = __esm({
|
|
4931
|
+
"src/version.ts"() {
|
|
4932
|
+
"use strict";
|
|
4933
|
+
__dir = dirname(fileURLToPath(import.meta.url));
|
|
4934
|
+
pkgPath = join4(__dir, "..", "package.json");
|
|
4935
|
+
VERSION = (() => {
|
|
4936
|
+
try {
|
|
4937
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
4938
|
+
return typeof parsed.version === "string" ? parsed.version : "0.0.0-unknown";
|
|
4939
|
+
} catch {
|
|
4940
|
+
return "0.0.0-unknown";
|
|
4941
|
+
}
|
|
4942
|
+
})();
|
|
4943
|
+
}
|
|
4944
|
+
});
|
|
4945
|
+
|
|
4302
4946
|
// src/lib/credentials.ts
|
|
4303
4947
|
import { mkdir as mkdir3, readFile as readFile3, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
4304
4948
|
import { join as join5 } from "path";
|
|
@@ -4473,8 +5117,8 @@ function readCause(err) {
|
|
|
4473
5117
|
return { detail, retryable, nothingSent: retryable || !vetoed && preFlight };
|
|
4474
5118
|
}
|
|
4475
5119
|
async function apiRequest(args) {
|
|
4476
|
-
const
|
|
4477
|
-
const url = `${
|
|
5120
|
+
const base2 = args.baseUrl ?? apiUrl();
|
|
5121
|
+
const url = `${base2}${args.path}`;
|
|
4478
5122
|
let res;
|
|
4479
5123
|
try {
|
|
4480
5124
|
res = await fetch(url, {
|
|
@@ -4498,7 +5142,7 @@ async function apiRequest(args) {
|
|
|
4498
5142
|
}
|
|
4499
5143
|
throw memgError(
|
|
4500
5144
|
"server_unreachable",
|
|
4501
|
-
`could not reach ${
|
|
5145
|
+
`could not reach ${base2}`,
|
|
4502
5146
|
`${cause.nothingSent ? NOTHING_SENT : MAY_HAVE_ARRIVED} Underlying cause: ${cause.detail}`
|
|
4503
5147
|
);
|
|
4504
5148
|
}
|
|
@@ -4530,7 +5174,7 @@ async function apiRequest(args) {
|
|
|
4530
5174
|
}
|
|
4531
5175
|
throw memgError(
|
|
4532
5176
|
"server_refused",
|
|
4533
|
-
`${
|
|
5177
|
+
`${base2} answered ${res.status} with no readable error`,
|
|
4534
5178
|
`This is not a shape this CLI knows. If MEMGINEERING_API_URL points at something other than a memgineering brain server, that would explain it. First 200 characters of the response: ${text.slice(0, 200)}`
|
|
4535
5179
|
);
|
|
4536
5180
|
}
|
|
@@ -4819,7 +5463,30 @@ var init_config = __esm({
|
|
|
4819
5463
|
* their own words are about to be replaced. It records no content, only
|
|
4820
5464
|
* whether the bytes are still ours.
|
|
4821
5465
|
*/
|
|
4822
|
-
installed: z8.record(z8.string(), z8.string()).default({})
|
|
5466
|
+
installed: z8.record(z8.string(), z8.string()).default({}),
|
|
5467
|
+
/**
|
|
5468
|
+
* Which release wrote the guidance that is sitting in the agent tools.
|
|
5469
|
+
*
|
|
5470
|
+
* The upgrade path used to be a `postinstall` script that told the agent
|
|
5471
|
+
* to run `setup`. npm 12 blocks install scripts by default — measured on
|
|
5472
|
+
* 12.0.2 installing this package: the script never ran and npm printed
|
|
5473
|
+
* only `1 package had install scripts blocked`, which says nothing about
|
|
5474
|
+
* what the script would have said. So the one channel that carried
|
|
5475
|
+
* "your guidance is now out of date" is gone for every user on that npm,
|
|
5476
|
+
* and nothing else noticed: the CLI upgrades itself, the hub and skills
|
|
5477
|
+
* in `~/.claude`, `~/.codex` and `~/.gemini` stay at whatever release
|
|
5478
|
+
* last ran setup, and both halves report success.
|
|
5479
|
+
*
|
|
5480
|
+
* A version rather than a digest, because this is read by a check that
|
|
5481
|
+
* runs before EVERY command and may not pay for reading eight bundled
|
|
5482
|
+
* assets to find out. The cost of the cheap answer is that a release
|
|
5483
|
+
* which did not change any guidance still asks for one `setup` run; the
|
|
5484
|
+
* cost of the expensive one is paid by every recall, forever.
|
|
5485
|
+
*
|
|
5486
|
+
* Absent means "written before this field existed", which is the same
|
|
5487
|
+
* answer as "older than this CLI" and wants the same one command.
|
|
5488
|
+
*/
|
|
5489
|
+
guidanceVersion: z8.string().optional()
|
|
4823
5490
|
}).default({ autoUpdate: "on" })
|
|
4824
5491
|
});
|
|
4825
5492
|
EMPTY_CONFIG = ConfigSchema.parse({});
|
|
@@ -4859,7 +5526,7 @@ async function createCloudBrain(token, baseUrl, name) {
|
|
|
4859
5526
|
});
|
|
4860
5527
|
return res.body;
|
|
4861
5528
|
}
|
|
4862
|
-
var call, askedFromField, cloudRecall, cloudOpen, cloudEvidence, cloudRemember, cloudUndo, cloudLog, cloudPutNote, cloudListNotes, cloudRevise, cloudBaseWrite, cloudLifecycle, cloudUnbind, cloudExclude, RANKED_BY_PROJECT, rankedOnProject, cloudResurface, cloudRules, cloudReindex;
|
|
5529
|
+
var call, askedFromField, cloudRecall, cloudOpen, cloudEvidence, cloudRemember, cloudUndo, cloudLog, cloudPutNote, cloudListNotes, cloudRevise, cloudBaseWrite, cloudLifecycle, cloudUnbind, cloudExclude, RANKED_BY_PROJECT, rankedOnProject, cloudResurface, cloudRescope, cloudExpire, cloudRules, cloudReindex;
|
|
4863
5530
|
var init_cloud_brain = __esm({
|
|
4864
5531
|
"src/lib/cloud-brain.ts"() {
|
|
4865
5532
|
"use strict";
|
|
@@ -4884,7 +5551,8 @@ var init_cloud_brain = __esm({
|
|
|
4884
5551
|
// the honest answer — a client that pre-validated against its own list
|
|
4885
5552
|
// would report a depth as accepted and then have it silently ignored.
|
|
4886
5553
|
...args.depth === void 0 ? {} : { depth: args.depth },
|
|
4887
|
-
...args.budget === void 0 ? {} : { budget: args.budget }
|
|
5554
|
+
...args.budget === void 0 ? {} : { budget: args.budget },
|
|
5555
|
+
...args.templateSlots === void 0 ? {} : { [UNFILLED_FIELDS.slots]: args.templateSlots }
|
|
4888
5556
|
});
|
|
4889
5557
|
cloudOpen = (brain, ref, askedFrom) => call(brain, "POST", "/open", { ref, ...askedFromField(askedFrom) });
|
|
4890
5558
|
cloudEvidence = (brain, ref) => call(brain, "POST", "/evidence", { ref });
|
|
@@ -4963,6 +5631,16 @@ var init_cloud_brain = __esm({
|
|
|
4963
5631
|
...args.limit === void 0 ? {} : { limit: args.limit },
|
|
4964
5632
|
...askedFromField(args.askedFrom)
|
|
4965
5633
|
});
|
|
5634
|
+
cloudRescope = (brain, args) => call(brain, "POST", "/rescope", {
|
|
5635
|
+
ref: args.ref,
|
|
5636
|
+
scope: args.scope,
|
|
5637
|
+
...args.reason === void 0 ? {} : { reason: args.reason }
|
|
5638
|
+
});
|
|
5639
|
+
cloudExpire = (brain, args) => call(brain, "POST", "/expire", {
|
|
5640
|
+
ref: args.ref,
|
|
5641
|
+
until: args.until,
|
|
5642
|
+
...args.reason === void 0 ? {} : { reason: args.reason }
|
|
5643
|
+
});
|
|
4966
5644
|
cloudRules = (brain, args = {}) => call(brain, "POST", "/rules", {
|
|
4967
5645
|
...args.limit === void 0 ? {} : { limit: args.limit },
|
|
4968
5646
|
// The server cannot see this machine's folders, so the project it is
|
|
@@ -5834,20 +6512,6 @@ async function createBrain(path, name) {
|
|
|
5834
6512
|
return { root: canonicalRoot2, indexed: built.entries.length };
|
|
5835
6513
|
}
|
|
5836
6514
|
function files(name) {
|
|
5837
|
-
const base = (id, title, summary, body) => [
|
|
5838
|
-
"---",
|
|
5839
|
-
"memgineering:",
|
|
5840
|
-
` id: ${id}`,
|
|
5841
|
-
` title: ${title}`,
|
|
5842
|
-
` summary: ${summary}`,
|
|
5843
|
-
" scope: base",
|
|
5844
|
-
"---",
|
|
5845
|
-
"",
|
|
5846
|
-
`# ${title}`,
|
|
5847
|
-
"",
|
|
5848
|
-
body,
|
|
5849
|
-
""
|
|
5850
|
-
].join("\n");
|
|
5851
6515
|
return {
|
|
5852
6516
|
"00_HUB/HUB.md": [
|
|
5853
6517
|
"---",
|
|
@@ -5887,61 +6551,9 @@ function files(name) {
|
|
|
5887
6551
|
"and rewritten without asking. A number in front means it is yours.",
|
|
5888
6552
|
""
|
|
5889
6553
|
].join("\n"),
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
"The few things that must never be wrong.",
|
|
5894
|
-
[
|
|
5895
|
-
"Keep this short enough to read every session. Anything that can be",
|
|
5896
|
-
"looked up when needed belongs in a note instead.",
|
|
5897
|
-
"",
|
|
5898
|
-
"- "
|
|
5899
|
-
].join("\n")
|
|
5900
|
-
),
|
|
5901
|
-
"01_BASE/USER.md": base(
|
|
5902
|
-
"user",
|
|
5903
|
-
"About me",
|
|
5904
|
-
"Who I am and what context I work in.",
|
|
5905
|
-
[
|
|
5906
|
-
"Background, role, what you are working on, what an agent should assume",
|
|
5907
|
-
"you already know.",
|
|
5908
|
-
"",
|
|
5909
|
-
"- "
|
|
5910
|
-
].join("\n")
|
|
5911
|
-
),
|
|
5912
|
-
"01_BASE/PREFERENCES.md": base(
|
|
5913
|
-
"preferences",
|
|
5914
|
-
"Preferences",
|
|
5915
|
-
"How I like answers, code and decisions handled.",
|
|
5916
|
-
["Style, verbosity, how much to check in, how you like code written.", "", "- "].join("\n")
|
|
5917
|
-
),
|
|
5918
|
-
"01_BASE/BOUNDARIES.md": base(
|
|
5919
|
-
"boundaries",
|
|
5920
|
-
"Boundaries",
|
|
5921
|
-
"What an agent must not do without asking me first.",
|
|
5922
|
-
[
|
|
5923
|
-
"Privacy, spending, anything irreversible, anything that leaves this",
|
|
5924
|
-
"machine. Be specific \u2014 a boundary that has to be interpreted is one",
|
|
5925
|
-
"that will be interpreted generously.",
|
|
5926
|
-
"",
|
|
5927
|
-
"Storage boundaries belong here too: topics you never want written into",
|
|
5928
|
-
"this brain \u2014 health, beliefs, relationships, anything you name. An",
|
|
5929
|
-
"agent checks this file before it records anything.",
|
|
5930
|
-
"",
|
|
5931
|
-
"- "
|
|
5932
|
-
].join("\n")
|
|
5933
|
-
),
|
|
5934
|
-
"01_BASE/TOOLING.md": base(
|
|
5935
|
-
"tooling",
|
|
5936
|
-
"Tooling",
|
|
5937
|
-
"The tools, machines and commands I actually use.",
|
|
5938
|
-
[
|
|
5939
|
-
"Editors, languages, deploy commands, where things run. Enough that an",
|
|
5940
|
-
"agent does not have to guess or discover it again each time.",
|
|
5941
|
-
"",
|
|
5942
|
-
"- "
|
|
5943
|
-
].join("\n")
|
|
5944
|
-
)
|
|
6554
|
+
// The five base files come from the engine, which the hosted server reads
|
|
6555
|
+
// too — one copy of what a starter template is (`base-templates.ts`).
|
|
6556
|
+
...BASE_TEMPLATES
|
|
5945
6557
|
};
|
|
5946
6558
|
}
|
|
5947
6559
|
var OBSERVATIONS_DIR2, FOLDERS;
|
|
@@ -6043,8 +6655,8 @@ import { join as join17 } from "path";
|
|
|
6043
6655
|
import { Command as Command13 } from "commander";
|
|
6044
6656
|
import prompts2 from "prompts";
|
|
6045
6657
|
function sameBasename(all, entry) {
|
|
6046
|
-
const
|
|
6047
|
-
return all.filter((p) => (p.split("/").pop() ?? "").normalize("NFC").toLowerCase() ===
|
|
6658
|
+
const base2 = (entry.split("/").pop() ?? entry).normalize("NFC").toLowerCase();
|
|
6659
|
+
return all.filter((p) => (p.split("/").pop() ?? "").normalize("NFC").toLowerCase() === base2);
|
|
6048
6660
|
}
|
|
6049
6661
|
function spreadAcrossFolders(rows, max) {
|
|
6050
6662
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6470,23 +7082,23 @@ var init_open_browser = __esm({
|
|
|
6470
7082
|
});
|
|
6471
7083
|
|
|
6472
7084
|
// src/commands/login.ts
|
|
6473
|
-
import { Command as
|
|
7085
|
+
import { Command as Command21 } from "commander";
|
|
6474
7086
|
function loginCommand() {
|
|
6475
|
-
return new
|
|
7087
|
+
return new Command21("login").description("sign in to a hosted brain").option(
|
|
6476
7088
|
"--emit-only",
|
|
6477
7089
|
"start the sign-in, open the browser, and return immediately \u2014 THE path an agent should use on a user\u2019s behalf; the next command claims the token automatically. Prefer this over the plain form, whose wait for a human click will outlast an agent\u2019s command timeout"
|
|
6478
7090
|
).option("--no-browser", "do not try to open a browser \u2014 just print the link").option("--print-url", "print the approval link even when a browser opened").addHelpText(
|
|
6479
7091
|
"after",
|
|
6480
7092
|
"\nAgents: run `login --emit-only`, then SHOW the code in your reply. The\napproval page asks the user to check it against what you showed them,\nand they cannot if you never showed it. Do not ask the user to run a\nlogin command themselves."
|
|
6481
7093
|
).action(async (opts) => {
|
|
6482
|
-
const
|
|
6483
|
-
const already = await currentCredentials(
|
|
7094
|
+
const base2 = apiUrl();
|
|
7095
|
+
const already = await currentCredentials(base2);
|
|
6484
7096
|
if (already) {
|
|
6485
7097
|
printDual({
|
|
6486
7098
|
json: {
|
|
6487
7099
|
already_signed_in: true,
|
|
6488
7100
|
account: { id: already.account_id, display_name: already.display_name },
|
|
6489
|
-
api_url:
|
|
7101
|
+
api_url: base2
|
|
6490
7102
|
},
|
|
6491
7103
|
human: () => {
|
|
6492
7104
|
printHuman(
|
|
@@ -6500,11 +7112,11 @@ function loginCommand() {
|
|
|
6500
7112
|
const { pending, opened } = await beginSignIn({ browser: opts.browser !== false });
|
|
6501
7113
|
const showUrl = !opened || opts.printUrl === true;
|
|
6502
7114
|
if (opts.emitOnly) {
|
|
6503
|
-
emit(pending, { base, opened, showUrl, blocking: false });
|
|
7115
|
+
emit(pending, { base: base2, opened, showUrl, blocking: false });
|
|
6504
7116
|
return;
|
|
6505
7117
|
}
|
|
6506
|
-
emit(pending, { base, opened, showUrl, blocking: true });
|
|
6507
|
-
await waitForApproval(pending,
|
|
7118
|
+
emit(pending, { base: base2, opened, showUrl, blocking: true });
|
|
7119
|
+
await waitForApproval(pending, base2);
|
|
6508
7120
|
});
|
|
6509
7121
|
}
|
|
6510
7122
|
async function beginSignIn(opts) {
|
|
@@ -6514,32 +7126,32 @@ async function beginSignIn(opts) {
|
|
|
6514
7126
|
return { pending, opened };
|
|
6515
7127
|
}
|
|
6516
7128
|
async function signInSummary() {
|
|
6517
|
-
const
|
|
7129
|
+
const base2 = apiUrl();
|
|
6518
7130
|
const stored = await readCredentials();
|
|
6519
7131
|
const pending = await pendingOrNull();
|
|
6520
7132
|
return {
|
|
6521
|
-
signedIn: stored !== null && stored.api_url ===
|
|
6522
|
-
displayName: stored?.api_url ===
|
|
6523
|
-
pendingUserCode: pending?.api_url ===
|
|
6524
|
-
apiUrl:
|
|
7133
|
+
signedIn: stored !== null && stored.api_url === base2,
|
|
7134
|
+
displayName: stored?.api_url === base2 ? stored?.display_name ?? null : null,
|
|
7135
|
+
pendingUserCode: pending?.api_url === base2 ? pending.user_code : null,
|
|
7136
|
+
apiUrl: base2
|
|
6525
7137
|
};
|
|
6526
7138
|
}
|
|
6527
|
-
async function startPairing(
|
|
7139
|
+
async function startPairing(base2) {
|
|
6528
7140
|
const res = await apiRequest({
|
|
6529
7141
|
method: "POST",
|
|
6530
7142
|
path: "/v1/auth/device/code",
|
|
6531
|
-
baseUrl:
|
|
7143
|
+
baseUrl: base2
|
|
6532
7144
|
});
|
|
6533
7145
|
const { device_code, user_code, verification_url, expires_in } = res.body ?? {};
|
|
6534
7146
|
if (!device_code || !user_code || !verification_url) {
|
|
6535
7147
|
throw memgError(
|
|
6536
7148
|
"server_refused",
|
|
6537
7149
|
"the brain server did not return a usable sign-in code",
|
|
6538
|
-
`Nothing is stored. Check that MEMGINEERING_API_URL points at a memgineering brain server \u2014 it is currently ${
|
|
7150
|
+
`Nothing is stored. Check that MEMGINEERING_API_URL points at a memgineering brain server \u2014 it is currently ${base2}.`
|
|
6539
7151
|
);
|
|
6540
7152
|
}
|
|
6541
7153
|
return {
|
|
6542
|
-
api_url:
|
|
7154
|
+
api_url: base2,
|
|
6543
7155
|
device_code,
|
|
6544
7156
|
user_code,
|
|
6545
7157
|
verification_url,
|
|
@@ -6590,7 +7202,7 @@ function emit(pending, ctx) {
|
|
|
6590
7202
|
}
|
|
6591
7203
|
});
|
|
6592
7204
|
}
|
|
6593
|
-
async function waitForApproval(pending,
|
|
7205
|
+
async function waitForApproval(pending, base2) {
|
|
6594
7206
|
const deadline = Date.now() + BLOCKING_TIMEOUT_MS;
|
|
6595
7207
|
progress("Waiting for approval");
|
|
6596
7208
|
for (; ; ) {
|
|
@@ -6604,7 +7216,7 @@ async function waitForApproval(pending, base) {
|
|
|
6604
7216
|
await sleep(5e3);
|
|
6605
7217
|
let claimed;
|
|
6606
7218
|
try {
|
|
6607
|
-
claimed = await claimOnce(pending,
|
|
7219
|
+
claimed = await claimOnce(pending, base2);
|
|
6608
7220
|
} catch (err) {
|
|
6609
7221
|
if (err instanceof MemgError && err.code === "server_unreachable") continue;
|
|
6610
7222
|
throw err;
|
|
@@ -6614,7 +7226,7 @@ async function waitForApproval(pending, base) {
|
|
|
6614
7226
|
json: {
|
|
6615
7227
|
signed_in: true,
|
|
6616
7228
|
account: { id: claimed.account_id, display_name: claimed.display_name },
|
|
6617
|
-
api_url:
|
|
7229
|
+
api_url: base2,
|
|
6618
7230
|
registry: claimed.registry ? { username: claimed.registry.username, api_url: claimed.registry.api_url } : null
|
|
6619
7231
|
},
|
|
6620
7232
|
human: () => {
|
|
@@ -6667,9 +7279,9 @@ var push_exports = {};
|
|
|
6667
7279
|
__export(push_exports, {
|
|
6668
7280
|
pushCommand: () => pushCommand
|
|
6669
7281
|
});
|
|
6670
|
-
import { Command as
|
|
7282
|
+
import { Command as Command36 } from "commander";
|
|
6671
7283
|
function pushCommand() {
|
|
6672
|
-
return new
|
|
7284
|
+
return new Command36("push").description("upload the brain on this machine to your hosted one").option("--vault <path>", "which local brain to upload").option("--brain <name>", "the hosted brain to upload into (created if it does not exist)").option("--dry-run", "list what would be uploaded and send nothing").action(async (opts) => {
|
|
6673
7285
|
const { brain: local } = await resolveBrain(opts.vault);
|
|
6674
7286
|
const adapter = await openBrainOrExplain(local.root);
|
|
6675
7287
|
const scan = await adapter.scan();
|
|
@@ -6822,28 +7434,28 @@ Nothing was deleted from \`${local.root}\` \u2014
|
|
|
6822
7434
|
});
|
|
6823
7435
|
}
|
|
6824
7436
|
async function resolveDestination(named, root) {
|
|
6825
|
-
const
|
|
6826
|
-
const credentials = await requireCredentials(
|
|
7437
|
+
const base2 = apiUrl();
|
|
7438
|
+
const credentials = await requireCredentials(base2);
|
|
6827
7439
|
const config = await loadConfig();
|
|
6828
|
-
const brains = await listCloudBrains(credentials.token,
|
|
7440
|
+
const brains = await listCloudBrains(credentials.token, base2);
|
|
6829
7441
|
const pick = (summary) => ({
|
|
6830
7442
|
brainId: summary.id,
|
|
6831
7443
|
name: summary.name,
|
|
6832
7444
|
token: credentials.token,
|
|
6833
|
-
apiUrl:
|
|
7445
|
+
apiUrl: base2
|
|
6834
7446
|
});
|
|
6835
7447
|
if (named) {
|
|
6836
7448
|
const found = brains.find((b) => b.name === named || b.id === named);
|
|
6837
|
-
return pick(found ?? await createCloudBrain(credentials.token,
|
|
7449
|
+
return pick(found ?? await createCloudBrain(credentials.token, base2, named));
|
|
6838
7450
|
}
|
|
6839
7451
|
const current = config.brain.cloud;
|
|
6840
|
-
if (current && current.api_url ===
|
|
7452
|
+
if (current && current.api_url === base2) {
|
|
6841
7453
|
const found = brains.find((b) => b.id === current.id);
|
|
6842
7454
|
if (found) return pick(found);
|
|
6843
7455
|
}
|
|
6844
7456
|
const fallbackName = root.split("/").filter(Boolean).pop() ?? "brain";
|
|
6845
7457
|
const existing = brains.find((b) => b.name === fallbackName);
|
|
6846
|
-
return pick(existing ?? await createCloudBrain(credentials.token,
|
|
7458
|
+
return pick(existing ?? await createCloudBrain(credentials.token, base2, fallbackName));
|
|
6847
7459
|
}
|
|
6848
7460
|
var HELD_BACK_WORDING, printHeldBack2;
|
|
6849
7461
|
var init_push = __esm({
|
|
@@ -7198,33 +7810,129 @@ var init_setup_signin = __esm({
|
|
|
7198
7810
|
}
|
|
7199
7811
|
});
|
|
7200
7812
|
|
|
7201
|
-
// src/
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7813
|
+
// src/lib/provenance.ts
|
|
7814
|
+
import { execFile } from "child_process";
|
|
7815
|
+
import { realpath as realpath3 } from "fs/promises";
|
|
7816
|
+
import { dirname as dirname8, join as join23, resolve as resolve14 } from "path";
|
|
7817
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7818
|
+
import { promisify } from "util";
|
|
7819
|
+
async function detectProvenance(selfPath) {
|
|
7820
|
+
const entry = selfPath ?? fileURLToPath2(import.meta.url);
|
|
7821
|
+
const selfDir = await realpath3(dirname8(entry)).catch(() => dirname8(entry));
|
|
7822
|
+
if (/[\\/]_npx[\\/]|[\\/]\.npm[\\/]_cacache[\\/]/.test(selfDir)) {
|
|
7823
|
+
return {
|
|
7824
|
+
kind: "npx",
|
|
7825
|
+
selfDir,
|
|
7826
|
+
prefix: null,
|
|
7827
|
+
command: "npm i -g memgineering@latest",
|
|
7828
|
+
reason: "this is a one-off npx run, not an installed copy"
|
|
7829
|
+
};
|
|
7830
|
+
}
|
|
7831
|
+
for (const manager of MANAGERS) {
|
|
7832
|
+
const prefix = await prefixOf(manager.bin, manager.prefixArgs);
|
|
7833
|
+
if (prefix === null) continue;
|
|
7834
|
+
if (isInside2(selfDir, manager.moduleRoot(prefix))) {
|
|
7835
|
+
return {
|
|
7836
|
+
kind: manager.kind,
|
|
7837
|
+
selfDir,
|
|
7838
|
+
prefix,
|
|
7839
|
+
command: manager.install,
|
|
7840
|
+
reason: null
|
|
7841
|
+
};
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
const local = selfDir.includes(`${"node_modules"}`);
|
|
7845
|
+
return {
|
|
7846
|
+
kind: local ? "local" : "unknown",
|
|
7847
|
+
selfDir,
|
|
7848
|
+
prefix: null,
|
|
7849
|
+
command: "npm i -g memgineering@latest",
|
|
7850
|
+
reason: local ? "this copy is a project dependency, not a global install" : "no package manager on PATH owns the directory this copy is in \u2014 under nvm, asdf, Volta or mise that usually means a different Node version is active than the one it was installed with"
|
|
7851
|
+
};
|
|
7852
|
+
}
|
|
7853
|
+
function canSelfUpdate(p) {
|
|
7854
|
+
return p.kind === "npm" || p.kind === "pnpm" || p.kind === "bun";
|
|
7855
|
+
}
|
|
7856
|
+
async function prefixOf(bin, args) {
|
|
7857
|
+
try {
|
|
7858
|
+
const { stdout } = await run(bin, args, { timeout: 5e3 });
|
|
7859
|
+
const value = stdout.trim();
|
|
7860
|
+
return value === "" ? null : await realpath3(value).catch(() => value);
|
|
7861
|
+
} catch {
|
|
7862
|
+
return null;
|
|
7863
|
+
}
|
|
7864
|
+
}
|
|
7865
|
+
function isInside2(child, parent) {
|
|
7866
|
+
const c2 = resolve14(child);
|
|
7867
|
+
const p = resolve14(parent);
|
|
7868
|
+
return c2 === p || c2.startsWith(p.endsWith("/") ? p : `${p}/`);
|
|
7869
|
+
}
|
|
7870
|
+
async function unreachableByName(selfPath) {
|
|
7871
|
+
const entry = selfPath ?? fileURLToPath2(import.meta.url);
|
|
7872
|
+
const selfDir = await realpath3(dirname8(entry)).catch(() => dirname8(entry));
|
|
7873
|
+
if (!/[\\/](node_modules|_npx|_cacache)[\\/]/.test(`${selfDir}/`)) return null;
|
|
7874
|
+
const p = await detectProvenance(entry);
|
|
7875
|
+
return p.kind === "local" || p.kind === "npx" ? p.reason : null;
|
|
7876
|
+
}
|
|
7877
|
+
var run, MANAGERS;
|
|
7878
|
+
var init_provenance = __esm({
|
|
7879
|
+
"src/lib/provenance.ts"() {
|
|
7880
|
+
"use strict";
|
|
7881
|
+
run = promisify(execFile);
|
|
7882
|
+
MANAGERS = [
|
|
7883
|
+
{
|
|
7884
|
+
kind: "npm",
|
|
7885
|
+
bin: "npm",
|
|
7886
|
+
prefixArgs: ["prefix", "-g"],
|
|
7887
|
+
moduleRoot: (prefix) => process.platform === "win32" ? join23(prefix, "node_modules") : join23(prefix, "lib", "node_modules"),
|
|
7888
|
+
install: "npm i -g memgineering@latest"
|
|
7889
|
+
},
|
|
7890
|
+
{
|
|
7891
|
+
kind: "pnpm",
|
|
7892
|
+
bin: "pnpm",
|
|
7893
|
+
// `pnpm root -g` already prints the global node_modules.
|
|
7894
|
+
prefixArgs: ["root", "-g"],
|
|
7895
|
+
moduleRoot: (root) => root,
|
|
7896
|
+
install: "pnpm add -g memgineering@latest"
|
|
7897
|
+
},
|
|
7898
|
+
{
|
|
7899
|
+
kind: "bun",
|
|
7900
|
+
bin: "bun",
|
|
7901
|
+
prefixArgs: ["pm", "bin", "-g"],
|
|
7902
|
+
moduleRoot: (bin) => bin,
|
|
7903
|
+
install: "bun add -g memgineering"
|
|
7904
|
+
}
|
|
7905
|
+
];
|
|
7906
|
+
}
|
|
7907
|
+
});
|
|
7908
|
+
|
|
7909
|
+
// src/commands/setup-cloud.ts
|
|
7910
|
+
var setup_cloud_exports = {};
|
|
7911
|
+
__export(setup_cloud_exports, {
|
|
7912
|
+
SETUP_CHILD_ENV: () => SETUP_CHILD_ENV,
|
|
7913
|
+
applyAnswers: () => applyAnswers,
|
|
7914
|
+
readChildToken: () => readChildToken,
|
|
7915
|
+
runSetupCloud: () => runSetupCloud,
|
|
7916
|
+
runSetupCloudChild: () => runSetupCloudChild,
|
|
7917
|
+
waitAndApply: () => waitAndApply
|
|
7918
|
+
});
|
|
7919
|
+
import { spawn as spawn2 } from "child_process";
|
|
7920
|
+
import { readdir as readdir7, stat as stat6 } from "fs/promises";
|
|
7921
|
+
import { homedir as homedir5 } from "os";
|
|
7922
|
+
import { join as join24 } from "path";
|
|
7923
|
+
async function report2(setupToken, state, detail) {
|
|
7924
|
+
try {
|
|
7925
|
+
await apiRequest({
|
|
7926
|
+
method: "POST",
|
|
7927
|
+
path: "/v1/setup/session/report",
|
|
7928
|
+
body: { setup_token: setupToken, state, ...detail === void 0 ? {} : { detail } }
|
|
7929
|
+
});
|
|
7930
|
+
} catch {
|
|
7931
|
+
}
|
|
7932
|
+
}
|
|
7933
|
+
async function folderCandidates() {
|
|
7226
7934
|
const home = homedir5();
|
|
7227
|
-
const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) =>
|
|
7935
|
+
const roots = ["Documents", "notes", "Notes", "Obsidian", "vault"].map((d) => join24(home, d));
|
|
7228
7936
|
const found = [];
|
|
7229
7937
|
const countNotes = async (dir) => {
|
|
7230
7938
|
let count = 0;
|
|
@@ -7233,7 +7941,7 @@ async function folderCandidates() {
|
|
|
7233
7941
|
if (entry.name.startsWith(".")) continue;
|
|
7234
7942
|
if (entry.isFile() && /\.(md|markdown)$/i.test(entry.name)) count += 1;
|
|
7235
7943
|
else if (entry.isDirectory()) {
|
|
7236
|
-
const inner = await readdir7(
|
|
7944
|
+
const inner = await readdir7(join24(dir, entry.name)).catch(() => []);
|
|
7237
7945
|
count += inner.filter((n) => /\.(md|markdown)$/i.test(n)).length;
|
|
7238
7946
|
}
|
|
7239
7947
|
if (count > 999) break;
|
|
@@ -7488,18 +8196,18 @@ var init_setup_cloud = __esm({
|
|
|
7488
8196
|
// src/commands/setup.ts
|
|
7489
8197
|
import { createHash as createHash9 } from "crypto";
|
|
7490
8198
|
import { access as access2, mkdir as mkdir15, readFile as readFile24 } from "fs/promises";
|
|
7491
|
-
import { dirname as
|
|
7492
|
-
import { fileURLToPath as
|
|
7493
|
-
import { Command as
|
|
8199
|
+
import { dirname as dirname9, join as join25 } from "path";
|
|
8200
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
8201
|
+
import { Command as Command40 } from "commander";
|
|
7494
8202
|
import prompts5 from "prompts";
|
|
7495
8203
|
import { homedir as homedir6 } from "os";
|
|
7496
8204
|
function setupCommand() {
|
|
7497
|
-
return new
|
|
8205
|
+
return new Command40("setup").description("install into your agents, and point at a brain (--dry-run to only report)").option("--agent", "non-interactive: take every answer from flags").option("--human", "interactive: choose from a list").option("--web", "open a screen in the browser and answer it there").option("--print-url", "with --web: print the URL instead of opening a browser").option(
|
|
7498
8206
|
"--wait",
|
|
7499
8207
|
"with --web: block until the screen is finished, even with nobody watching the terminal"
|
|
7500
8208
|
).option(
|
|
7501
8209
|
"--tools <list>",
|
|
7502
|
-
"comma-separated: claude, codex, grok, gemini (default: all detected)"
|
|
8210
|
+
"comma-separated: claude, codex, grok, gemini (default: all detected) \u2014 chat apps: see below"
|
|
7503
8211
|
).option("--auto-update <choice>", "on | off (default: on, recommended)").option(
|
|
7504
8212
|
"--error-telemetry <choice>",
|
|
7505
8213
|
"on | off \u2014 anonymized crash reports; unset leaves it undecided (off) so a person can be asked later"
|
|
@@ -7509,13 +8217,22 @@ function setupCommand() {
|
|
|
7509
8217
|
).option("--login", "start a hosted-account sign-in as the first step").option("--no-browser", "with --login: print the approval link instead of opening a browser").option(
|
|
7510
8218
|
"--no-login",
|
|
7511
8219
|
"skip the sign-in step \u2014 everything installed here works without an account"
|
|
7512
|
-
).option("--dry-run", "report what would change, write nothing").option("--silent", "only speak if something changed").
|
|
8220
|
+
).option("--dry-run", "report what would change, write nothing").option("--silent", "only speak if something changed").addHelpText(
|
|
8221
|
+
"after",
|
|
8222
|
+
[
|
|
8223
|
+
"",
|
|
8224
|
+
"Chat apps with no shell \u2014 ChatGPT, Claude.ai, Claude Desktop, Cowork \u2014 need no",
|
|
8225
|
+
`setup: add ${HOSTED_CONNECTOR_URL} as a custom connector in the app and`,
|
|
8226
|
+
"sign in. It reads and writes the brain on your account (not a folder on this",
|
|
8227
|
+
"machine)."
|
|
8228
|
+
].join("\n")
|
|
8229
|
+
).action(async (opts) => {
|
|
7513
8230
|
const detected = await detectTools();
|
|
7514
8231
|
if (detected.length === 0) {
|
|
7515
8232
|
throw memgError(
|
|
7516
8233
|
"invalid_input",
|
|
7517
8234
|
"no supported agent tools found on this machine",
|
|
7518
|
-
|
|
8235
|
+
`memgineering installs into ${SUPPORTED_LIST}. Each is found by the folder it creates the first time it runs, so a tool installed but never opened is not found yet \u2014 open it once, then run setup again. If none is installed, install one first.`
|
|
7519
8236
|
);
|
|
7520
8237
|
}
|
|
7521
8238
|
if (opts.web) {
|
|
@@ -7545,11 +8262,13 @@ function setupCommand() {
|
|
|
7545
8262
|
const choice = mode.kind === "human" ? await askHuman(detected, opts) : fromFlags(detected, opts);
|
|
7546
8263
|
const defaultsTaken = !mode.chosen ? [
|
|
7547
8264
|
...opts.autoUpdate === void 0 ? ["auto-update: on"] : [],
|
|
7548
|
-
//
|
|
7549
|
-
// summaries at session start,
|
|
7550
|
-
// edit
|
|
7551
|
-
// was decided on their behalf.
|
|
7552
|
-
|
|
8265
|
+
// Every callback this flag arms, named — on Claude Code and Codex
|
|
8266
|
+
// that is two (note summaries at session start, the user's own
|
|
8267
|
+
// rules before an edit), and saying only the first would be
|
|
8268
|
+
// reporting half of what was decided on their behalf. On Grok it
|
|
8269
|
+
// is one, at the end of a turn, and naming the other two there
|
|
8270
|
+
// reported callbacks that were never installed.
|
|
8271
|
+
...opts.hook === void 0 && choice?.hook ? hooksFor(choice.tools).map((h) => `${h.what}: on`) : []
|
|
7553
8272
|
] : [];
|
|
7554
8273
|
if (choice === null) {
|
|
7555
8274
|
printHuman(c.yellow("Cancelled \u2014 nothing was changed."));
|
|
@@ -7562,9 +8281,14 @@ function setupCommand() {
|
|
|
7562
8281
|
const changed = results.filter((r) => r.result !== "unchanged" && r.result !== "refused");
|
|
7563
8282
|
const brains = (await loadConfig()).brain.brains;
|
|
7564
8283
|
const brainStep = await runBrainStep(signIn, opts.dryRun === true);
|
|
8284
|
+
const notByName = opts.dryRun !== true ? await unreachableByName() : null;
|
|
8285
|
+
const hooksLanded = choice.hook && results.some(
|
|
8286
|
+
(r) => Object.values(HOOK_WHAT).includes(r.what) && r.result !== "refused" && r.result !== "removed"
|
|
8287
|
+
);
|
|
7565
8288
|
const hints = [
|
|
7566
8289
|
...refusals.length > 0 ? [REFUSAL_HINT] : [],
|
|
7567
8290
|
...duplicated.length > 0 ? [DUPLICATE_HINT] : [],
|
|
8291
|
+
...notByName !== null ? [globalInstallHint(notByName, hooksLanded)] : [],
|
|
7568
8292
|
/*
|
|
7569
8293
|
* Local mode only, and that condition is the point of it.
|
|
7570
8294
|
*
|
|
@@ -7603,6 +8327,10 @@ function setupCommand() {
|
|
|
7603
8327
|
*/
|
|
7604
8328
|
...agentHome() !== homedir6() ? { agent_home: agentHome() } : {},
|
|
7605
8329
|
tools: choice.tools.map((t) => t.id),
|
|
8330
|
+
// `tools` is what was asked for; this is where the guide actually
|
|
8331
|
+
// stands after the run. They differ exactly when a guide was refused.
|
|
8332
|
+
registered: registeredTools(choice.tools, results).map((t) => t.id),
|
|
8333
|
+
...notByName !== null ? { global_install_needed: notByName } : {},
|
|
7606
8334
|
auto_update: choice.autoUpdate,
|
|
7607
8335
|
error_telemetry: choice.errorTelemetry,
|
|
7608
8336
|
hook: choice.hook,
|
|
@@ -7652,7 +8380,7 @@ function setupCommand() {
|
|
|
7652
8380
|
}
|
|
7653
8381
|
printSignInStep(signIn);
|
|
7654
8382
|
printBrainStep(brainStep);
|
|
7655
|
-
printHuman(
|
|
8383
|
+
printHuman(`${registrationLine(choice.tools, results)}
|
|
7656
8384
|
`);
|
|
7657
8385
|
const edited = results.filter((r) => r.edited === true);
|
|
7658
8386
|
if (edited.length > 0) {
|
|
@@ -7669,14 +8397,35 @@ function setupCommand() {
|
|
|
7669
8397
|
if (choice.errorTelemetry !== "undecided") {
|
|
7670
8398
|
printHuman(c.gray(` error-telemetry: ${choice.errorTelemetry}`));
|
|
7671
8399
|
}
|
|
7672
|
-
|
|
7673
|
-
|
|
8400
|
+
const armed = (what) => choice.tools.filter(
|
|
8401
|
+
(t) => results.some(
|
|
8402
|
+
(r) => r.tool === t.label && r.what === what && r.result !== "refused" && r.result !== "removed"
|
|
8403
|
+
)
|
|
8404
|
+
);
|
|
8405
|
+
const atStart = choice.hook ? armed(HOOK_WHAT.sessionStart) : [];
|
|
8406
|
+
const atStop = choice.hook ? armed(HOOK_WHAT.endOfTurn) : [];
|
|
8407
|
+
const once = notByName !== null ? ", once memgineering is installed globally (below)" : "";
|
|
8408
|
+
if (atStop.length > 0) {
|
|
8409
|
+
printHuman(
|
|
8410
|
+
c.gray(
|
|
8411
|
+
` your rules are shown at the end of each turn on ${atStop.map((t) => t.label).join(", ")}${once} \u2014
|
|
8412
|
+
nothing there reaches the model earlier, so a turn that breaks one is corrected
|
|
8413
|
+
in the next.`
|
|
8414
|
+
)
|
|
8415
|
+
);
|
|
8416
|
+
}
|
|
8417
|
+
if (atStart.length > 0) {
|
|
8418
|
+
printHuman(
|
|
8419
|
+
c.gray(
|
|
8420
|
+
` resurface runs at session start on ${atStart.map((t) => t.label).join(", ")}${once}`
|
|
8421
|
+
)
|
|
8422
|
+
);
|
|
7674
8423
|
printHuman(
|
|
7675
8424
|
c.gray(
|
|
7676
|
-
" \u2192 this puts a few note summaries into every new session, unasked.\n Anything you would not want surfacing there: `memgineering exclude <path
|
|
8425
|
+
" \u2192 this puts a few note summaries into every new session, unasked.\n Anything you would not want surfacing there: `memgineering exclude <path>`."
|
|
7677
8426
|
)
|
|
7678
8427
|
);
|
|
7679
|
-
if (
|
|
8428
|
+
if (atStart.some((t) => t.id === "codex")) {
|
|
7680
8429
|
printHuman(
|
|
7681
8430
|
c.gray(
|
|
7682
8431
|
" \u2192 Codex will ask you to review these hooks once, next time it\n starts: press `t` to trust them. Until you do, neither one runs,\n and an upgrade that changes a hook asks again."
|
|
@@ -7684,6 +8433,19 @@ function setupCommand() {
|
|
|
7684
8433
|
);
|
|
7685
8434
|
}
|
|
7686
8435
|
}
|
|
8436
|
+
if (atStart.length > 0 || atStop.length > 0) {
|
|
8437
|
+
printHuman(
|
|
8438
|
+
c.gray(" Turn the hooks back off with `memgineering setup --agent --hook off`.")
|
|
8439
|
+
);
|
|
8440
|
+
}
|
|
8441
|
+
if (notByName !== null) {
|
|
8442
|
+
printHuman(
|
|
8443
|
+
c.yellow(
|
|
8444
|
+
`
|
|
8445
|
+
${globalInstallHint(notByName, hooksLanded).split("\n").join("\n ")}`
|
|
8446
|
+
)
|
|
8447
|
+
);
|
|
8448
|
+
}
|
|
7687
8449
|
const removed = results.filter((r) => r.result === "removed");
|
|
7688
8450
|
if (!choice.hook && removed.length > 0) {
|
|
7689
8451
|
printHuman(c.gray(` removed: ${label(removed)}`));
|
|
@@ -7716,7 +8478,7 @@ function setupCommand() {
|
|
|
7716
8478
|
const codexHooksPending = choice.hook && choice.tools.some((t) => t.id === "codex");
|
|
7717
8479
|
printHuman(
|
|
7718
8480
|
c.bold(
|
|
7719
|
-
codexHooksPending ? "\n Ready to use now \u2014 recall and remember work; the hooks start once\n Codex has asked you about them." : "\n Ready to use now \u2014 recall, remember and the hooks all work."
|
|
8481
|
+
notByName !== null ? hooksLanded ? "\n Installed \u2014 the hooks, and an agent following the guide, start working\n once memgineering is installed globally." : "\n Installed \u2014 an agent following the guide starts working once\n memgineering is installed globally." : codexHooksPending ? "\n Ready to use now \u2014 recall and remember work; the hooks start once\n Codex has asked you about them." : "\n Ready to use now \u2014 recall, remember and the hooks all work."
|
|
7720
8482
|
)
|
|
7721
8483
|
);
|
|
7722
8484
|
printHuman(
|
|
@@ -7773,6 +8535,11 @@ async function applySetup(choice, dryRun) {
|
|
|
7773
8535
|
...cfg.system,
|
|
7774
8536
|
autoUpdate: choice.autoUpdate,
|
|
7775
8537
|
installed,
|
|
8538
|
+
// Stamped whether or not anything was rewritten. A run that finds every
|
|
8539
|
+
// file already current is exactly the run that must clear the notice —
|
|
8540
|
+
// recording only on a change would leave `refresh_guidance` firing
|
|
8541
|
+
// forever on the release where the guidance happened not to move.
|
|
8542
|
+
guidanceVersion: VERSION,
|
|
7776
8543
|
// 'undecided' means nobody answered THIS run — it must not erase an
|
|
7777
8544
|
// answer somebody gave in an earlier one.
|
|
7778
8545
|
...choice.errorTelemetry !== "undecided" ? { errorTelemetry: choice.errorTelemetry } : {}
|
|
@@ -7786,13 +8553,13 @@ async function applySetup(choice, dryRun) {
|
|
|
7786
8553
|
};
|
|
7787
8554
|
const keyOf = (path) => path.startsWith(agentHome()) ? path.slice(agentHome().length + 1) : path;
|
|
7788
8555
|
try {
|
|
7789
|
-
return await
|
|
8556
|
+
return await write2();
|
|
7790
8557
|
} finally {
|
|
7791
8558
|
await persist();
|
|
7792
8559
|
}
|
|
7793
|
-
async function
|
|
8560
|
+
async function write2() {
|
|
7794
8561
|
for (const tool of choice.tools) {
|
|
7795
|
-
const hubPath =
|
|
8562
|
+
const hubPath = join25(agentHome(), tool.hubFile);
|
|
7796
8563
|
const written = await injectBlock(
|
|
7797
8564
|
hubPath,
|
|
7798
8565
|
hub + tailFor(tool, choice.hook),
|
|
@@ -7807,8 +8574,8 @@ async function applySetup(choice, dryRun) {
|
|
|
7807
8574
|
});
|
|
7808
8575
|
if (!dryRun && written.after !== null) mark(hubPath, written.after);
|
|
7809
8576
|
for (const skill of BRAND.subSkills) {
|
|
7810
|
-
const content = await loadAsset(
|
|
7811
|
-
const path =
|
|
8577
|
+
const content = await loadAsset(join25(skill, "SKILL.md"));
|
|
8578
|
+
const path = join25(agentHome(), tool.skillsDir, skill, "SKILL.md");
|
|
7812
8579
|
const out = await writeIfDifferent(path, content, dryRun);
|
|
7813
8580
|
results.push({
|
|
7814
8581
|
tool: tool.label,
|
|
@@ -7853,6 +8620,34 @@ ${surfacedForYou && rulesForYou ? `Two moments matter here, and a hook covers ea
|
|
|
7853
8620
|
The skills named above are files at \`${tool.skillsDir}/memgineering-<name>/SKILL.md\`. Your host may list a skill by name with its description trimmed away, in which case the name is all you get \u2014 read the file rather than assuming there is nothing there.${skills}
|
|
7854
8621
|
`;
|
|
7855
8622
|
}
|
|
8623
|
+
function registeredTools(tools, results) {
|
|
8624
|
+
return tools.filter(
|
|
8625
|
+
(t) => results.some((r) => r.tool === t.label && r.what === "guide" && r.result !== "refused")
|
|
8626
|
+
);
|
|
8627
|
+
}
|
|
8628
|
+
function registrationLine(tools, results) {
|
|
8629
|
+
const registered = registeredTools(tools, results);
|
|
8630
|
+
const notRegistered = tools.filter((t) => !registered.includes(t));
|
|
8631
|
+
const skipped = (t) => results.filter((r) => r.tool === t.label && r.result === "refused").length;
|
|
8632
|
+
const names = (ts) => ts.map((t) => t.label).join(", ");
|
|
8633
|
+
const parts = [];
|
|
8634
|
+
if (registered.length > 0) {
|
|
8635
|
+
const partial = registered.filter((t) => skipped(t) > 0);
|
|
8636
|
+
parts.push(
|
|
8637
|
+
`Registered with ${names(registered)}` + (partial.length > 0 ? ` \u2014 but ${partial.map((t) => `${skipped(t)} file(s) for ${t.label}`).join(", ")} could not be written, listed below` : "")
|
|
8638
|
+
);
|
|
8639
|
+
}
|
|
8640
|
+
if (notRegistered.length > 0) {
|
|
8641
|
+
parts.push(
|
|
8642
|
+
`Not registered with ${names(notRegistered)} \u2014 its guide could not be written, listed below`
|
|
8643
|
+
);
|
|
8644
|
+
}
|
|
8645
|
+
return `${parts.join(". ")}.`;
|
|
8646
|
+
}
|
|
8647
|
+
function globalInstallHint(reason, hooks) {
|
|
8648
|
+
return `${hooks ? "The hooks and the guide call" : "The guide tells your agent to run"} \`memgineering\` by name, and ${reason} \u2014
|
|
8649
|
+
so nothing reaches it until a global install exists: \`npm i -g memgineering\`. Nothing needs re-running after that.`;
|
|
8650
|
+
}
|
|
7856
8651
|
async function resolveMode(opts) {
|
|
7857
8652
|
if (opts.agent) return { kind: "agent", chosen: true };
|
|
7858
8653
|
if (opts.human) {
|
|
@@ -7886,21 +8681,28 @@ async function detectTools() {
|
|
|
7886
8681
|
const home = agentHome();
|
|
7887
8682
|
const found = [];
|
|
7888
8683
|
for (const tool of TOOLS) {
|
|
7889
|
-
if (await exists2(
|
|
8684
|
+
if (await exists2(join25(home, tool.dir))) found.push(tool);
|
|
7890
8685
|
}
|
|
7891
8686
|
return found;
|
|
7892
8687
|
}
|
|
7893
8688
|
function fromFlags(detected, opts) {
|
|
7894
8689
|
const tools = opts.tools ? opts.tools.split(",").map((t) => t.trim().toLowerCase()).map((id) => {
|
|
7895
8690
|
const tool = detected.find((t) => t.id === id);
|
|
7896
|
-
if (
|
|
8691
|
+
if (tool) return tool;
|
|
8692
|
+
const detectedList = `Detected: ${detected.map((t) => t.id).join(", ") || "none"}. Omit --tools to use all of them.`;
|
|
8693
|
+
const known = TOOLS.find((t) => t.id === id);
|
|
8694
|
+
if (!known) {
|
|
7897
8695
|
throw memgError(
|
|
7898
8696
|
"invalid_input",
|
|
7899
|
-
`
|
|
7900
|
-
`
|
|
8697
|
+
`unknown tool: ${id}`,
|
|
8698
|
+
`Supported: ${TOOLS.map((t) => t.id).join(", ")}. ${detectedList}`
|
|
7901
8699
|
);
|
|
7902
8700
|
}
|
|
7903
|
-
|
|
8701
|
+
throw memgError(
|
|
8702
|
+
"invalid_input",
|
|
8703
|
+
`${known.label} not found on this machine: no ~/${known.dir} yet`,
|
|
8704
|
+
`${known.label} creates ~/${known.dir} the first time it runs. If it is installed, open it once, then run setup again. ${detectedList}`
|
|
8705
|
+
);
|
|
7904
8706
|
}) : detected;
|
|
7905
8707
|
return {
|
|
7906
8708
|
tools,
|
|
@@ -7978,7 +8780,7 @@ ${BRAND.hubMarkerEnd}`;
|
|
|
7978
8780
|
const existing = await readFile24(filePath, "utf8").catch(() => null);
|
|
7979
8781
|
if (existing === null) {
|
|
7980
8782
|
if (!dryRun) {
|
|
7981
|
-
await mkdir15(
|
|
8783
|
+
await mkdir15(dirname9(filePath), { recursive: true });
|
|
7982
8784
|
await writeThenRename(filePath, `${block}
|
|
7983
8785
|
`);
|
|
7984
8786
|
}
|
|
@@ -8022,13 +8824,18 @@ async function writeIfDifferent(filePath, content, dryRun) {
|
|
|
8022
8824
|
const after = fingerprint(content);
|
|
8023
8825
|
if (existing === content) return { outcome: "unchanged", before: existing, after };
|
|
8024
8826
|
if (!dryRun) {
|
|
8025
|
-
await mkdir15(
|
|
8827
|
+
await mkdir15(dirname9(filePath), { recursive: true });
|
|
8026
8828
|
await writeThenRename(filePath, content);
|
|
8027
8829
|
}
|
|
8028
8830
|
return { outcome: existing === null ? "injected" : "updated", before: existing, after };
|
|
8029
8831
|
}
|
|
8832
|
+
function hooksFor(tools) {
|
|
8833
|
+
return HOOKS.filter(
|
|
8834
|
+
(h) => tools.some((t) => t.hooks && (h.tools ?? DEFAULT_HOOK_TOOLS).includes(t.id))
|
|
8835
|
+
);
|
|
8836
|
+
}
|
|
8030
8837
|
async function installHook(spec, tool, dryRun) {
|
|
8031
|
-
const settingsPath =
|
|
8838
|
+
const settingsPath = join25(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
8032
8839
|
const raw = await readFile24(settingsPath, "utf8").catch(() => null);
|
|
8033
8840
|
let settings = {};
|
|
8034
8841
|
if (raw !== null) {
|
|
@@ -8071,14 +8878,14 @@ async function installHook(spec, tool, dryRun) {
|
|
|
8071
8878
|
}
|
|
8072
8879
|
const next = { ...settings, hooks: { ...hooks, [spec.event]: existingGroups } };
|
|
8073
8880
|
if (!dryRun) {
|
|
8074
|
-
await mkdir15(
|
|
8881
|
+
await mkdir15(dirname9(settingsPath), { recursive: true });
|
|
8075
8882
|
await writeThenRename(settingsPath, `${JSON.stringify(next, null, 2)}
|
|
8076
8883
|
`);
|
|
8077
8884
|
}
|
|
8078
8885
|
return raw === null ? "injected" : "updated";
|
|
8079
8886
|
}
|
|
8080
8887
|
async function removeHook(spec, tool, dryRun) {
|
|
8081
|
-
const settingsPath =
|
|
8888
|
+
const settingsPath = join25(agentHome(), ...(tool.hooksFile ?? "").split("/"));
|
|
8082
8889
|
const raw = await readFile24(settingsPath, "utf8").catch(() => null);
|
|
8083
8890
|
if (raw === null) return "unchanged";
|
|
8084
8891
|
let parsed;
|
|
@@ -8103,7 +8910,7 @@ async function removeHook(spec, tool, dryRun) {
|
|
|
8103
8910
|
return "removed";
|
|
8104
8911
|
}
|
|
8105
8912
|
async function loadAsset(relPath) {
|
|
8106
|
-
const raw = await readFile24(
|
|
8913
|
+
const raw = await readFile24(join25(await assetRoot(), relPath), "utf8");
|
|
8107
8914
|
if (relPath === BRAND.hubFileName && raw.startsWith("---")) {
|
|
8108
8915
|
const end = raw.indexOf("\n---", 4);
|
|
8109
8916
|
if (end !== -1) return raw.slice(end + 4).trimStart();
|
|
@@ -8111,14 +8918,14 @@ async function loadAsset(relPath) {
|
|
|
8111
8918
|
return raw;
|
|
8112
8919
|
}
|
|
8113
8920
|
async function assetRoot() {
|
|
8114
|
-
const here =
|
|
8115
|
-
const bundled =
|
|
8116
|
-
if (await exists2(
|
|
8921
|
+
const here = dirname9(fileURLToPath3(import.meta.url));
|
|
8922
|
+
const bundled = join25(here, "..", "assets");
|
|
8923
|
+
if (await exists2(join25(bundled, BRAND.hubFileName))) return bundled;
|
|
8117
8924
|
let dir = here;
|
|
8118
8925
|
for (; ; ) {
|
|
8119
|
-
const candidate =
|
|
8120
|
-
if (await exists2(
|
|
8121
|
-
const parent =
|
|
8926
|
+
const candidate = join25(dir, "packages", "skills");
|
|
8927
|
+
if (await exists2(join25(candidate, BRAND.hubFileName))) return candidate;
|
|
8928
|
+
const parent = dirname9(dir);
|
|
8122
8929
|
if (parent === dir) break;
|
|
8123
8930
|
dir = parent;
|
|
8124
8931
|
}
|
|
@@ -8136,7 +8943,7 @@ async function exists2(path) {
|
|
|
8136
8943
|
return false;
|
|
8137
8944
|
}
|
|
8138
8945
|
}
|
|
8139
|
-
var TOOLS, REFUSAL_HINT, DUPLICATE_HINT, HOOK_TIMEOUT_S, DEFAULT_HOOK_TOOLS, HOOKS;
|
|
8946
|
+
var TOOLS, HOSTED_CONNECTOR_URL, SUPPORTED_LIST, REFUSAL_HINT, DUPLICATE_HINT, HOOK_TIMEOUT_S, DEFAULT_HOOK_TOOLS, HOOK_WHAT, HOOKS;
|
|
8140
8947
|
var init_setup = __esm({
|
|
8141
8948
|
"src/commands/setup.ts"() {
|
|
8142
8949
|
"use strict";
|
|
@@ -8147,7 +8954,9 @@ var init_setup = __esm({
|
|
|
8147
8954
|
init_setup_brain();
|
|
8148
8955
|
init_setup_signin();
|
|
8149
8956
|
init_config();
|
|
8957
|
+
init_provenance();
|
|
8150
8958
|
init_ui();
|
|
8959
|
+
init_version();
|
|
8151
8960
|
TOOLS = [
|
|
8152
8961
|
{
|
|
8153
8962
|
id: "claude",
|
|
@@ -8215,20 +9024,27 @@ var init_setup = __esm({
|
|
|
8215
9024
|
hooksFile: ".grok/hooks/memgineering.json"
|
|
8216
9025
|
}
|
|
8217
9026
|
];
|
|
9027
|
+
HOSTED_CONNECTOR_URL = "https://mcp.memgineering.com/mcp";
|
|
9028
|
+
SUPPORTED_LIST = TOOLS.map((t) => `${t.label} (\`${t.id}\`, ~/${t.dir})`).join(", ");
|
|
8218
9029
|
REFUSAL_HINT = `Either a guide setup wrote is still there with its markers removed, or a settings file is not valid JSON. Everything else in this run was applied.
|
|
8219
9030
|
For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around that block, or delete the block, then run setup again.`;
|
|
8220
9031
|
DUPLICATE_HINT = "That file already looked like it held a memgineering guide with no markers around it, so the new one was appended below rather than replacing it.\nIf an older copy is now sitting above it, delete that copy \u2014 two guides means both are in every session. If those lines are your own writing, nothing is wrong.";
|
|
8221
9032
|
HOOK_TIMEOUT_S = Math.ceil(HOOK_DEADLINE_MS / 1e3) + 3;
|
|
8222
9033
|
DEFAULT_HOOK_TOOLS = ["claude", "codex"];
|
|
9034
|
+
HOOK_WHAT = {
|
|
9035
|
+
sessionStart: "session-start hook",
|
|
9036
|
+
beforeEdit: "before-edit hook",
|
|
9037
|
+
endOfTurn: "end-of-turn hook"
|
|
9038
|
+
};
|
|
8223
9039
|
HOOKS = [
|
|
8224
9040
|
{
|
|
8225
|
-
what:
|
|
9041
|
+
what: HOOK_WHAT.sessionStart,
|
|
8226
9042
|
event: "SessionStart",
|
|
8227
9043
|
command: `memgineering resurface --limit 5 --deadline ${HOOK_DEADLINE_MS} || true`,
|
|
8228
9044
|
signature: "memgineering resurface"
|
|
8229
9045
|
},
|
|
8230
9046
|
{
|
|
8231
|
-
what:
|
|
9047
|
+
what: HOOK_WHAT.beforeEdit,
|
|
8232
9048
|
event: "PreToolUse",
|
|
8233
9049
|
// Both lists are subsets of what `guard` filters on, so the process does
|
|
8234
9050
|
// not start at all for a call it would ignore.
|
|
@@ -8279,7 +9095,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
8279
9095
|
* because speaking here keeps the agent working — which is why the report
|
|
8280
9096
|
* says so rather than leaving somebody to notice.
|
|
8281
9097
|
*/
|
|
8282
|
-
what:
|
|
9098
|
+
what: HOOK_WHAT.endOfTurn,
|
|
8283
9099
|
event: "Stop",
|
|
8284
9100
|
command: "memgineering guard --at stop || true",
|
|
8285
9101
|
signature: "memgineering guard --at stop",
|
|
@@ -8293,7 +9109,7 @@ For a guide: put ${BRAND.hubMarkerStart} and ${BRAND.hubMarkerEnd} back around t
|
|
|
8293
9109
|
import net from "net";
|
|
8294
9110
|
|
|
8295
9111
|
// src/program.ts
|
|
8296
|
-
import { Command as
|
|
9112
|
+
import { Command as Command46 } from "commander";
|
|
8297
9113
|
|
|
8298
9114
|
// src/help.ts
|
|
8299
9115
|
init_ui();
|
|
@@ -8337,7 +9153,18 @@ var COMMAND_GROUPS = [
|
|
|
8337
9153
|
{
|
|
8338
9154
|
title: "CURATION",
|
|
8339
9155
|
blurb: "occasional \u2014 correct what is remembered and what is read",
|
|
8340
|
-
commands: [
|
|
9156
|
+
commands: [
|
|
9157
|
+
"retire",
|
|
9158
|
+
"unretire",
|
|
9159
|
+
"rescope",
|
|
9160
|
+
"expire",
|
|
9161
|
+
"retract",
|
|
9162
|
+
"exclude",
|
|
9163
|
+
"unexclude",
|
|
9164
|
+
"consolidate",
|
|
9165
|
+
"propose",
|
|
9166
|
+
"proposals"
|
|
9167
|
+
]
|
|
8341
9168
|
},
|
|
8342
9169
|
{
|
|
8343
9170
|
title: "ACCOUNT",
|
|
@@ -8429,26 +9256,10 @@ init_errors();
|
|
|
8429
9256
|
// src/lib/error-reporter.ts
|
|
8430
9257
|
init_src();
|
|
8431
9258
|
init_errors();
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
// src/version.ts
|
|
8435
|
-
import { readFileSync } from "fs";
|
|
8436
|
-
import { fileURLToPath } from "url";
|
|
8437
|
-
import { dirname, join as join4 } from "path";
|
|
8438
|
-
var __dir = dirname(fileURLToPath(import.meta.url));
|
|
8439
|
-
var pkgPath = join4(__dir, "..", "package.json");
|
|
8440
|
-
var VERSION = (() => {
|
|
8441
|
-
try {
|
|
8442
|
-
const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
8443
|
-
return typeof parsed.version === "string" ? parsed.version : "0.0.0-unknown";
|
|
8444
|
-
} catch {
|
|
8445
|
-
return "0.0.0-unknown";
|
|
8446
|
-
}
|
|
8447
|
-
})();
|
|
8448
|
-
|
|
8449
|
-
// src/lib/error-reporter.ts
|
|
9259
|
+
init_version();
|
|
8450
9260
|
init_api_client();
|
|
8451
9261
|
init_config();
|
|
9262
|
+
import { homedir as homedir3, platform } from "os";
|
|
8452
9263
|
var HOME = homedir3();
|
|
8453
9264
|
var REPORT_TIMEOUT_MS = 3e3;
|
|
8454
9265
|
function classifyError(err) {
|
|
@@ -8494,9 +9305,9 @@ async function reportError(kind, err) {
|
|
|
8494
9305
|
...code ? { error_code: code } : {},
|
|
8495
9306
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
8496
9307
|
});
|
|
8497
|
-
const
|
|
9308
|
+
const base2 = process.env["MEMGINEERING_TELEMETRY_URL"]?.trim() || apiUrl();
|
|
8498
9309
|
await Promise.race([
|
|
8499
|
-
fetch(`${
|
|
9310
|
+
fetch(`${base2.replace(/\/+$/, "")}/v1/telemetry/error`, {
|
|
8500
9311
|
method: "POST",
|
|
8501
9312
|
headers: { "content-type": "application/json" },
|
|
8502
9313
|
body: JSON.stringify(payload)
|
|
@@ -10587,10 +11398,10 @@ function adaptRegistryError(err) {
|
|
|
10587
11398
|
}
|
|
10588
11399
|
if (err instanceof KordisApiError) {
|
|
10589
11400
|
const code = CODE_MAP[err.code] ?? "server_refused";
|
|
10590
|
-
const
|
|
11401
|
+
const base2 = err.hint ?? FALLBACK_HINTS[code] ?? "Nothing was changed. The registry refused this request; running it again is reasonable.";
|
|
10591
11402
|
const retry = err.retryAfter !== void 0 ? `
|
|
10592
11403
|
Wait ${err.retryAfter}s before trying again.` : "";
|
|
10593
|
-
return memgError(code, err.message, `${withFlags(
|
|
11404
|
+
return memgError(code, err.message, `${withFlags(base2, err)}${retry}`);
|
|
10594
11405
|
}
|
|
10595
11406
|
return null;
|
|
10596
11407
|
}
|
|
@@ -10743,7 +11554,24 @@ var OpVerbSchema = z23.enum([
|
|
|
10743
11554
|
* same thing. A verb the server can emit and this cannot name is a line this
|
|
10744
11555
|
* CLI would silently count as corruption.
|
|
10745
11556
|
*/
|
|
10746
|
-
"write"
|
|
11557
|
+
"write",
|
|
11558
|
+
/**
|
|
11559
|
+
* Where a memory binds, moved — and nothing it says changed.
|
|
11560
|
+
*
|
|
11561
|
+
* Its own verb rather than a `revise` for the reason `reclassify` has one: a
|
|
11562
|
+
* ledger is read by people asking what happened to their notes, and "revise"
|
|
11563
|
+
* for a change that touched no conclusion tells them something untrue. This
|
|
11564
|
+
* one has a second reason — a rescope is the only claimless write that can
|
|
11565
|
+
* change WHICH decisions an agent is handed before it edits a file, so it is
|
|
11566
|
+
* the row somebody goes looking for after a rule started or stopped applying
|
|
11567
|
+
* to them.
|
|
11568
|
+
*
|
|
11569
|
+
* Note `unbind` is absent here on purpose: local `revise --no-rule` records
|
|
11570
|
+
* `revise`, because it IS that command. These two are their own commands.
|
|
11571
|
+
*/
|
|
11572
|
+
"rescope",
|
|
11573
|
+
/** A memory was given an end date. It was NOT retired — see `expire`. */
|
|
11574
|
+
"expire"
|
|
10747
11575
|
]);
|
|
10748
11576
|
var OpRecordSchema = z23.object({
|
|
10749
11577
|
op_id: z23.string().min(1),
|
|
@@ -10980,9 +11808,9 @@ async function namedCloudTarget(opts) {
|
|
|
10980
11808
|
"`--brain <name>` reads an existing brain on the account. `--local` and `--vault <path>` read a folder on this machine. Nothing was read or changed; choose one target kind."
|
|
10981
11809
|
);
|
|
10982
11810
|
}
|
|
10983
|
-
const
|
|
10984
|
-
const credentials = await requireCredentials(
|
|
10985
|
-
const brains = await listCloudBrains(credentials.token,
|
|
11811
|
+
const base2 = apiUrl();
|
|
11812
|
+
const credentials = await requireCredentials(base2);
|
|
11813
|
+
const brains = await listCloudBrains(credentials.token, base2);
|
|
10986
11814
|
if (brains.length === 0) {
|
|
10987
11815
|
throw memgError(
|
|
10988
11816
|
"invalid_input",
|
|
@@ -11001,23 +11829,23 @@ ${available}
|
|
|
11001
11829
|
Use one of those names or ids. No brain was created.`
|
|
11002
11830
|
);
|
|
11003
11831
|
}
|
|
11004
|
-
return { kind: "cloud", brainId: found.id, name: found.name, apiUrl:
|
|
11832
|
+
return { kind: "cloud", brainId: found.id, name: found.name, apiUrl: base2 };
|
|
11005
11833
|
}
|
|
11006
11834
|
var isNoVaultLinked = (err) => typeof err === "object" && err !== null && err.code === "no_vault_linked";
|
|
11007
11835
|
async function accountAwareRefusal() {
|
|
11008
11836
|
try {
|
|
11009
|
-
const
|
|
11837
|
+
const base2 = apiUrl();
|
|
11010
11838
|
const credentials = await readCredentials();
|
|
11011
|
-
if (!credentials || credentials.api_url !==
|
|
11839
|
+
if (!credentials || credentials.api_url !== base2) {
|
|
11012
11840
|
const cloud = (await loadConfig()).brain.cloud;
|
|
11013
|
-
if (!cloud || cloud.api_url !==
|
|
11841
|
+
if (!cloud || cloud.api_url !== base2) return null;
|
|
11014
11842
|
return memgError(
|
|
11015
11843
|
"not_signed_in",
|
|
11016
11844
|
`this machine is signed out of \`${oneLine(cloud.name, WIDTH.title)}\``,
|
|
11017
11845
|
"Your notes are on that account \u2014 nothing is lost, and nothing here needs creating.\n\nSign back in: `memgineering login`\n\nDo NOT run `init` or `link` to fix this. They make a NEW empty brain on this\nmachine and leave the notes where they are."
|
|
11018
11846
|
);
|
|
11019
11847
|
}
|
|
11020
|
-
const brains = await listCloudBrains(credentials.token,
|
|
11848
|
+
const brains = await listCloudBrains(credentials.token, base2, { retries: 0 });
|
|
11021
11849
|
if (brains.length === 0) return null;
|
|
11022
11850
|
const names = brains.map((b) => ` ${oneLine(b.name, WIDTH.title)}`).join("\n");
|
|
11023
11851
|
return memgError(
|
|
@@ -11052,29 +11880,29 @@ machine and leave the notes where they are.`
|
|
|
11052
11880
|
}
|
|
11053
11881
|
}
|
|
11054
11882
|
async function hostedPointer() {
|
|
11055
|
-
const
|
|
11883
|
+
const base2 = apiUrl();
|
|
11056
11884
|
const config = await loadConfig();
|
|
11057
11885
|
const cloud = config.brain.cloud;
|
|
11058
|
-
if (!cloud || cloud.api_url !==
|
|
11886
|
+
if (!cloud || cloud.api_url !== base2) return null;
|
|
11059
11887
|
const stored = await readCredentials().catch(() => null);
|
|
11060
|
-
const signedIn = stored !== null && stored.api_url ===
|
|
11888
|
+
const signedIn = stored !== null && stored.api_url === base2;
|
|
11061
11889
|
const pending = signedIn ? null : await readPendingLogin().catch(() => null);
|
|
11062
11890
|
return {
|
|
11063
11891
|
name: cloud.name,
|
|
11064
11892
|
id: cloud.id,
|
|
11065
|
-
apiUrl:
|
|
11893
|
+
apiUrl: base2,
|
|
11066
11894
|
signedIn,
|
|
11067
|
-
signInPending: pending !== null && pending.api_url ===
|
|
11895
|
+
signInPending: pending !== null && pending.api_url === base2 && !pendingExpired(pending)
|
|
11068
11896
|
};
|
|
11069
11897
|
}
|
|
11070
11898
|
async function cloudTarget() {
|
|
11071
|
-
const
|
|
11899
|
+
const base2 = apiUrl();
|
|
11072
11900
|
const config = await loadConfig();
|
|
11073
11901
|
const cloud = config.brain.cloud;
|
|
11074
|
-
if (!cloud || cloud.api_url !==
|
|
11075
|
-
const credentials = await currentCredentials(
|
|
11902
|
+
if (!cloud || cloud.api_url !== base2) return null;
|
|
11903
|
+
const credentials = await currentCredentials(base2);
|
|
11076
11904
|
if (!credentials) return null;
|
|
11077
|
-
return { kind: "cloud", brainId: cloud.id, name: cloud.name, apiUrl:
|
|
11905
|
+
return { kind: "cloud", brainId: cloud.id, name: cloud.name, apiUrl: base2 };
|
|
11078
11906
|
}
|
|
11079
11907
|
|
|
11080
11908
|
// src/commands/base-write.ts
|
|
@@ -11979,6 +12807,7 @@ async function readEventsAt(stateDir) {
|
|
|
11979
12807
|
init_index_build();
|
|
11980
12808
|
|
|
11981
12809
|
// src/lib/base-files.ts
|
|
12810
|
+
init_src();
|
|
11982
12811
|
init_init();
|
|
11983
12812
|
var BASE_FILES = [
|
|
11984
12813
|
{
|
|
@@ -12028,43 +12857,31 @@ var BASE_FILES = [
|
|
|
12028
12857
|
}
|
|
12029
12858
|
];
|
|
12030
12859
|
var HUB_PATH = "00_HUB/HUB.md";
|
|
12860
|
+
var TEMPLATE_SLOTS = 2;
|
|
12861
|
+
function capTemplates(cards, isTemplate) {
|
|
12862
|
+
const kept = [];
|
|
12863
|
+
const withheld = [];
|
|
12864
|
+
let templates = 0;
|
|
12865
|
+
for (const card of cards) {
|
|
12866
|
+
if (isTemplate(card) && ++templates > TEMPLATE_SLOTS) withheld.push(card);
|
|
12867
|
+
else kept.push(card);
|
|
12868
|
+
}
|
|
12869
|
+
return { kept, withheld };
|
|
12870
|
+
}
|
|
12031
12871
|
function bodyOf2(content) {
|
|
12032
12872
|
const stripped = content.replace(/^/, "");
|
|
12033
12873
|
const fence2 = /^---[ \t]*\r?\n[\s\S]*?\r?\n---[ \t]*(\r?\n|$)/.exec(stripped);
|
|
12034
12874
|
return (fence2 ? stripped.slice(fence2[0].length) : stripped).trim();
|
|
12035
12875
|
}
|
|
12036
|
-
var RETIRED_BASE_TEMPLATES = {
|
|
12037
|
-
// Retired 0.11.1, when the prompt grew a storage-boundary invitation.
|
|
12038
|
-
"01_BASE/BOUNDARIES.md": [
|
|
12039
|
-
[
|
|
12040
|
-
"---",
|
|
12041
|
-
"memgineering:",
|
|
12042
|
-
" id: boundaries",
|
|
12043
|
-
" title: Boundaries",
|
|
12044
|
-
" summary: What an agent must not do without asking me first.",
|
|
12045
|
-
" scope: base",
|
|
12046
|
-
"---",
|
|
12047
|
-
"",
|
|
12048
|
-
"# Boundaries",
|
|
12049
|
-
"",
|
|
12050
|
-
"Privacy, spending, anything irreversible, anything that leaves this",
|
|
12051
|
-
"machine. Be specific \u2014 a boundary that has to be interpreted is one",
|
|
12052
|
-
"that will be interpreted generously.",
|
|
12053
|
-
"",
|
|
12054
|
-
"- ",
|
|
12055
|
-
""
|
|
12056
|
-
].join("\n")
|
|
12057
|
-
]
|
|
12058
|
-
};
|
|
12059
12876
|
function templatesFor(path, current) {
|
|
12060
|
-
const retired = RETIRED_BASE_TEMPLATES
|
|
12877
|
+
const retired = ownEntry(RETIRED_BASE_TEMPLATES, path) ?? [];
|
|
12061
12878
|
return current === void 0 ? retired : [current, ...retired];
|
|
12062
12879
|
}
|
|
12063
12880
|
function isBaseFilePath(path) {
|
|
12064
12881
|
return path.startsWith("01_BASE/");
|
|
12065
12882
|
}
|
|
12066
12883
|
function bodyStillTemplate(path, content) {
|
|
12067
|
-
const template = files("brain")
|
|
12884
|
+
const template = ownEntry(files("brain"), path);
|
|
12068
12885
|
if (template === void 0) return false;
|
|
12069
12886
|
const body = bodyOf2(content);
|
|
12070
12887
|
if (body === "") return false;
|
|
@@ -12090,7 +12907,7 @@ async function untouchedBase(adapter, paths) {
|
|
|
12090
12907
|
const out = /* @__PURE__ */ new Set();
|
|
12091
12908
|
await Promise.all(
|
|
12092
12909
|
paths.map(async (path) => {
|
|
12093
|
-
const template = templates
|
|
12910
|
+
const template = ownEntry(templates, path);
|
|
12094
12911
|
if (template === void 0) return;
|
|
12095
12912
|
const actual = await adapter.read(path).then((r) => r.content).catch(() => null);
|
|
12096
12913
|
if (actual === null) return;
|
|
@@ -12173,6 +12990,9 @@ function openCommand() {
|
|
|
12173
12990
|
claims: found.claims,
|
|
12174
12991
|
relations: found.relations,
|
|
12175
12992
|
...showBody ? { body: found.body } : {},
|
|
12993
|
+
// Only when the server said; an older one cannot, and false
|
|
12994
|
+
// would be a claim it never made.
|
|
12995
|
+
...found.unfilled === void 0 ? {} : { scaffolding: found.unfilled },
|
|
12176
12996
|
brain: { hosted: true, name: target.name }
|
|
12177
12997
|
},
|
|
12178
12998
|
human: () => {
|
|
@@ -12180,6 +13000,11 @@ function openCommand() {
|
|
|
12180
13000
|
`## ${oneLine(String(found.memory["title"] ?? found.path), WIDTH.title)}
|
|
12181
13001
|
`
|
|
12182
13002
|
);
|
|
13003
|
+
if (found.unfilled === true) {
|
|
13004
|
+
printHuman(
|
|
13005
|
+
c.yellow("not filled in yet") + c.gray(" \u2014 still the template it started as")
|
|
13006
|
+
);
|
|
13007
|
+
}
|
|
12183
13008
|
const summary = found.memory["summary"];
|
|
12184
13009
|
if (typeof summary === "string" && summary !== "") {
|
|
12185
13010
|
printHuman(`> ${oneLine(summary, WIDTH.text)}
|
|
@@ -12784,7 +13609,7 @@ function rememberCommand() {
|
|
|
12784
13609
|
const kind = parseKind(opts.kind);
|
|
12785
13610
|
const neverStore = parseNeverStore(opts.neverStore);
|
|
12786
13611
|
const binding = opts.rule === true || neverStore.length > 0;
|
|
12787
|
-
const scope = await scopeToWrite(opts.scope);
|
|
13612
|
+
const { scope, via: scopeVia } = await scopeToWrite(opts.scope);
|
|
12788
13613
|
if (binding && scope === void 0 && !said.noScope) {
|
|
12789
13614
|
throw memgError(
|
|
12790
13615
|
"invalid_input",
|
|
@@ -12817,6 +13642,7 @@ function rememberCommand() {
|
|
|
12817
13642
|
path: written.path,
|
|
12818
13643
|
op_id: written.op_id,
|
|
12819
13644
|
scope: written.scope,
|
|
13645
|
+
scope_via: scopeVia,
|
|
12820
13646
|
brain: { hosted: true, name: target.name },
|
|
12821
13647
|
...binding ? { binding: true } : {},
|
|
12822
13648
|
...neverStore.length > 0 ? { never_store: neverStore } : {},
|
|
@@ -12857,6 +13683,7 @@ function rememberCommand() {
|
|
|
12857
13683
|
printHuman(
|
|
12858
13684
|
`note: \`${oneLine(written.id, WIDTH.handle)}\` \xB7 \`${oneLine(written.path, WIDTH.path)}\``
|
|
12859
13685
|
);
|
|
13686
|
+
printHuman(c.gray(scopeLine(written.scope, scopeVia, binding)));
|
|
12860
13687
|
if (binding) {
|
|
12861
13688
|
printHuman("rule: shown before you write a file here, once a session");
|
|
12862
13689
|
}
|
|
@@ -12979,6 +13806,7 @@ function rememberCommand() {
|
|
|
12979
13806
|
path,
|
|
12980
13807
|
op_id: record.op_id,
|
|
12981
13808
|
scope: scope ?? null,
|
|
13809
|
+
scope_via: scopeVia,
|
|
12982
13810
|
binding,
|
|
12983
13811
|
...neverStore.length > 0 ? { never_store: neverStore } : {},
|
|
12984
13812
|
// Null on nearly every write, and present exactly when the ledger
|
|
@@ -12992,6 +13820,7 @@ function rememberCommand() {
|
|
|
12992
13820
|
` : `Remembered.
|
|
12993
13821
|
`);
|
|
12994
13822
|
printHuman(`note: \`${id}\` \xB7 \`${path}\``);
|
|
13823
|
+
printHuman(c.gray(scopeLine(scope, scopeVia, binding)));
|
|
12995
13824
|
if (binding) {
|
|
12996
13825
|
printHuman(c.gray(`rule: shown before you write a file here, once a session`));
|
|
12997
13826
|
}
|
|
@@ -13039,14 +13868,27 @@ function renderObservation(args) {
|
|
|
13039
13868
|
return lines2.join("\n");
|
|
13040
13869
|
}
|
|
13041
13870
|
async function scopeToWrite(flag2) {
|
|
13042
|
-
if (flag2 === false) return void 0;
|
|
13043
|
-
if (flag2 !== void 0) return flag2;
|
|
13871
|
+
if (flag2 === false) return { scope: void 0, via: "flag" };
|
|
13872
|
+
if (flag2 !== void 0) return { scope: flag2, via: "flag" };
|
|
13044
13873
|
try {
|
|
13045
|
-
const derived =
|
|
13046
|
-
return derived !== null && stampable(derived) ? derived : void 0;
|
|
13874
|
+
const derived = await projectOf();
|
|
13875
|
+
return derived.name !== null && stampable(derived.name) ? { scope: derived.name, via: derived.via } : { scope: void 0, via: "none" };
|
|
13047
13876
|
} catch {
|
|
13048
|
-
return void 0;
|
|
13877
|
+
return { scope: void 0, via: "none" };
|
|
13878
|
+
}
|
|
13879
|
+
}
|
|
13880
|
+
function scopeLine(scope, via, binding) {
|
|
13881
|
+
if (!scope) {
|
|
13882
|
+
return binding ? "scope: none \u2014 this rule holds in every project" : "scope: none \u2014 recall finds it from any project";
|
|
13049
13883
|
}
|
|
13884
|
+
const from = {
|
|
13885
|
+
flag: "as given with --scope",
|
|
13886
|
+
override: "the name set for this folder (`memgineering project`)",
|
|
13887
|
+
remote: "from this repository's git remote",
|
|
13888
|
+
dirname: "from this folder's name \u2014 the repository has no git remote",
|
|
13889
|
+
none: "as given"
|
|
13890
|
+
};
|
|
13891
|
+
return `scope: \`${oneLine(scope, WIDTH.title)}\` \u2014 ${from[via]}`;
|
|
13050
13892
|
}
|
|
13051
13893
|
function stampable(name) {
|
|
13052
13894
|
if (name.length > SCOPE_LIMIT) return false;
|
|
@@ -14512,11 +15354,11 @@ init_init();
|
|
|
14512
15354
|
var USER_PATH = "01_BASE/USER.md";
|
|
14513
15355
|
var PREFERENCES_PATH = "01_BASE/PREFERENCES.md";
|
|
14514
15356
|
var SCAFFOLD_PATHS = Object.keys(files("any"));
|
|
14515
|
-
function baseFilledPaths(
|
|
14516
|
-
return Object.keys(bulletsFor(
|
|
15357
|
+
function baseFilledPaths(base2) {
|
|
15358
|
+
return Object.keys(bulletsFor(base2));
|
|
14517
15359
|
}
|
|
14518
|
-
function scaffoldNotes(name,
|
|
14519
|
-
const bullets = bulletsFor(
|
|
15360
|
+
function scaffoldNotes(name, base2 = {}) {
|
|
15361
|
+
const bullets = bulletsFor(base2);
|
|
14520
15362
|
return Object.entries(files(name)).map(([path, content]) => {
|
|
14521
15363
|
const lines2 = bullets[path];
|
|
14522
15364
|
return { path, content: lines2 ? fillPlaceholder(content, lines2) : content };
|
|
@@ -14526,10 +15368,10 @@ function oneLine2(value) {
|
|
|
14526
15368
|
const trimmed = value?.replace(/\s+/g, " ").trim();
|
|
14527
15369
|
return trimmed ? trimmed : void 0;
|
|
14528
15370
|
}
|
|
14529
|
-
function bulletsFor(
|
|
14530
|
-
const user = oneLine2(
|
|
14531
|
-
const language = oneLine2(
|
|
14532
|
-
const about = oneLine2(
|
|
15371
|
+
function bulletsFor(base2) {
|
|
15372
|
+
const user = oneLine2(base2.user);
|
|
15373
|
+
const language = oneLine2(base2.language);
|
|
15374
|
+
const about = oneLine2(base2.about);
|
|
14533
15375
|
const out = {};
|
|
14534
15376
|
const userBullets = [
|
|
14535
15377
|
...user === void 0 ? [] : [`Call them: ${user}`],
|
|
@@ -14580,9 +15422,9 @@ function createCommand() {
|
|
|
14580
15422
|
"Nothing was created. Agree one with the user \u2014 it is what they will see in\n`memgineering brains` and how they tell two brains apart \u2014 then run\n`memgineering create <name>`."
|
|
14581
15423
|
);
|
|
14582
15424
|
}
|
|
14583
|
-
const
|
|
14584
|
-
const notes = scaffoldNotes(name,
|
|
14585
|
-
const filled = baseFilledPaths(
|
|
15425
|
+
const base2 = { user: opts.user, language: opts.language, about: opts.about };
|
|
15426
|
+
const notes = scaffoldNotes(name, base2);
|
|
15427
|
+
const filled = baseFilledPaths(base2);
|
|
14586
15428
|
if (opts.dryRun === true) {
|
|
14587
15429
|
printDual({
|
|
14588
15430
|
json: {
|
|
@@ -15208,9 +16050,9 @@ function assertNothingLost(before, after, action) {
|
|
|
15208
16050
|
});
|
|
15209
16051
|
}
|
|
15210
16052
|
}
|
|
15211
|
-
async function commit2(root,
|
|
16053
|
+
async function commit2(root, base2, next) {
|
|
15212
16054
|
const current = await readIgnoreFileState(root);
|
|
15213
|
-
if (current.hash !==
|
|
16055
|
+
if (current.hash !== base2.hash) {
|
|
15214
16056
|
return { ok: false, reason: "revision_conflict", current };
|
|
15215
16057
|
}
|
|
15216
16058
|
if (next === current.content) return { ok: true, content: next, changed: false };
|
|
@@ -15256,15 +16098,15 @@ function planRemoveIgnoreEntry(content, relPath) {
|
|
|
15256
16098
|
assertNothingLost(next, content, "un-excluding");
|
|
15257
16099
|
return { content: next, changed: true };
|
|
15258
16100
|
}
|
|
15259
|
-
async function addIgnoreEntry(root, relPath,
|
|
15260
|
-
const edit = planAddIgnoreEntry(
|
|
15261
|
-
if (!edit.changed) return { ok: true, content:
|
|
15262
|
-
return commit2(root,
|
|
16101
|
+
async function addIgnoreEntry(root, relPath, base2) {
|
|
16102
|
+
const edit = planAddIgnoreEntry(base2.content, relPath);
|
|
16103
|
+
if (!edit.changed) return { ok: true, content: base2.content, changed: false };
|
|
16104
|
+
return commit2(root, base2, edit.content);
|
|
15263
16105
|
}
|
|
15264
|
-
async function removeIgnoreEntry(root, relPath,
|
|
15265
|
-
const edit = planRemoveIgnoreEntry(
|
|
15266
|
-
if (!edit.changed) return { ok: true, content:
|
|
15267
|
-
return commit2(root,
|
|
16106
|
+
async function removeIgnoreEntry(root, relPath, base2) {
|
|
16107
|
+
const edit = planRemoveIgnoreEntry(base2.content, relPath);
|
|
16108
|
+
if (!edit.changed) return { ok: true, content: base2.content, changed: false };
|
|
16109
|
+
return commit2(root, base2, edit.content);
|
|
15268
16110
|
}
|
|
15269
16111
|
|
|
15270
16112
|
// src/commands/curation.ts
|
|
@@ -15379,9 +16221,9 @@ function ignoreCommand(mode) {
|
|
|
15379
16221
|
createPrefix: brain.observationsDir
|
|
15380
16222
|
});
|
|
15381
16223
|
const target = mode === "exclude" ? await mustMatchAFile(brain.root, adapter, path) : path;
|
|
15382
|
-
const
|
|
16224
|
+
const base2 = await readIgnoreFileState(brain.root);
|
|
15383
16225
|
const record = await withWriteLock(brain.root, async () => {
|
|
15384
|
-
const result = mode === "exclude" ? await addIgnoreEntry(brain.root, target,
|
|
16226
|
+
const result = mode === "exclude" ? await addIgnoreEntry(brain.root, target, base2) : await removeIgnoreEntry(brain.root, target, base2);
|
|
15385
16227
|
if (!result.ok) {
|
|
15386
16228
|
throw memgError(
|
|
15387
16229
|
"undo_conflict",
|
|
@@ -15394,11 +16236,11 @@ function ignoreCommand(mode) {
|
|
|
15394
16236
|
verb: mode,
|
|
15395
16237
|
target: ".memgignore",
|
|
15396
16238
|
memoryId: null,
|
|
15397
|
-
hashBefore: hashContent(
|
|
16239
|
+
hashBefore: hashContent(base2.content),
|
|
15398
16240
|
hashAfter: hashContent(result.content),
|
|
15399
|
-
patch: { before:
|
|
16241
|
+
patch: { before: base2.content, after: result.content },
|
|
15400
16242
|
rationale,
|
|
15401
|
-
snapshotBefore:
|
|
16243
|
+
snapshotBefore: base2.content
|
|
15402
16244
|
});
|
|
15403
16245
|
});
|
|
15404
16246
|
printDual({
|
|
@@ -15502,59 +16344,321 @@ var unretireCommand = () => lifecycleCommand("unretire");
|
|
|
15502
16344
|
var excludeCommand = () => ignoreCommand("exclude");
|
|
15503
16345
|
var unexcludeCommand = () => ignoreCommand("unexclude");
|
|
15504
16346
|
|
|
15505
|
-
// src/commands/
|
|
16347
|
+
// src/commands/scope-validity.ts
|
|
15506
16348
|
init_src();
|
|
15507
16349
|
import { Command as Command15 } from "commander";
|
|
16350
|
+
init_errors();
|
|
15508
16351
|
init_index_build();
|
|
15509
16352
|
init_cloud_brain();
|
|
15510
16353
|
init_vault();
|
|
15511
16354
|
init_ui();
|
|
15512
|
-
function
|
|
15513
|
-
return new Command15("
|
|
15514
|
-
|
|
15515
|
-
|
|
15516
|
-
|
|
15517
|
-
|
|
15518
|
-
|
|
15519
|
-
|
|
15520
|
-
|
|
15521
|
-
|
|
15522
|
-
|
|
15523
|
-
|
|
15524
|
-
const
|
|
16355
|
+
function rescopeCommand() {
|
|
16356
|
+
return new Command15("rescope").description("change which project a memory binds in \u2014 nothing it says changes").argument("<ref>", "handle, id, path, or exact title").option("--scope <name>", "the project it binds in from now on").option("--global", "it binds everywhere \u2014 say this rather than leaving the scope off").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
|
|
16357
|
+
async (ref, opts) => {
|
|
16358
|
+
const scope = scopeAskedFor(opts);
|
|
16359
|
+
const where = await resolveTarget({
|
|
16360
|
+
...opts.local === void 0 ? {} : { local: opts.local },
|
|
16361
|
+
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
16362
|
+
});
|
|
16363
|
+
if (where.kind === "cloud") {
|
|
16364
|
+
await rescopeHosted(where, ref, scope, opts.reason);
|
|
16365
|
+
return;
|
|
16366
|
+
}
|
|
16367
|
+
const { entry, record, was } = await rescopeLocal(opts.vault, ref, scope, opts.reason);
|
|
16368
|
+
const widened = isGlobalScope(scope);
|
|
15525
16369
|
printDual({
|
|
15526
|
-
json: {
|
|
15527
|
-
|
|
15528
|
-
|
|
15529
|
-
|
|
15530
|
-
|
|
15531
|
-
|
|
16370
|
+
json: {
|
|
16371
|
+
action: "rescope",
|
|
16372
|
+
memory_id: entry.memory.id,
|
|
16373
|
+
target: entry.path,
|
|
16374
|
+
scope: widened ? null : scope,
|
|
16375
|
+
op_id: record.op_id
|
|
16376
|
+
},
|
|
16377
|
+
human: () => {
|
|
16378
|
+
printHuman(
|
|
16379
|
+
widened ? `**${oneLine(entry.memory.title, WIDTH.title)}** now binds everywhere.
|
|
16380
|
+
` : `**${oneLine(entry.memory.title, WIDTH.title)}** now binds in **${oneLine(scope, WIDTH.title)}**.
|
|
16381
|
+
`
|
|
16382
|
+
);
|
|
16383
|
+
printHuman(c.gray(reachOf(entry.memory.binding === true, widened, scope, was)));
|
|
16384
|
+
printHuman(c.gray(`
|
|
16385
|
+
Undo: \`${undoHint(record.op_id, opts)}\``));
|
|
16386
|
+
}
|
|
15532
16387
|
});
|
|
15533
|
-
return;
|
|
15534
16388
|
}
|
|
15535
|
-
|
|
15536
|
-
const adapter = await openBrainOrExplain(brain.root);
|
|
15537
|
-
const built = await openIndex(adapter, brain.root);
|
|
15538
|
-
const entry = resolveRef(built.entries, ref);
|
|
15539
|
-
const [events, ledger] = await Promise.all([readEvents(brain.root), readOps(brain.root)]);
|
|
15540
|
-
const now = /* @__PURE__ */ new Date();
|
|
15541
|
-
const report3 = assembleEvidence(entry, events, ledger.ops, {
|
|
15542
|
-
contextDir: process.cwd(),
|
|
15543
|
-
now,
|
|
15544
|
-
brain: built.entries
|
|
15545
|
-
});
|
|
15546
|
-
printDual({
|
|
15547
|
-
json: report3,
|
|
15548
|
-
human: () => renderEvidence(report3, { now, hosted: false })
|
|
15549
|
-
});
|
|
15550
|
-
});
|
|
16389
|
+
);
|
|
15551
16390
|
}
|
|
15552
|
-
function
|
|
15553
|
-
|
|
15554
|
-
|
|
15555
|
-
|
|
15556
|
-
|
|
15557
|
-
|
|
16391
|
+
function expireCommand() {
|
|
16392
|
+
return new Command15("expire").description("give a memory an end date \u2014 it is NOT retired, and recall still returns it").argument("<ref>", "handle, id, path, or exact title").requiredOption("--until <date>", "the day it stops being current, e.g. 2026-10-31").option("--reason <text>", "why \u2014 kept in the ledger").option("--vault <path>", "which brain the memory is in").option("--local", "use the brain on this machine, even when signed in to a hosted one").action(
|
|
16393
|
+
async (ref, opts) => {
|
|
16394
|
+
const where = await resolveTarget({
|
|
16395
|
+
...opts.local === void 0 ? {} : { local: opts.local },
|
|
16396
|
+
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
16397
|
+
});
|
|
16398
|
+
if (where.kind === "cloud") {
|
|
16399
|
+
await expireHosted(where, ref, opts.until, opts.reason);
|
|
16400
|
+
return;
|
|
16401
|
+
}
|
|
16402
|
+
const { entry, record } = await expireLocal(opts.vault, ref, opts.until, opts.reason);
|
|
16403
|
+
printDual({
|
|
16404
|
+
json: {
|
|
16405
|
+
action: "expire",
|
|
16406
|
+
memory_id: entry.memory.id,
|
|
16407
|
+
target: entry.path,
|
|
16408
|
+
valid_until: opts.until.trim(),
|
|
16409
|
+
op_id: record.op_id
|
|
16410
|
+
},
|
|
16411
|
+
human: () => {
|
|
16412
|
+
printHuman(
|
|
16413
|
+
`**${oneLine(entry.memory.title, WIDTH.title)}** holds until **${oneLine(opts.until.trim(), WIDTH.title)}**.
|
|
16414
|
+
`
|
|
16415
|
+
);
|
|
16416
|
+
printHuman(
|
|
16417
|
+
c.gray(
|
|
16418
|
+
"It is NOT retired. After that date recall still returns it \u2014 ranked below what is current, and labelled.\nTo take it out of the answers: `memgineering retire " + oneLine(entry.path, WIDTH.path) + "`"
|
|
16419
|
+
)
|
|
16420
|
+
);
|
|
16421
|
+
printHuman(c.gray(`
|
|
16422
|
+
Undo: \`${undoHint(record.op_id, opts)}\``));
|
|
16423
|
+
}
|
|
16424
|
+
});
|
|
16425
|
+
}
|
|
16426
|
+
);
|
|
16427
|
+
}
|
|
16428
|
+
function scopeAskedFor(opts) {
|
|
16429
|
+
const named = opts.scope !== void 0;
|
|
16430
|
+
if (named && opts.global === true) {
|
|
16431
|
+
throw memgError(
|
|
16432
|
+
"invalid_input",
|
|
16433
|
+
"`--scope` and `--global` say opposite things",
|
|
16434
|
+
"Pass one. `--scope <name>` binds the memory in that project; `--global` binds it everywhere."
|
|
16435
|
+
);
|
|
16436
|
+
}
|
|
16437
|
+
if (opts.global === true) return GLOBAL_SCOPE;
|
|
16438
|
+
if (!named || oneLineWhole(opts.scope ?? "") === "") {
|
|
16439
|
+
throw memgError(
|
|
16440
|
+
"invalid_input",
|
|
16441
|
+
"rescope needs to know where the memory binds",
|
|
16442
|
+
"Pass `--scope <project>`, or `--global` if it holds everywhere. Widening is spelled out because it is the change that is hardest to notice afterwards."
|
|
16443
|
+
);
|
|
16444
|
+
}
|
|
16445
|
+
const scope = oneLineWhole(opts.scope ?? "");
|
|
16446
|
+
if (isGlobalScope(scope)) {
|
|
16447
|
+
throw memgError(
|
|
16448
|
+
"invalid_input",
|
|
16449
|
+
"`global` is a reserved scope",
|
|
16450
|
+
"Use `--global` to say a memory binds everywhere. Keeping one spelling of it means nothing downstream has two to match on."
|
|
16451
|
+
);
|
|
16452
|
+
}
|
|
16453
|
+
return scope;
|
|
16454
|
+
}
|
|
16455
|
+
function reachOf(binding, widened, scope, was) {
|
|
16456
|
+
if (!binding) {
|
|
16457
|
+
return "Where a memory is scoped decides which project raises it. Nothing else about it changed.";
|
|
16458
|
+
}
|
|
16459
|
+
if (widened) {
|
|
16460
|
+
return `It is a rule, and it binds everywhere now${was === null ? "" : ` rather than only in ${was}`}. Agents in every project will be shown it before they edit a file.`;
|
|
16461
|
+
}
|
|
16462
|
+
return `It is a rule, and it binds only in ${scope} now. Agents working anywhere else will no longer be shown it.`;
|
|
16463
|
+
}
|
|
16464
|
+
async function rescopeLocal(vault, ref, scope, reason) {
|
|
16465
|
+
const { adapter, brain, built, entry } = await openFor(vault, ref);
|
|
16466
|
+
assertLocalBoundary(built.entries, { texts: [scope] });
|
|
16467
|
+
const was = declaredScopeOf(entry.memory);
|
|
16468
|
+
if (isGlobalScope(scope) ? was === null : was === scope) {
|
|
16469
|
+
throw memgError(
|
|
16470
|
+
"nothing_to_change",
|
|
16471
|
+
`${entry.memory.title} already binds there`,
|
|
16472
|
+
`Nothing was written, and nothing needed to be. \`memgineering open ${ref}\` shows the scope it declares; a note that declares none binds everywhere.`
|
|
16473
|
+
);
|
|
16474
|
+
}
|
|
16475
|
+
const record = await write(
|
|
16476
|
+
adapter,
|
|
16477
|
+
brain.root,
|
|
16478
|
+
entry,
|
|
16479
|
+
ref,
|
|
16480
|
+
"rescope",
|
|
16481
|
+
() => rescopeInput(entry, scope, { reason: reason ?? null }),
|
|
16482
|
+
reason
|
|
16483
|
+
);
|
|
16484
|
+
return { entry, record, was };
|
|
16485
|
+
}
|
|
16486
|
+
async function expireLocal(vault, ref, until, reason) {
|
|
16487
|
+
const { adapter, brain, entry } = await openFor(vault, ref);
|
|
16488
|
+
const wanted = until.trim();
|
|
16489
|
+
if (!Number.isFinite(Date.parse(wanted))) {
|
|
16490
|
+
throw memgError(
|
|
16491
|
+
"invalid_input",
|
|
16492
|
+
`${until} is not a date`,
|
|
16493
|
+
"Pass an ISO date, such as 2026-10-31 or 2026-10-31T00:00:00Z."
|
|
16494
|
+
);
|
|
16495
|
+
}
|
|
16496
|
+
if ((entry.claims[0]?.valid_until ?? entry.memory.valid_until ?? null) === wanted) {
|
|
16497
|
+
throw memgError(
|
|
16498
|
+
"nothing_to_change",
|
|
16499
|
+
`${entry.memory.title} already ends then`,
|
|
16500
|
+
`Nothing was written, and nothing needed to be. \`memgineering open ${ref}\` shows the date it carries.`
|
|
16501
|
+
);
|
|
16502
|
+
}
|
|
16503
|
+
const record = await write(
|
|
16504
|
+
adapter,
|
|
16505
|
+
brain.root,
|
|
16506
|
+
entry,
|
|
16507
|
+
ref,
|
|
16508
|
+
"expire",
|
|
16509
|
+
() => expireInput(entry, wanted, { reason: reason ?? null }),
|
|
16510
|
+
reason
|
|
16511
|
+
);
|
|
16512
|
+
return { entry, record };
|
|
16513
|
+
}
|
|
16514
|
+
async function openFor(vault, ref) {
|
|
16515
|
+
const { brain } = await resolveBrain(vault);
|
|
16516
|
+
const adapter = await LocalFileCasAdapter.openVault(brain.root, {
|
|
16517
|
+
createPrefix: brain.observationsDir
|
|
16518
|
+
});
|
|
16519
|
+
const built = await openIndex(adapter, brain.root);
|
|
16520
|
+
return { adapter, brain, built, entry: resolveRef(built.entries, ref) };
|
|
16521
|
+
}
|
|
16522
|
+
async function write(adapter, root, entry, ref, verb, input, reason) {
|
|
16523
|
+
const rationale = sanitizeRationale(reason);
|
|
16524
|
+
const result = commit(asSingleMatch(entry), input());
|
|
16525
|
+
if (result.action === "ignore") {
|
|
16526
|
+
throw memgError("invalid_input", `${verb} produced nothing`, "This is a defect \u2014 report it.");
|
|
16527
|
+
}
|
|
16528
|
+
const preview = await previewProposal(adapter, result.proposal);
|
|
16529
|
+
if (preview.refusal) throw preview.refusal;
|
|
16530
|
+
const before = await adapter.read(entry.path);
|
|
16531
|
+
if (changesNothing(result.proposal, preview, before.content)) {
|
|
16532
|
+
throw memgError(
|
|
16533
|
+
"nothing_to_change",
|
|
16534
|
+
`${entry.path} already says exactly this`,
|
|
16535
|
+
`Nothing was written. \`memgineering open ${ref}\` shows what it says now.`
|
|
16536
|
+
);
|
|
16537
|
+
}
|
|
16538
|
+
return withWriteLock(root, async () => {
|
|
16539
|
+
const outcome = await applyProposal(
|
|
16540
|
+
adapter,
|
|
16541
|
+
approve(result.proposal, {
|
|
16542
|
+
actor: "agent",
|
|
16543
|
+
note: `${verb}d autonomously; reversible from the operation ledger`
|
|
16544
|
+
})
|
|
16545
|
+
);
|
|
16546
|
+
if (!outcome.ok) {
|
|
16547
|
+
throw memgError(
|
|
16548
|
+
"undo_conflict",
|
|
16549
|
+
`${entry.path} changed while this was being prepared`,
|
|
16550
|
+
"Run the command again \u2014 it will re-read the note first."
|
|
16551
|
+
);
|
|
16552
|
+
}
|
|
16553
|
+
return appendOp(root, {
|
|
16554
|
+
verb,
|
|
16555
|
+
target: entry.path,
|
|
16556
|
+
memoryId: entry.memory.id,
|
|
16557
|
+
hashBefore: hashContent(before.content),
|
|
16558
|
+
hashAfter: hashContent(outcome.content),
|
|
16559
|
+
patch: { before: preview.before, after: preview.after },
|
|
16560
|
+
rationale,
|
|
16561
|
+
snapshotBefore: before.content
|
|
16562
|
+
});
|
|
16563
|
+
});
|
|
16564
|
+
}
|
|
16565
|
+
async function rescopeHosted(where, ref, scope, reason) {
|
|
16566
|
+
const brain = await openCloudBrain(where);
|
|
16567
|
+
const result = await cloudRescope(brain, {
|
|
16568
|
+
ref,
|
|
16569
|
+
scope,
|
|
16570
|
+
...reason === void 0 ? {} : { reason }
|
|
16571
|
+
});
|
|
16572
|
+
printDual({
|
|
16573
|
+
json: { brain: where.name, ...result },
|
|
16574
|
+
human: () => {
|
|
16575
|
+
printHuman(
|
|
16576
|
+
result.scope === null ? `Binds everywhere in **${oneLine(where.name, WIDTH.title)}**.
|
|
16577
|
+
` : `Binds in **${oneLine(result.scope, WIDTH.title)}**.
|
|
16578
|
+
`
|
|
16579
|
+
);
|
|
16580
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
16581
|
+
`);
|
|
16582
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
16583
|
+
printHuman(c.gray(`
|
|
16584
|
+
Undo: \`memgineering undo ${result.op_id}\``));
|
|
16585
|
+
}
|
|
16586
|
+
});
|
|
16587
|
+
}
|
|
16588
|
+
async function expireHosted(where, ref, until, reason) {
|
|
16589
|
+
const brain = await openCloudBrain(where);
|
|
16590
|
+
const result = await cloudExpire(brain, {
|
|
16591
|
+
ref,
|
|
16592
|
+
until,
|
|
16593
|
+
...reason === void 0 ? {} : { reason }
|
|
16594
|
+
});
|
|
16595
|
+
printDual({
|
|
16596
|
+
json: { brain: where.name, ...result },
|
|
16597
|
+
human: () => {
|
|
16598
|
+
printHuman(`Holds until **${oneLine(result.valid_until, WIDTH.title)}**.
|
|
16599
|
+
`);
|
|
16600
|
+
printHuman(`\`${oneLine(result.target, WIDTH.path)}\`
|
|
16601
|
+
`);
|
|
16602
|
+
printHuman(c.gray(oneLine(result.note, WIDTH.text)));
|
|
16603
|
+
printHuman(c.gray(`
|
|
16604
|
+
Undo: \`memgineering undo ${result.op_id}\``));
|
|
16605
|
+
}
|
|
16606
|
+
});
|
|
16607
|
+
}
|
|
16608
|
+
|
|
16609
|
+
// src/commands/evidence.ts
|
|
16610
|
+
init_src();
|
|
16611
|
+
import { Command as Command16 } from "commander";
|
|
16612
|
+
init_index_build();
|
|
16613
|
+
init_cloud_brain();
|
|
16614
|
+
init_vault();
|
|
16615
|
+
init_ui();
|
|
16616
|
+
function evidenceCommand() {
|
|
16617
|
+
return new Command16("evidence").description("how much a memory has been used, and why it last changed").argument("<ref>", "handle, id, path, or exact title").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
16618
|
+
"--brain <name>",
|
|
16619
|
+
"read from one existing hosted brain by name or id, without changing the pointer"
|
|
16620
|
+
).action(async (ref, opts) => {
|
|
16621
|
+
const target = await resolveTarget({
|
|
16622
|
+
...opts.brain === void 0 ? {} : { brain: opts.brain },
|
|
16623
|
+
...opts.local === void 0 ? {} : { local: opts.local },
|
|
16624
|
+
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
16625
|
+
});
|
|
16626
|
+
if (target.kind === "cloud") {
|
|
16627
|
+
const cloud = await openCloudBrain(target);
|
|
16628
|
+
const report4 = await cloudEvidence(cloud, ref);
|
|
16629
|
+
printDual({
|
|
16630
|
+
json: { ...report4, brain: { hosted: true, name: target.name } },
|
|
16631
|
+
human: () => renderEvidence(viewFromCloud(report4), {
|
|
16632
|
+
now: /* @__PURE__ */ new Date(),
|
|
16633
|
+
hosted: true,
|
|
16634
|
+
brain: target.name
|
|
16635
|
+
})
|
|
16636
|
+
});
|
|
16637
|
+
return;
|
|
16638
|
+
}
|
|
16639
|
+
const brain = target.brain;
|
|
16640
|
+
const adapter = await openBrainOrExplain(brain.root);
|
|
16641
|
+
const built = await openIndex(adapter, brain.root);
|
|
16642
|
+
const entry = resolveRef(built.entries, ref);
|
|
16643
|
+
const [events, ledger] = await Promise.all([readEvents(brain.root), readOps(brain.root)]);
|
|
16644
|
+
const now = /* @__PURE__ */ new Date();
|
|
16645
|
+
const report3 = assembleEvidence(entry, events, ledger.ops, {
|
|
16646
|
+
contextDir: process.cwd(),
|
|
16647
|
+
now,
|
|
16648
|
+
brain: built.entries
|
|
16649
|
+
});
|
|
16650
|
+
printDual({
|
|
16651
|
+
json: report3,
|
|
16652
|
+
human: () => renderEvidence(report3, { now, hosted: false })
|
|
16653
|
+
});
|
|
16654
|
+
});
|
|
16655
|
+
}
|
|
16656
|
+
function renderEvidence(report3, opts) {
|
|
16657
|
+
const brainHeading = opts.brain === void 0 ? "" : ` \u2014 ${oneLine(opts.brain, WIDTH.title)}`;
|
|
16658
|
+
printHuman(`## evidence: ${oneLine(report3.title, WIDTH.title)}${brainHeading}
|
|
16659
|
+
`);
|
|
16660
|
+
printHuman(
|
|
16661
|
+
`\`id: ${oneLine(report3.id, WIDTH.handle)}\` \xB7 \`${oneLine(report3.path, WIDTH.path)}\`
|
|
15558
16662
|
`
|
|
15559
16663
|
);
|
|
15560
16664
|
if (report3.critical) {
|
|
@@ -15801,7 +16905,7 @@ init_config();
|
|
|
15801
16905
|
import { createHash as createHash7 } from "crypto";
|
|
15802
16906
|
import { mkdir as mkdir11, readdir as readdir6, stat as stat4, unlink as unlink3, writeFile as writeFile9 } from "fs/promises";
|
|
15803
16907
|
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join18, resolve as resolve9 } from "path";
|
|
15804
|
-
import { Command as
|
|
16908
|
+
import { Command as Command17 } from "commander";
|
|
15805
16909
|
|
|
15806
16910
|
// src/lib/rules.ts
|
|
15807
16911
|
init_src();
|
|
@@ -15872,7 +16976,7 @@ init_src();
|
|
|
15872
16976
|
init_deadline();
|
|
15873
16977
|
init_cloud_brain();
|
|
15874
16978
|
function guardCommand() {
|
|
15875
|
-
return new
|
|
16979
|
+
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) => {
|
|
15876
16980
|
try {
|
|
15877
16981
|
const out = await withDeadline(HOOK_DEADLINE_MS, async () => {
|
|
15878
16982
|
const payload = await readPayload();
|
|
@@ -16068,7 +17172,11 @@ async function hostedRuleLines(where, contextDir) {
|
|
|
16068
17172
|
}
|
|
16069
17173
|
function groupHostedLines(rules, project) {
|
|
16070
17174
|
const width = ruleWidthFor(rules.length);
|
|
16071
|
-
const line = (r) =>
|
|
17175
|
+
const line = (r) => {
|
|
17176
|
+
const quoted = renderRuleLine(r.text ?? r.summary ?? r.title, width);
|
|
17177
|
+
return r.freshness === "current" || r.freshness === void 0 ? quoted : `${quoted}
|
|
17178
|
+
(${r.freshness === "expired" ? "past its end date" : "superseded"} \u2014 worth checking)`;
|
|
17179
|
+
};
|
|
16072
17180
|
const globals = rules.filter((r) => r.lane === "global");
|
|
16073
17181
|
const scoped = rules.filter((r) => r.lane === "project");
|
|
16074
17182
|
if (globals.length === 0 || scoped.length === 0) return rules.map(line);
|
|
@@ -16089,14 +17197,14 @@ init_link();
|
|
|
16089
17197
|
init_src();
|
|
16090
17198
|
init_walk();
|
|
16091
17199
|
import { lstat as lstat4, readFile as readFile19 } from "fs/promises";
|
|
16092
|
-
import { Command as
|
|
17200
|
+
import { Command as Command19 } from "commander";
|
|
16093
17201
|
init_errors();
|
|
16094
17202
|
init_cloud_brain();
|
|
16095
17203
|
|
|
16096
17204
|
// src/commands/retract.ts
|
|
16097
17205
|
init_errors();
|
|
16098
17206
|
init_cloud_brain();
|
|
16099
|
-
import { Command as
|
|
17207
|
+
import { Command as Command18 } from "commander";
|
|
16100
17208
|
init_vault();
|
|
16101
17209
|
init_ui();
|
|
16102
17210
|
var CONTENT_VERBS = /* @__PURE__ */ new Set([
|
|
@@ -16167,7 +17275,7 @@ function parseInstant(flag2, raw) {
|
|
|
16167
17275
|
return { ms, iso: new Date(ms).toISOString() };
|
|
16168
17276
|
}
|
|
16169
17277
|
function retractCommand() {
|
|
16170
|
-
return new
|
|
17278
|
+
return new Command18("retract").description(
|
|
16171
17279
|
"retire every memory one writer or one stretch of time wrote \u2014 shows the set first; --yes applies it"
|
|
16172
17280
|
).option("--actor <actor>", 'writes recorded under this actor, e.g. "mcp:ChatGPT" or "agent"').option(
|
|
16173
17281
|
"--token <id>",
|
|
@@ -16382,7 +17490,7 @@ ${failed.length} could not be retired:`);
|
|
|
16382
17490
|
init_vault();
|
|
16383
17491
|
init_ui();
|
|
16384
17492
|
function logCommand() {
|
|
16385
|
-
return new
|
|
17493
|
+
return new Command19("log").description("what changed in this brain, and what can still be undone").option("--limit <n>", "how many operations", (v) => parseInt(v, 10), 20).option("--verbose", "include hashes and reverse patches").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").option(
|
|
16386
17494
|
"--brain <name>",
|
|
16387
17495
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
16388
17496
|
).option("--actor <actor>", 'only operations recorded under this actor, e.g. "mcp:ChatGPT" or "agent"').option("--token <id>", "only operations made with this token (id or 5+ char prefix) \u2014 hosted brains").option("--since <when>", "only operations at or after this date / ISO instant").option("--until <when>", "only operations before this date / ISO instant").action(async (opts) => {
|
|
@@ -16563,9 +17671,19 @@ var REVERSIBLE_LOCAL = /* @__PURE__ */ new Set([
|
|
|
16563
17671
|
"retire",
|
|
16564
17672
|
"unretire",
|
|
16565
17673
|
"exclude",
|
|
16566
|
-
"unexclude"
|
|
17674
|
+
"unexclude",
|
|
17675
|
+
"rescope",
|
|
17676
|
+
"expire"
|
|
17677
|
+
]);
|
|
17678
|
+
var REVERSIBLE_HOSTED = /* @__PURE__ */ new Set([
|
|
17679
|
+
"remember",
|
|
17680
|
+
"revise",
|
|
17681
|
+
"retire",
|
|
17682
|
+
"unretire",
|
|
17683
|
+
"write",
|
|
17684
|
+
"rescope",
|
|
17685
|
+
"expire"
|
|
16567
17686
|
]);
|
|
16568
|
-
var REVERSIBLE_HOSTED = /* @__PURE__ */ new Set(["remember", "revise", "retire", "unretire", "write"]);
|
|
16569
17687
|
function undoable(op, hosted) {
|
|
16570
17688
|
if (op.undone || op.diverged || op.superseded) return false;
|
|
16571
17689
|
return (hosted ? REVERSIBLE_HOSTED : REVERSIBLE_LOCAL).has(op.verb);
|
|
@@ -16650,10 +17768,10 @@ async function readOrNull(root, path) {
|
|
|
16650
17768
|
// src/commands/project.ts
|
|
16651
17769
|
init_errors();
|
|
16652
17770
|
import { resolve as resolve10 } from "path";
|
|
16653
|
-
import { Command as
|
|
17771
|
+
import { Command as Command20 } from "commander";
|
|
16654
17772
|
init_ui();
|
|
16655
17773
|
function projectCommand() {
|
|
16656
|
-
return new
|
|
17774
|
+
return new Command20("project").description("show or set what the work in this folder is called").argument("[name]", "what to call it \u2014 omit to show what this folder answers to").option("--here <dir>", "act on this directory instead of the current one").option("--unset", "forget the name set here and go back to what the repository says").addHelpText(
|
|
16657
17775
|
"after",
|
|
16658
17776
|
[
|
|
16659
17777
|
"",
|
|
@@ -16780,18 +17898,18 @@ init_api_client();
|
|
|
16780
17898
|
init_credentials();
|
|
16781
17899
|
init_pending_login();
|
|
16782
17900
|
init_ui();
|
|
16783
|
-
import { Command as
|
|
17901
|
+
import { Command as Command22 } from "commander";
|
|
16784
17902
|
function logoutCommand() {
|
|
16785
|
-
return new
|
|
17903
|
+
return new Command22("logout").description("sign out of your account \u2014 the pointer and your notes stay").option("--all", "revoke every token on the account, not just this machine\u2019s").option(
|
|
16786
17904
|
"--local",
|
|
16787
17905
|
"forget the token here without telling the server \u2014 only for a server you cannot reach"
|
|
16788
17906
|
).action(async (opts) => {
|
|
16789
|
-
const
|
|
17907
|
+
const base2 = apiUrl();
|
|
16790
17908
|
const credentials = await readCredentials();
|
|
16791
17909
|
await clearPendingLogin();
|
|
16792
17910
|
if (!credentials) {
|
|
16793
17911
|
printDual({
|
|
16794
|
-
json: { signed_out: true, was_signed_in: false, api_url:
|
|
17912
|
+
json: { signed_out: true, was_signed_in: false, api_url: base2 },
|
|
16795
17913
|
human: () => printHuman("Not signed in \u2014 nothing to do.")
|
|
16796
17914
|
});
|
|
16797
17915
|
return;
|
|
@@ -16901,12 +18019,12 @@ async function revokeRegistrySession(reg) {
|
|
|
16901
18019
|
|
|
16902
18020
|
// src/commands/onboard.ts
|
|
16903
18021
|
init_src();
|
|
16904
|
-
import { Command as
|
|
18022
|
+
import { Command as Command23 } from "commander";
|
|
16905
18023
|
init_cloud_brain();
|
|
16906
18024
|
init_vault();
|
|
16907
18025
|
init_ui();
|
|
16908
18026
|
function onboardCommand() {
|
|
16909
|
-
return new
|
|
18027
|
+
return new Command23("onboard").description("what to ask so the brain knows who it belongs to").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
16910
18028
|
"--brain <name>",
|
|
16911
18029
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
16912
18030
|
).action(async (opts) => {
|
|
@@ -17052,9 +18170,9 @@ init_cloud_brain();
|
|
|
17052
18170
|
init_ui();
|
|
17053
18171
|
import { mkdir as mkdir12, readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
17054
18172
|
import { dirname as dirname6, isAbsolute as isAbsolute4, join as join19, resolve as resolve11, sep as sep2 } from "path";
|
|
17055
|
-
import { Command as
|
|
18173
|
+
import { Command as Command24 } from "commander";
|
|
17056
18174
|
function pullCommand() {
|
|
17057
|
-
return new
|
|
18175
|
+
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) => {
|
|
17058
18176
|
const { brain: source, others } = await resolveSource(opts.brain);
|
|
17059
18177
|
const target = resolve11(destination ?? `./${oneLine(source.name, WIDTH.title)}`);
|
|
17060
18178
|
const first = await cloudListNotes(source, { limit: 1 });
|
|
@@ -17181,9 +18299,9 @@ async function collectPaths(source) {
|
|
|
17181
18299
|
return all;
|
|
17182
18300
|
}
|
|
17183
18301
|
async function resolveSource(named) {
|
|
17184
|
-
const
|
|
17185
|
-
const credentials = await requireCredentials(
|
|
17186
|
-
const brains = await listCloudBrains(credentials.token,
|
|
18302
|
+
const base2 = apiUrl();
|
|
18303
|
+
const credentials = await requireCredentials(base2);
|
|
18304
|
+
const brains = await listCloudBrains(credentials.token, base2);
|
|
17187
18305
|
if (brains.length === 0) {
|
|
17188
18306
|
throw memgError(
|
|
17189
18307
|
"invalid_input",
|
|
@@ -17192,7 +18310,7 @@ async function resolveSource(named) {
|
|
|
17192
18310
|
);
|
|
17193
18311
|
}
|
|
17194
18312
|
const pointedAt = named ? null : (await loadConfig()).brain.cloud;
|
|
17195
|
-
const usable = pointedAt && pointedAt.api_url ===
|
|
18313
|
+
const usable = pointedAt && pointedAt.api_url === base2 ? pointedAt.id : null;
|
|
17196
18314
|
const found = named ? brains.find((b) => b.name === named || b.id === named) : usable ? brains.find((b) => b.id === usable) ?? (brains.length === 1 ? brains[0] : void 0) : brains.length === 1 ? brains[0] : void 0;
|
|
17197
18315
|
if (!found) {
|
|
17198
18316
|
throw memgError(
|
|
@@ -17202,7 +18320,7 @@ async function resolveSource(named) {
|
|
|
17202
18320
|
);
|
|
17203
18321
|
}
|
|
17204
18322
|
return {
|
|
17205
|
-
brain: { brainId: found.id, name: found.name, token: credentials.token, apiUrl:
|
|
18323
|
+
brain: { brainId: found.id, name: found.name, token: credentials.token, apiUrl: base2 },
|
|
17206
18324
|
// The ones this run is NOT downloading.
|
|
17207
18325
|
//
|
|
17208
18326
|
// A tester pulled a brain, was told 21 notes had arrived, and reasonably
|
|
@@ -17254,7 +18372,7 @@ async function exists(file) {
|
|
|
17254
18372
|
}
|
|
17255
18373
|
|
|
17256
18374
|
// src/commands/publish.ts
|
|
17257
|
-
import { Command as
|
|
18375
|
+
import { Command as Command26 } from "commander";
|
|
17258
18376
|
import { z as z32 } from "zod";
|
|
17259
18377
|
init_src();
|
|
17260
18378
|
init_errors();
|
|
@@ -17398,6 +18516,7 @@ init_index_build();
|
|
|
17398
18516
|
// src/lib/registry-session.ts
|
|
17399
18517
|
init_errors();
|
|
17400
18518
|
init_credentials();
|
|
18519
|
+
init_version();
|
|
17401
18520
|
function registryApiUrl(stored) {
|
|
17402
18521
|
const override = process.env["KORDIS_API_URL"];
|
|
17403
18522
|
return override !== void 0 && override !== "" ? override : stored;
|
|
@@ -18150,7 +19269,7 @@ function isSameBrainNote(publishedOrigin, memoryId) {
|
|
|
18150
19269
|
}
|
|
18151
19270
|
|
|
18152
19271
|
// src/commands/registry/read.ts
|
|
18153
|
-
import { Command as
|
|
19272
|
+
import { Command as Command25 } from "commander";
|
|
18154
19273
|
init_errors();
|
|
18155
19274
|
init_credentials();
|
|
18156
19275
|
|
|
@@ -18171,8 +19290,9 @@ function untrustedFenceNotice(sentinel) {
|
|
|
18171
19290
|
|
|
18172
19291
|
// src/commands/registry/read.ts
|
|
18173
19292
|
init_ui();
|
|
19293
|
+
init_version();
|
|
18174
19294
|
function readCommand() {
|
|
18175
|
-
return new
|
|
19295
|
+
return new Command25("read").description("read a judgment from the public layer into your context (nothing is installed)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was read").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
|
|
18176
19296
|
"after",
|
|
18177
19297
|
"\nEnvironment:\n KORDIS_API_URL=<url> read from this registry instead of the linked one\n"
|
|
18178
19298
|
).action(async (rawSlug, opts) => {
|
|
@@ -18320,7 +19440,7 @@ async function recordUsage(verb, detail, where = {}) {
|
|
|
18320
19440
|
init_vault();
|
|
18321
19441
|
init_ui();
|
|
18322
19442
|
function publishCommand() {
|
|
18323
|
-
return new
|
|
19443
|
+
return new Command26("publish").description("publish one decision from this brain to the public layer").argument("<ref>", "handle, id, path, or exact title of the note to publish").option("--slug <name>", "the package name (default: derived from the note title)").option("--yes", "actually send it (without this, nothing leaves this machine)").option("--reason <text>", "why this is being published \u2014 the user's own words").option("--no-lineage", "do not propose lineage from what this machine has adapted").option(
|
|
18324
19444
|
"--bump <kind>",
|
|
18325
19445
|
"patch | minor | major \u2014 publish the next version of a package this note already produced"
|
|
18326
19446
|
).option("--wait <ms>", "how long to wait for the AI review before answering (default 3000)").option("--vault <path>", "which brain to read").option("--local", "read the brain on this machine, even when signed in to a hosted one").addHelpText(
|
|
@@ -18393,8 +19513,8 @@ function publishCommand() {
|
|
|
18393
19513
|
});
|
|
18394
19514
|
return;
|
|
18395
19515
|
}
|
|
18396
|
-
const session = await openRegistrySession();
|
|
18397
19516
|
const waitForReviewMs = parseWait(opts.wait);
|
|
19517
|
+
const session = await openRegistrySession();
|
|
18398
19518
|
const response = await publishOrExplain(
|
|
18399
19519
|
session.client,
|
|
18400
19520
|
ref,
|
|
@@ -18646,7 +19766,7 @@ function parseWait(raw) {
|
|
|
18646
19766
|
}
|
|
18647
19767
|
|
|
18648
19768
|
// src/commands/registry/adapt.ts
|
|
18649
|
-
import { Command as
|
|
19769
|
+
import { Command as Command27 } from "commander";
|
|
18650
19770
|
init_errors();
|
|
18651
19771
|
|
|
18652
19772
|
// src/lib/harness-context.ts
|
|
@@ -18655,6 +19775,7 @@ import { access, readFile as readFile21 } from "fs/promises";
|
|
|
18655
19775
|
import { join as join20 } from "path";
|
|
18656
19776
|
var PER_FILE_CAP = 16 * 1024;
|
|
18657
19777
|
var TOTAL_CAP = 48 * 1024;
|
|
19778
|
+
var TRUNCATION_RESERVE = 64;
|
|
18658
19779
|
async function pathExists(p) {
|
|
18659
19780
|
try {
|
|
18660
19781
|
await access(p);
|
|
@@ -18665,6 +19786,7 @@ async function pathExists(p) {
|
|
|
18665
19786
|
}
|
|
18666
19787
|
function capContent(raw, cap) {
|
|
18667
19788
|
if (Buffer.byteLength(raw, "utf8") <= cap) return { content: raw, truncated: false };
|
|
19789
|
+
const fill = cap - TRUNCATION_RESERVE;
|
|
18668
19790
|
const lines2 = raw.split("\n");
|
|
18669
19791
|
let acc = 0;
|
|
18670
19792
|
let lastHeadingIdx = -1;
|
|
@@ -18673,7 +19795,7 @@ function capContent(raw, cap) {
|
|
|
18673
19795
|
const line = lines2[i] ?? "";
|
|
18674
19796
|
const lineBytes = Buffer.byteLength(`${line}
|
|
18675
19797
|
`, "utf8");
|
|
18676
|
-
if (acc + lineBytes >
|
|
19798
|
+
if (acc + lineBytes > fill) {
|
|
18677
19799
|
cutIdx = i;
|
|
18678
19800
|
break;
|
|
18679
19801
|
}
|
|
@@ -18681,6 +19803,7 @@ function capContent(raw, cap) {
|
|
|
18681
19803
|
if (/^#{1,6}\s/.test(line)) lastHeadingIdx = i;
|
|
18682
19804
|
}
|
|
18683
19805
|
const cut2 = lastHeadingIdx > cutIdx / 2 ? lastHeadingIdx : cutIdx;
|
|
19806
|
+
if (cut2 === 0) return null;
|
|
18684
19807
|
const kept = lines2.slice(0, cut2).join("\n");
|
|
18685
19808
|
return {
|
|
18686
19809
|
content: `${kept}
|
|
@@ -18705,7 +19828,6 @@ async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
|
18705
19828
|
const files2 = [];
|
|
18706
19829
|
let totalBytes = 0;
|
|
18707
19830
|
for (const cand of candidates) {
|
|
18708
|
-
if (totalBytes >= TOTAL_CAP) break;
|
|
18709
19831
|
if (!await pathExists(cand.path)) continue;
|
|
18710
19832
|
let raw;
|
|
18711
19833
|
try {
|
|
@@ -18714,7 +19836,9 @@ async function readHarnessContext(cwd = process.cwd(), home = agentHome()) {
|
|
|
18714
19836
|
continue;
|
|
18715
19837
|
}
|
|
18716
19838
|
const cap = Math.min(PER_FILE_CAP, TOTAL_CAP - totalBytes);
|
|
18717
|
-
const
|
|
19839
|
+
const capped2 = capContent(raw, cap);
|
|
19840
|
+
if (capped2 === null) break;
|
|
19841
|
+
const { content, truncated } = capped2;
|
|
18718
19842
|
totalBytes += Buffer.byteLength(content, "utf8");
|
|
18719
19843
|
files2.push({
|
|
18720
19844
|
path: cand.display,
|
|
@@ -18778,7 +19902,7 @@ function renderBrief(res, harness) {
|
|
|
18778
19902
|
return out.join("\n");
|
|
18779
19903
|
}
|
|
18780
19904
|
function adaptCommand() {
|
|
18781
|
-
return new
|
|
19905
|
+
return new Command27("adapt").description("fit a public judgment to your existing harness (prints a brief; writes nothing)").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").option("--allow-pending", "allow versions whose AI review has not passed", false).option("--vault <path>", "which brain records that this was adapted").option("--local", "record it beside the brain on this machine, not the hosted one").addHelpText(
|
|
18782
19906
|
"after",
|
|
18783
19907
|
"\nEnvironment:\n KORDIS_API_URL=<url> read from this registry instead of the linked one\n"
|
|
18784
19908
|
).action(async (rawSlug, opts) => {
|
|
@@ -18826,10 +19950,10 @@ function adaptCommand() {
|
|
|
18826
19950
|
}
|
|
18827
19951
|
|
|
18828
19952
|
// src/commands/registry/info.ts
|
|
18829
|
-
import { Command as
|
|
19953
|
+
import { Command as Command28 } from "commander";
|
|
18830
19954
|
init_ui();
|
|
18831
19955
|
function infoCommand() {
|
|
18832
|
-
return new
|
|
19956
|
+
return new Command28("info").description("what a package in the public layer is, without reading it").argument("<full_slug>", "package identifier {username}/{slug} or {username}/{slug}@{version}").addHelpText(
|
|
18833
19957
|
"after",
|
|
18834
19958
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
18835
19959
|
).action(async (rawSlug) => {
|
|
@@ -18915,7 +20039,7 @@ function renderCanonical(component) {
|
|
|
18915
20039
|
}
|
|
18916
20040
|
|
|
18917
20041
|
// src/commands/registry/search.ts
|
|
18918
|
-
import { Command as
|
|
20042
|
+
import { Command as Command29 } from "commander";
|
|
18919
20043
|
init_errors();
|
|
18920
20044
|
init_ui();
|
|
18921
20045
|
var SEARCHABLE_TYPES = PHASE_1_TYPES;
|
|
@@ -18923,7 +20047,7 @@ var LIMIT_MIN = 1;
|
|
|
18923
20047
|
var LIMIT_MAX = 100;
|
|
18924
20048
|
var LIMIT_DEFAULT = 20;
|
|
18925
20049
|
function searchCommand() {
|
|
18926
|
-
return new
|
|
20050
|
+
return new Command29("search").description("find a judgment in the public layer (titles only; nothing is read or installed)").argument("[query...]", "words to match against title and description").option("--type <type>", `narrow by kind: ${SEARCHABLE_TYPES.join("|")}`).option("--limit <n>", `how many results (${LIMIT_MIN}-${LIMIT_MAX})`, String(LIMIT_DEFAULT)).addHelpText(
|
|
18927
20051
|
"after",
|
|
18928
20052
|
"\nEnvironment:\n KORDIS_API_URL=<url> search this registry instead of the linked one\n"
|
|
18929
20053
|
).action(async (query, opts) => {
|
|
@@ -19101,7 +20225,7 @@ function parseLimit2(raw) {
|
|
|
19101
20225
|
}
|
|
19102
20226
|
|
|
19103
20227
|
// src/commands/registry/pull.ts
|
|
19104
|
-
import { Command as
|
|
20228
|
+
import { Command as Command30 } from "commander";
|
|
19105
20229
|
import prompts3 from "prompts";
|
|
19106
20230
|
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
19107
20231
|
import { mkdir as mkdir14, readFile as readFile23, stat as stat5, writeFile as writeFile12 } from "fs/promises";
|
|
@@ -20040,7 +21164,7 @@ async function doPullInstall(fullSlug, tool, installRoot, opts = {}) {
|
|
|
20040
21164
|
};
|
|
20041
21165
|
}
|
|
20042
21166
|
function registryPullCommand() {
|
|
20043
|
-
return new
|
|
21167
|
+
return new Command30("pull").description("install a package from the public layer into this project").argument(
|
|
20044
21168
|
"<full_slug>",
|
|
20045
21169
|
"package identifier {username}/{slug}, optionally @{version} or @{range}"
|
|
20046
21170
|
).option("--tool <name>", "target tool format (default: work it out from this directory)").option("--target <path>", "install into this directory instead of the current one").option("--with-recommends", "also install recommended (depth-0) dependencies", false).option(
|
|
@@ -20269,10 +21393,10 @@ async function applyEmittedFile(abs, f, fullSlug, notes) {
|
|
|
20269
21393
|
// src/commands/registry/update.ts
|
|
20270
21394
|
init_errors();
|
|
20271
21395
|
init_ui();
|
|
20272
|
-
import { Command as
|
|
21396
|
+
import { Command as Command31 } from "commander";
|
|
20273
21397
|
import semver2 from "semver";
|
|
20274
21398
|
function registryUpdateCommand() {
|
|
20275
|
-
return new
|
|
21399
|
+
return new Command31("update").description("update installed packages to their latest in-range versions").argument("[full_slug]", "package identifier {username}/{slug} (omit to update all)").option("--dry-run", "show what would be updated without doing it", false).addHelpText(
|
|
20276
21400
|
"after",
|
|
20277
21401
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
20278
21402
|
).action(async (fullSlugArg, opts) => {
|
|
@@ -20406,10 +21530,10 @@ function registryUpdateCommand() {
|
|
|
20406
21530
|
|
|
20407
21531
|
// src/commands/registry/outdated.ts
|
|
20408
21532
|
init_ui();
|
|
20409
|
-
import { Command as
|
|
21533
|
+
import { Command as Command32 } from "commander";
|
|
20410
21534
|
import semver3 from "semver";
|
|
20411
21535
|
function outdatedCommand() {
|
|
20412
|
-
return new
|
|
21536
|
+
return new Command32("outdated").description("check installed packages for available updates").addHelpText(
|
|
20413
21537
|
"after",
|
|
20414
21538
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
20415
21539
|
).action(async () => {
|
|
@@ -20501,9 +21625,9 @@ function outdatedCommand() {
|
|
|
20501
21625
|
// src/commands/registry/versions.ts
|
|
20502
21626
|
init_errors();
|
|
20503
21627
|
init_ui();
|
|
20504
|
-
import { Command as
|
|
21628
|
+
import { Command as Command33 } from "commander";
|
|
20505
21629
|
function versionsCommand() {
|
|
20506
|
-
return new
|
|
21630
|
+
return new Command33("versions").description("list every published version of a package").argument("<full_slug>", "package identifier {username}/{slug}").addHelpText(
|
|
20507
21631
|
"after",
|
|
20508
21632
|
"\nEnvironment:\n KORDIS_API_URL=<url> ask this registry instead of the linked one\n"
|
|
20509
21633
|
).action(async (fullSlug) => {
|
|
@@ -20567,9 +21691,9 @@ function relativeTime(isoString) {
|
|
|
20567
21691
|
// src/commands/registry/diff.ts
|
|
20568
21692
|
init_errors();
|
|
20569
21693
|
init_ui();
|
|
20570
|
-
import { Command as
|
|
21694
|
+
import { Command as Command34 } from "commander";
|
|
20571
21695
|
function diffCommand() {
|
|
20572
|
-
return new
|
|
21696
|
+
return new Command34("diff").description("show what changed between two versions of a package").argument(
|
|
20573
21697
|
"<full_slug>",
|
|
20574
21698
|
"package identifier {username}/{slug}, or {username}/{slug}@{from}..{to}"
|
|
20575
21699
|
).argument("[from]", "older version (omit to use the second-latest)").argument("[to]", "newer version (omit to use the latest)").addHelpText(
|
|
@@ -20646,7 +21770,7 @@ function diffCommand() {
|
|
|
20646
21770
|
}
|
|
20647
21771
|
|
|
20648
21772
|
// src/commands/registry/list.ts
|
|
20649
|
-
import { Command as
|
|
21773
|
+
import { Command as Command35 } from "commander";
|
|
20650
21774
|
init_errors();
|
|
20651
21775
|
init_ui();
|
|
20652
21776
|
var SORT_KEYWORDS = ["latest", "popular", "recent", "updated", "stars", "downloads"];
|
|
@@ -20669,7 +21793,7 @@ function parseUsername(raw) {
|
|
|
20669
21793
|
return parsed.data;
|
|
20670
21794
|
}
|
|
20671
21795
|
function listCommand() {
|
|
20672
|
-
return new
|
|
21796
|
+
return new Command35("list").description("list packages \u2014 your own with no argument, or a public user\u2019s").argument("[username]", "whose packages to list (omit for your own)").option("--limit <n>", "maximum results per page", (v) => parseInt(v, 10), 20).option("--page <n>", "page number for your own packages (1-based)", (v) => parseInt(v, 10), 1).option("--cursor <token>", "continue a public listing from a previous result").option(
|
|
20673
21797
|
"--sort <mode>",
|
|
20674
21798
|
"latest|popular for your own; stars|downloads|recent|updated for a public listing",
|
|
20675
21799
|
"latest"
|
|
@@ -20767,7 +21891,7 @@ init_push();
|
|
|
20767
21891
|
// src/commands/recall.ts
|
|
20768
21892
|
init_src();
|
|
20769
21893
|
init_config();
|
|
20770
|
-
import { Command as
|
|
21894
|
+
import { Command as Command37 } from "commander";
|
|
20771
21895
|
import { basename as basename2, resolve as resolve13 } from "path";
|
|
20772
21896
|
init_index_build();
|
|
20773
21897
|
init_cloud_brain();
|
|
@@ -20775,7 +21899,7 @@ init_errors();
|
|
|
20775
21899
|
init_vault();
|
|
20776
21900
|
init_ui();
|
|
20777
21901
|
function recallCommand() {
|
|
20778
|
-
return new
|
|
21902
|
+
return new Command37("recall").description("recall memory cards for a question").argument("<query>", "what you want to remember").option("--vault <path>", "which brain to search").option("--local", "search the brain on this machine, even when signed in to a hosted one").option(
|
|
20779
21903
|
"--brain <name>",
|
|
20780
21904
|
"search one existing hosted brain by name or id, without changing the pointer"
|
|
20781
21905
|
).option("--scope <scope>", "restrict to one scope").option("--limit <n>", "max cards", (v) => parseInt(v, 10), 8).option(
|
|
@@ -20797,6 +21921,8 @@ function recallCommand() {
|
|
|
20797
21921
|
let unsearchedLocal = [];
|
|
20798
21922
|
let searchedLocalName = null;
|
|
20799
21923
|
let isScaffolding;
|
|
21924
|
+
let templatesWithheld = 0;
|
|
21925
|
+
let spread = null;
|
|
20800
21926
|
let reasons;
|
|
20801
21927
|
let pathOf;
|
|
20802
21928
|
let entries;
|
|
@@ -20825,13 +21951,25 @@ function recallCommand() {
|
|
|
20825
21951
|
// older clients: the route applies a ceiling only when one arrives,
|
|
20826
21952
|
// so a CLI that predates this field gets the whole ranking rather
|
|
20827
21953
|
// than a trimmed packet it cannot describe.
|
|
20828
|
-
budget
|
|
21954
|
+
budget,
|
|
21955
|
+
// Two, like a folder brain. The server holds the content the check
|
|
21956
|
+
// needs; a server older than the field ignores it and caps nothing.
|
|
21957
|
+
templateSlots: TEMPLATE_SLOTS
|
|
20829
21958
|
});
|
|
20830
|
-
|
|
21959
|
+
const unfilledIds = new Set(
|
|
21960
|
+
answered.memory_cards.filter((card) => card.unfilled === true).map((card) => card.id)
|
|
21961
|
+
);
|
|
21962
|
+
rows = answered.memory_cards.map(({ unfilled: _unfilled, ...card }) => ({
|
|
21963
|
+
card,
|
|
21964
|
+
handle: card.handle,
|
|
21965
|
+
stage: {}
|
|
21966
|
+
}));
|
|
21967
|
+
spread = isScopeSpread(answered.scope_spread) ? answered.scope_spread : null;
|
|
21968
|
+
templatesWithheld = Number.isInteger(answered.templates_withheld) && (answered.templates_withheld ?? 0) > 0 ? answered.templates_withheld : 0;
|
|
20831
21969
|
if (rows.length === 0) {
|
|
20832
21970
|
unsearchedBrains = await listCloudBrains(cloud.token, cloud.apiUrl, { retries: 0 }).then((all) => all.filter((b) => b.id !== cloud.brainId)).catch(() => []);
|
|
20833
21971
|
}
|
|
20834
|
-
isScaffolding = () =>
|
|
21972
|
+
isScaffolding = (card) => unfilledIds.has(card.id);
|
|
20835
21973
|
reasons = /* @__PURE__ */ new Map();
|
|
20836
21974
|
pathOf = () => null;
|
|
20837
21975
|
entries = null;
|
|
@@ -20861,8 +21999,28 @@ function recallCommand() {
|
|
|
20861
21999
|
...depth === void 0 ? {} : { depth },
|
|
20862
22000
|
tokenBudget: budget
|
|
20863
22001
|
});
|
|
20864
|
-
|
|
22002
|
+
const byId = new Map(built.entries.map((e) => [e.memory.id, e]));
|
|
22003
|
+
const scaffolding = await scaffoldingAmong(
|
|
22004
|
+
adapter,
|
|
22005
|
+
local.memory_cards.map((card) => {
|
|
22006
|
+
const entry = byId.get(card.id);
|
|
22007
|
+
return { path: entry?.path ?? "", title: entry?.memory.title ?? "" };
|
|
22008
|
+
})
|
|
22009
|
+
);
|
|
22010
|
+
isScaffolding = (card) => scaffolding.has(byId.get(card.id)?.path ?? "");
|
|
22011
|
+
const capped2 = capTemplates(local.memory_cards, isScaffolding);
|
|
22012
|
+
templatesWithheld = capped2.withheld.length;
|
|
22013
|
+
result = { ...local, memory_cards: capped2.kept };
|
|
20865
22014
|
entries = built.entries;
|
|
22015
|
+
if (!opts.scope) {
|
|
22016
|
+
spread = scopeSpread(
|
|
22017
|
+
result.memory_cards.map((card) => {
|
|
22018
|
+
const memory = byId.get(card.id)?.memory;
|
|
22019
|
+
return memory === void 0 ? null : declaredScope(memory);
|
|
22020
|
+
}),
|
|
22021
|
+
built.entries.map((e) => declaredScope(e.memory))
|
|
22022
|
+
);
|
|
22023
|
+
}
|
|
20866
22024
|
if (result.memory_cards.length === 0) {
|
|
20867
22025
|
searchedLocalName = basename2(resolve13(brain.root));
|
|
20868
22026
|
unsearchedLocal = await loadConfig().then(
|
|
@@ -20882,7 +22040,6 @@ function recallCommand() {
|
|
|
20882
22040
|
returnedIds: result.memory_cards.map((card) => card.id)
|
|
20883
22041
|
});
|
|
20884
22042
|
const allIds = built.entries.map((e) => e.memory.id);
|
|
20885
|
-
const byId = new Map(built.entries.map((e) => [e.memory.id, e]));
|
|
20886
22043
|
pathOf = (card) => byId.get(card.id)?.path ?? null;
|
|
20887
22044
|
rows = await Promise.all(
|
|
20888
22045
|
result.memory_cards.map(async (card) => ({
|
|
@@ -20891,14 +22048,6 @@ function recallCommand() {
|
|
|
20891
22048
|
stage: await stageFor(adapter, byId.get(card.id), detail)
|
|
20892
22049
|
}))
|
|
20893
22050
|
);
|
|
20894
|
-
const scaffolding = await scaffoldingAmong(
|
|
20895
|
-
adapter,
|
|
20896
|
-
rows.map(({ card }) => {
|
|
20897
|
-
const entry = byId.get(card.id);
|
|
20898
|
-
return { path: entry?.path ?? "", title: entry?.memory.title ?? "" };
|
|
20899
|
-
})
|
|
20900
|
-
);
|
|
20901
|
-
isScaffolding = (card) => scaffolding.has(byId.get(card.id)?.path ?? "");
|
|
20902
22051
|
reasons = result.conflicts.length > 0 ? await conflictReasons(brain.root, result.conflicts, byId) : /* @__PURE__ */ new Map();
|
|
20903
22052
|
}
|
|
20904
22053
|
printDual({
|
|
@@ -20935,7 +22084,11 @@ function recallCommand() {
|
|
|
20935
22084
|
// Same order as `between`, so a parser can zip the three arrays.
|
|
20936
22085
|
reason: conflict.between.map((id) => reasons.get(id) ?? null)
|
|
20937
22086
|
})),
|
|
20938
|
-
...result.truncated === void 0 ? {} : { truncated: result.truncated }
|
|
22087
|
+
...result.truncated === void 0 ? {} : { truncated: result.truncated },
|
|
22088
|
+
// Counted, never silent: these matched and were left off because
|
|
22089
|
+
// they are still exactly what `init` wrote.
|
|
22090
|
+
...templatesWithheld > 0 ? { templates_withheld: templatesWithheld } : {},
|
|
22091
|
+
...spread === null ? {} : { scope_spread: spread }
|
|
20939
22092
|
},
|
|
20940
22093
|
human: () => {
|
|
20941
22094
|
const brainHeading = answeredBy === null ? "" : ` \u2014 ${oneLine(answeredBy, WIDTH.title)}`;
|
|
@@ -21027,20 +22180,48 @@ ${unsearchedLocal.map(
|
|
|
21027
22180
|
)
|
|
21028
22181
|
);
|
|
21029
22182
|
}
|
|
21030
|
-
|
|
21031
|
-
|
|
21032
|
-
({ card }) => card.confidence === "stated" && !isScaffolding(card)
|
|
21033
|
-
);
|
|
21034
|
-
for (const [i, { card, handle, stage }] of rows.entries()) {
|
|
21035
|
-
const mark = reachedSomethingWritten && isScaffolding(card);
|
|
22183
|
+
const templateCount = rows.filter(({ card }) => isScaffolding(card)).length;
|
|
22184
|
+
if (reachedSomethingWritten && templateCount > 0) {
|
|
21036
22185
|
printHuman(
|
|
21037
|
-
|
|
21038
|
-
|
|
21039
|
-
|
|
21040
|
-
|
|
21041
|
-
|
|
21042
|
-
|
|
21043
|
-
|
|
22186
|
+
c.gray(
|
|
22187
|
+
` ${templateCount} of ${rows.length} cards ${templateCount === 1 ? "is" : "are"} still the untouched starter template \u2014 marked "not filled in yet" below.
|
|
22188
|
+
`
|
|
22189
|
+
)
|
|
22190
|
+
);
|
|
22191
|
+
}
|
|
22192
|
+
if (spread !== null) {
|
|
22193
|
+
const others = spread.elsewhere.map((s) => `\`${oneLine(s, WIDTH.title)}\``).join(", ");
|
|
22194
|
+
const more = spread.elsewhere_total - spread.elsewhere.length;
|
|
22195
|
+
printHuman(
|
|
22196
|
+
c.gray(
|
|
22197
|
+
(spread.cards === spread.of ? ` ${spread.cards === 1 ? "The one card is" : `All ${spread.cards} cards are`} from \`${oneLine(spread.scope, WIDTH.title)}\`.` : ` Every card with a project is from \`${oneLine(spread.scope, WIDTH.title)}\` (${spread.cards} of ${spread.of}).`) + `
|
|
22198
|
+
Also in this brain: ${others}${more > 0 ? ` and ${more} more` : ""} \u2014 add \`--scope <name>\` to ask one of them.
|
|
22199
|
+
`
|
|
22200
|
+
)
|
|
22201
|
+
);
|
|
22202
|
+
}
|
|
22203
|
+
if (templatesWithheld > 0) {
|
|
22204
|
+
printHuman(
|
|
22205
|
+
c.gray(
|
|
22206
|
+
` ${templatesWithheld} more untouched template${templatesWithheld === 1 ? "" : "s"} matched and ${templatesWithheld === 1 ? "is" : "are"} not shown \u2014 at most ${TEMPLATE_SLOTS} per answer.
|
|
22207
|
+
`
|
|
22208
|
+
)
|
|
22209
|
+
);
|
|
22210
|
+
}
|
|
22211
|
+
if (otherScript) sayWhy();
|
|
22212
|
+
const glossOn = rows.findIndex(
|
|
22213
|
+
({ card }) => card.confidence === "stated" && !isScaffolding(card)
|
|
22214
|
+
);
|
|
22215
|
+
for (const [i, { card, handle, stage }] of rows.entries()) {
|
|
22216
|
+
const mark = reachedSomethingWritten && isScaffolding(card);
|
|
22217
|
+
printHuman(
|
|
22218
|
+
renderCard(
|
|
22219
|
+
i + 1,
|
|
22220
|
+
card,
|
|
22221
|
+
handle,
|
|
22222
|
+
detail,
|
|
22223
|
+
stage,
|
|
22224
|
+
i === glossOn,
|
|
21044
22225
|
mark,
|
|
21045
22226
|
// Only when `--brain` aimed this elsewhere. A recall of the
|
|
21046
22227
|
// brain this machine already points at mints handles `open`
|
|
@@ -21167,6 +22348,7 @@ function renderCard(n, card, handle, detail, stage, glossHere = false, scaffoldi
|
|
|
21167
22348
|
].filter(Boolean);
|
|
21168
22349
|
out.push(oneLine(marks.join(c.gray(" \xB7 ")), WIDTH.text));
|
|
21169
22350
|
}
|
|
22351
|
+
if (card.matched === "meaning") out.push(c.gray(MEANING_ONLY_NOTE));
|
|
21170
22352
|
if (glossHere && !scaffolding) {
|
|
21171
22353
|
out.push(c.gray("stated = these fields came from the note, not from inference"));
|
|
21172
22354
|
}
|
|
@@ -21249,17 +22431,21 @@ function dominantScriptOf(text) {
|
|
|
21249
22431
|
function scriptsIn(text) {
|
|
21250
22432
|
return new Set(scriptCounts(text).keys());
|
|
21251
22433
|
}
|
|
22434
|
+
function isScopeSpread(value) {
|
|
22435
|
+
const v = value;
|
|
22436
|
+
return typeof v === "object" && v !== null && typeof v.scope === "string" && Number.isInteger(v.cards) && Number.isInteger(v.of) && Number.isInteger(v.elsewhere_total) && Array.isArray(v.elsewhere) && v.elsewhere.every((s) => typeof s === "string");
|
|
22437
|
+
}
|
|
21252
22438
|
|
|
21253
22439
|
// src/commands/resurface.ts
|
|
21254
22440
|
init_deadline();
|
|
21255
22441
|
init_src();
|
|
21256
|
-
import { Command as
|
|
22442
|
+
import { Command as Command38 } from "commander";
|
|
21257
22443
|
init_index_build();
|
|
21258
22444
|
init_cloud_brain();
|
|
21259
22445
|
init_vault();
|
|
21260
22446
|
init_ui();
|
|
21261
22447
|
function resurfaceCommand() {
|
|
21262
|
-
return new
|
|
22448
|
+
return new Command38("resurface").description("what is worth having in view right now, without being asked").option("--context-dir <path>", "where the work is happening (default: cwd)").option("--limit <n>", "how many cards", (v) => parseInt(v, 10), 5).option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
21263
22449
|
"--brain <name>",
|
|
21264
22450
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
21265
22451
|
).option(
|
|
@@ -21301,7 +22487,6 @@ function resurfaceCommand() {
|
|
|
21301
22487
|
// so it is the only side that can answer this.
|
|
21302
22488
|
here: (event) => isInside(event.context_dir, contextDir)
|
|
21303
22489
|
});
|
|
21304
|
-
const TEMPLATE_SLOTS = 2;
|
|
21305
22490
|
const untouchedPaths = await untouchedBase(
|
|
21306
22491
|
adapter,
|
|
21307
22492
|
ranked.map((r) => r.entry.path)
|
|
@@ -21373,9 +22558,7 @@ function resurfaceCommand() {
|
|
|
21373
22558
|
)
|
|
21374
22559
|
);
|
|
21375
22560
|
}
|
|
21376
|
-
const noEvidenceYet = !rankedShown.some(
|
|
21377
|
-
({ why }) => why.some((w) => w.startsWith("recalled"))
|
|
21378
|
-
);
|
|
22561
|
+
const noEvidenceYet = !rankedShown.some(({ used }) => used);
|
|
21379
22562
|
for (const [i, { entry, why }] of rankedShown.entries()) {
|
|
21380
22563
|
const blank = untouched.has(entry.path);
|
|
21381
22564
|
printHuman(`### ${i + 1}. ${oneLine(entry.memory.title, WIDTH.title)}`);
|
|
@@ -21484,11 +22667,11 @@ rank by project yet, so that is not one of the signals above.`
|
|
|
21484
22667
|
}
|
|
21485
22668
|
|
|
21486
22669
|
// src/commands/rules.ts
|
|
21487
|
-
import { Command as
|
|
22670
|
+
import { Command as Command39 } from "commander";
|
|
21488
22671
|
init_cloud_brain();
|
|
21489
22672
|
init_ui();
|
|
21490
22673
|
function rulesCommand() {
|
|
21491
|
-
return new
|
|
22674
|
+
return new Command39("rules").description("the user\u2019s standing decisions \u2014 what binds you here").option("--vault <path>", "which brain").option("--local", "use the brain on this machine, even when signed in to a hosted one").option(
|
|
21492
22675
|
"--brain <name>",
|
|
21493
22676
|
"read one existing hosted brain by name or id, without changing the pointer"
|
|
21494
22677
|
).action(async (opts) => {
|
|
@@ -21648,89 +22831,160 @@ If the rest are decisions that no longer decide anything: \`memgineering retire
|
|
|
21648
22831
|
init_setup();
|
|
21649
22832
|
|
|
21650
22833
|
// src/commands/update.ts
|
|
21651
|
-
|
|
22834
|
+
init_brand();
|
|
22835
|
+
init_version();
|
|
21652
22836
|
init_config();
|
|
22837
|
+
import { spawn as spawn3 } from "child_process";
|
|
22838
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
22839
|
+
import { mkdir as mkdir18, open, readFile as readFile27, stat as stat7, unlink as unlink4 } from "fs/promises";
|
|
22840
|
+
import { join as join28 } from "path";
|
|
22841
|
+
import { Command as Command41 } from "commander";
|
|
21653
22842
|
|
|
21654
|
-
// src/lib/
|
|
21655
|
-
|
|
21656
|
-
|
|
21657
|
-
|
|
21658
|
-
|
|
21659
|
-
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
|
|
21671
|
-
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
21676
|
-
|
|
21677
|
-
|
|
21678
|
-
|
|
21679
|
-
|
|
21680
|
-
|
|
21681
|
-
|
|
21682
|
-
|
|
21683
|
-
|
|
21684
|
-
|
|
21685
|
-
|
|
21686
|
-
|
|
22843
|
+
// src/lib/next-step.ts
|
|
22844
|
+
init_brand();
|
|
22845
|
+
init_api_client();
|
|
22846
|
+
init_credentials();
|
|
22847
|
+
init_config();
|
|
22848
|
+
init_version();
|
|
22849
|
+
import { mkdir as mkdir16, readFile as readFile25, writeFile as writeFile13 } from "fs/promises";
|
|
22850
|
+
import { join as join26 } from "path";
|
|
22851
|
+
function isSetUp(cfg) {
|
|
22852
|
+
return Object.keys(cfg.system.installed).length > 0;
|
|
22853
|
+
}
|
|
22854
|
+
var FINISH_SETUP = `## memgineering is installed, but not set up yet.
|
|
22855
|
+
|
|
22856
|
+
Nothing is connected: there is no brain to read, and the agent tools on this
|
|
22857
|
+
machine have no memgineering guidance yet. Setup is one command:
|
|
22858
|
+
|
|
22859
|
+
\`\`\`bash
|
|
22860
|
+
memgineering setup --web
|
|
22861
|
+
\`\`\`
|
|
22862
|
+
|
|
22863
|
+
It does NOT wait for them. It returns in a few seconds \u2014 measured \u2014 and leaves
|
|
22864
|
+
a setup screen open in their browser at memgineering.com, where they choose
|
|
22865
|
+
everything: signing in, where the memory lives, which notes. Nothing is
|
|
22866
|
+
installed until they finish there, and this machine picks it up on its own.
|
|
22867
|
+
|
|
22868
|
+
If they have not already asked you to set it up, ask them first \u2014 it opens a
|
|
22869
|
+
browser window on their computer. Once you run it, tell them in their language
|
|
22870
|
+
that the setup screen is open. It prints a short code first: show it to them,
|
|
22871
|
+
because the screen asks them to compare it.
|
|
22872
|
+
|
|
22873
|
+
No browser on this machine (a server, a container, a sandbox)? Run
|
|
22874
|
+
\`memgineering setup --web --print-url\` and give them the link to open on the
|
|
22875
|
+
computer they are actually sitting at.
|
|
22876
|
+
|
|
22877
|
+
\`setup --agent\` answers every one of those questions on their behalf, and
|
|
22878
|
+
\`--no-login\` answers the biggest one \u2014 no account, so the memory cannot follow
|
|
22879
|
+
them to another machine. Use it only when they have said they would rather you
|
|
22880
|
+
decided, or when this machine cannot reach a browser and \`--print-url\` is no
|
|
22881
|
+
use to them either.
|
|
22882
|
+
|
|
22883
|
+
Setup's output then says how to connect their brain \u2014 create one, pick one, or
|
|
22884
|
+
move existing notes up.
|
|
22885
|
+
|
|
22886
|
+
The \`memgineering-setup\` skill covers what comes after.`;
|
|
22887
|
+
var SETUP_NUDGE = "memgineering is installed, but not set up yet. `memgineering setup --web` opens a setup screen in their browser and does NOT wait for them \u2014 ask them first unless they asked you to set it up.\nThe full notes: run `memgineering` on its own.";
|
|
22888
|
+
async function claimFullSetupNotice() {
|
|
22889
|
+
const marker = join26(brandHome(), "setup-notice-shown");
|
|
22890
|
+
try {
|
|
22891
|
+
await readFile25(marker, "utf8");
|
|
22892
|
+
return false;
|
|
22893
|
+
} catch {
|
|
21687
22894
|
}
|
|
21688
|
-
|
|
21689
|
-
|
|
21690
|
-
|
|
21691
|
-
|
|
21692
|
-
|
|
21693
|
-
kind: manager.kind,
|
|
21694
|
-
selfDir,
|
|
21695
|
-
prefix,
|
|
21696
|
-
command: manager.install,
|
|
21697
|
-
reason: null
|
|
21698
|
-
};
|
|
21699
|
-
}
|
|
22895
|
+
try {
|
|
22896
|
+
await mkdir16(brandHome(), { recursive: true });
|
|
22897
|
+
await writeFile13(marker, `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
22898
|
+
`);
|
|
22899
|
+
} catch {
|
|
21700
22900
|
}
|
|
21701
|
-
|
|
21702
|
-
return {
|
|
21703
|
-
kind: local ? "local" : "unknown",
|
|
21704
|
-
selfDir,
|
|
21705
|
-
prefix: null,
|
|
21706
|
-
command: "npm i -g memgineering@latest",
|
|
21707
|
-
reason: local ? "this copy is a project dependency, not a global install" : "no package manager on PATH owns the directory this copy is in \u2014 under nvm, asdf, Volta or mise that usually means a different Node version is active than the one it was installed with"
|
|
21708
|
-
};
|
|
22901
|
+
return true;
|
|
21709
22902
|
}
|
|
21710
|
-
|
|
21711
|
-
|
|
22903
|
+
var REFRESH_GUIDANCE = `## The guidance in your agent tools is older than this memgineering.
|
|
22904
|
+
|
|
22905
|
+
The hub and skills this CLI installs into your tool's home were written by an
|
|
22906
|
+
earlier release. One command rewrites them:
|
|
22907
|
+
|
|
22908
|
+
\`\`\`bash
|
|
22909
|
+
memgineering setup
|
|
22910
|
+
\`\`\`
|
|
22911
|
+
|
|
22912
|
+
It does not change which brain this machine reads, or any note. Two things it
|
|
22913
|
+
does change, both worth knowing before it runs:
|
|
22914
|
+
|
|
22915
|
+
- A setup-owned file your user edited is overwritten; setup names it afterwards.
|
|
22916
|
+
- Run with no flags where there is no terminal (an agent's shell), it applies
|
|
22917
|
+
setup's defaults over whatever they chose before: auto-update on, its hooks
|
|
22918
|
+
on, every agent tool it finds \u2014 and on a machine not signed in, it starts a
|
|
22919
|
+
sign-in and prints a code. To keep what they chose, pass it:
|
|
22920
|
+
\`--auto-update off\`, \`--hook off\`, \`--no-login\` for a machine they keep
|
|
22921
|
+
local, and \`--tools\` naming every tool they use (a tool left out keeps its
|
|
22922
|
+
old guidance, and this notice stops appearing anyway).
|
|
22923
|
+
|
|
22924
|
+
If you are not sure what they chose, ask them. If you run it,
|
|
22925
|
+
mention it to your user in one line: it is upkeep for a tool they already set
|
|
22926
|
+
up, and they should not have to discover it later.
|
|
22927
|
+
|
|
22928
|
+
Guidance files are read at the start of a session, so what changes today
|
|
22929
|
+
arrives in the next one.`;
|
|
22930
|
+
var CONNECT_A_BRAIN = `## memgineering is set up, and no brain is connected.
|
|
22931
|
+
|
|
22932
|
+
A brain is a folder of markdown notes. Until one is connected there is nothing
|
|
22933
|
+
to recall and nowhere to remember. Which folder is your user's choice; the
|
|
22934
|
+
command is one you can run for them:
|
|
22935
|
+
|
|
22936
|
+
\`\`\`bash
|
|
22937
|
+
memgineering link <their-notes-folder> # they already keep notes
|
|
22938
|
+
memgineering init <path> # they do not \u2014 creates one, laid out
|
|
22939
|
+
\`\`\`
|
|
22940
|
+
|
|
22941
|
+
\`link\` reports what would be stored before anything is indexed. An existing
|
|
22942
|
+
folder needs no index and no frontmatter of its own \u2014 plain markdown is a brain.
|
|
22943
|
+
|
|
22944
|
+
Then run \`memgineering onboard\`: it prints what to ask so the brain knows whose
|
|
22945
|
+
it is. A brain whose \`01_BASE/\` is still template text answers almost nothing.
|
|
22946
|
+
|
|
22947
|
+
Read \`memgineering-setup\` for accounts and which of their tools this reaches.
|
|
22948
|
+
|
|
22949
|
+
Synced instead? \`memgineering login\`, then \`memgineering create <name>\`.`;
|
|
22950
|
+
var POINT_AT_A_BRAIN = `## Signed in, and this machine reads no brain yet.
|
|
22951
|
+
|
|
22952
|
+
\`memgineering brains\` lists what is on the account.
|
|
22953
|
+
\`memgineering use --brain <name>\` points this machine at one.
|
|
22954
|
+
\`memgineering create <name>\` makes one and points here, when the account has none yet.
|
|
22955
|
+
`;
|
|
22956
|
+
function nextStepFor(cfg, signedIn = false) {
|
|
22957
|
+
if (signedIn && cfg.brain.cloud === void 0) {
|
|
22958
|
+
return { id: "point_at_a_brain", markdown: POINT_AT_A_BRAIN };
|
|
22959
|
+
}
|
|
22960
|
+
if (isSetUp(cfg) && cfg.system.guidanceVersion !== VERSION) {
|
|
22961
|
+
return { id: "refresh_guidance", markdown: REFRESH_GUIDANCE };
|
|
22962
|
+
}
|
|
22963
|
+
if (cfg.brain.cloud !== void 0 && cfg.brain.cloud.api_url === apiUrl()) return null;
|
|
22964
|
+
if (!isSetUp(cfg)) return { id: "finish_setup", markdown: FINISH_SETUP };
|
|
22965
|
+
if (cfg.brain.brains.length === 0 && cfg.brain.cloud === void 0) {
|
|
22966
|
+
return { id: "connect_a_brain", markdown: CONNECT_A_BRAIN };
|
|
22967
|
+
}
|
|
22968
|
+
return null;
|
|
21712
22969
|
}
|
|
21713
|
-
async function
|
|
22970
|
+
async function currentNextStep() {
|
|
21714
22971
|
try {
|
|
21715
|
-
const
|
|
21716
|
-
|
|
21717
|
-
return value === "" ? null : await realpath3(value).catch(() => value);
|
|
22972
|
+
const stored = await readCredentials().catch(() => null);
|
|
22973
|
+
return nextStepFor(await loadConfig(), stored !== null && stored.api_url === apiUrl());
|
|
21718
22974
|
} catch {
|
|
21719
22975
|
return null;
|
|
21720
22976
|
}
|
|
21721
22977
|
}
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
|
|
21725
|
-
return c2 === p || c2.startsWith(p.endsWith("/") ? p : `${p}/`);
|
|
21726
|
-
}
|
|
22978
|
+
|
|
22979
|
+
// src/commands/update.ts
|
|
22980
|
+
init_provenance();
|
|
21727
22981
|
|
|
21728
22982
|
// src/lib/update-check.ts
|
|
21729
22983
|
init_atomic();
|
|
21730
22984
|
init_brand();
|
|
21731
22985
|
import { execFile as execFile2 } from "child_process";
|
|
21732
|
-
import { mkdir as
|
|
21733
|
-
import { join as
|
|
22986
|
+
import { mkdir as mkdir17, readFile as readFile26, writeFile as writeFile14 } from "fs/promises";
|
|
22987
|
+
import { join as join27 } from "path";
|
|
21734
22988
|
import { promisify as promisify2 } from "util";
|
|
21735
22989
|
import semver4 from "semver";
|
|
21736
22990
|
import { z as z33 } from "zod";
|
|
@@ -21811,11 +23065,11 @@ var UpdateStateSchema = z33.object({
|
|
|
21811
23065
|
message: z33.string().nullable()
|
|
21812
23066
|
});
|
|
21813
23067
|
function statePath() {
|
|
21814
|
-
return
|
|
23068
|
+
return join27(brandHome(), "update-state.json");
|
|
21815
23069
|
}
|
|
21816
23070
|
async function readUpdateState() {
|
|
21817
23071
|
try {
|
|
21818
|
-
const parsed = UpdateStateSchema.safeParse(JSON.parse(await
|
|
23072
|
+
const parsed = UpdateStateSchema.safeParse(JSON.parse(await readFile26(statePath(), "utf8")));
|
|
21819
23073
|
return parsed.success ? parsed.data : null;
|
|
21820
23074
|
} catch {
|
|
21821
23075
|
return null;
|
|
@@ -21823,7 +23077,7 @@ async function readUpdateState() {
|
|
|
21823
23077
|
}
|
|
21824
23078
|
async function writeUpdateState(state) {
|
|
21825
23079
|
try {
|
|
21826
|
-
await
|
|
23080
|
+
await mkdir17(brandHome(), { recursive: true });
|
|
21827
23081
|
await writeThenRename(statePath(), `${JSON.stringify(state, null, 2)}
|
|
21828
23082
|
`);
|
|
21829
23083
|
} catch {
|
|
@@ -21879,70 +23133,106 @@ function shortMessage(err) {
|
|
|
21879
23133
|
// src/commands/update.ts
|
|
21880
23134
|
init_ui();
|
|
21881
23135
|
function updateCommand() {
|
|
21882
|
-
return new
|
|
21883
|
-
const
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
...cfg,
|
|
21889
|
-
system: { ...cfg.system, lastUpdateCheck: (/* @__PURE__ */ new Date()).toISOString() }
|
|
21890
|
-
});
|
|
21891
|
-
if (!status.updateAvailable) {
|
|
21892
|
-
printDual({
|
|
21893
|
-
json: { ...status, provenance: provenance.kind, updated: false, previous },
|
|
21894
|
-
human: () => {
|
|
21895
|
-
printHuman(
|
|
21896
|
-
status.latest === null ? `memgineering ${VERSION} \u2014 could not reach npm to check for a newer version.` : `memgineering ${VERSION} is current.`
|
|
21897
|
-
);
|
|
21898
|
-
reportNotice(status.notice);
|
|
21899
|
-
reportBelowMinimum(status.belowMinimum);
|
|
21900
|
-
}
|
|
21901
|
-
});
|
|
21902
|
-
return;
|
|
23136
|
+
return new Command41("update").description("update memgineering itself").option("--check", "report what is available and change nothing").action(async (opts) => {
|
|
23137
|
+
const inherited = process.env[UPDATE_CLAIM_ENV] || void 0;
|
|
23138
|
+
try {
|
|
23139
|
+
await runUpdate(opts, inherited);
|
|
23140
|
+
} finally {
|
|
23141
|
+
if (inherited !== void 0) await releaseUpdateSlot(inherited);
|
|
21903
23142
|
}
|
|
21904
|
-
|
|
21905
|
-
|
|
21906
|
-
|
|
21907
|
-
|
|
21908
|
-
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
21914
|
-
|
|
23143
|
+
});
|
|
23144
|
+
}
|
|
23145
|
+
async function runUpdate(opts, inherited) {
|
|
23146
|
+
const status = await checkForUpdate(VERSION);
|
|
23147
|
+
const provenance = await detectProvenance();
|
|
23148
|
+
const previous = await readUpdateState();
|
|
23149
|
+
const cfg = await loadConfig();
|
|
23150
|
+
await saveConfig({
|
|
23151
|
+
...cfg,
|
|
23152
|
+
system: { ...cfg.system, lastUpdateCheck: (/* @__PURE__ */ new Date()).toISOString() }
|
|
23153
|
+
});
|
|
23154
|
+
if (!status.updateAvailable) {
|
|
23155
|
+
printDual({
|
|
23156
|
+
json: { ...status, provenance: provenance.kind, updated: false, previous },
|
|
23157
|
+
human: () => {
|
|
23158
|
+
printHuman(
|
|
23159
|
+
status.latest === null ? `memgineering ${VERSION} \u2014 could not reach npm to check for a newer version.` : `memgineering ${VERSION} is current.`
|
|
23160
|
+
);
|
|
23161
|
+
reportNotice(status.notice);
|
|
23162
|
+
reportBelowMinimum(status.belowMinimum);
|
|
23163
|
+
}
|
|
23164
|
+
});
|
|
23165
|
+
return;
|
|
23166
|
+
}
|
|
23167
|
+
if (opts.check || !canSelfUpdate(provenance)) {
|
|
23168
|
+
printDual({
|
|
23169
|
+
json: {
|
|
23170
|
+
...status,
|
|
23171
|
+
provenance: provenance.kind,
|
|
23172
|
+
reason: provenance.reason,
|
|
23173
|
+
command: provenance.command,
|
|
23174
|
+
updated: false
|
|
23175
|
+
},
|
|
23176
|
+
human: () => {
|
|
23177
|
+
printHuman(`memgineering ${status.latest} is available (you have ${VERSION}).
|
|
21915
23178
|
`);
|
|
21916
|
-
|
|
21917
|
-
|
|
23179
|
+
if (!opts.check && provenance.reason) {
|
|
23180
|
+
printHuman(c.yellow(`Not updating automatically \u2014 ${provenance.reason}.
|
|
21918
23181
|
`));
|
|
21919
|
-
}
|
|
21920
|
-
printHuman(`Run: ${provenance.command}`);
|
|
21921
|
-
reportNotice(status.notice);
|
|
21922
|
-
reportBelowMinimum(status.belowMinimum);
|
|
21923
23182
|
}
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
23183
|
+
printHuman(`Run: ${provenance.command}`);
|
|
23184
|
+
reportNotice(status.notice);
|
|
23185
|
+
reportBelowMinimum(status.belowMinimum);
|
|
23186
|
+
}
|
|
23187
|
+
});
|
|
23188
|
+
return;
|
|
23189
|
+
}
|
|
23190
|
+
const held = await holdUpdateSlot(inherited);
|
|
23191
|
+
if (held === null) {
|
|
23192
|
+
const started = (await claimOwner())?.split(" ")[0];
|
|
23193
|
+
const hint = "Another memgineering update is installing right now. Run `memgineering update` again in a few minutes \u2014 it reports whether that install worked.";
|
|
21930
23194
|
printDual({
|
|
21931
|
-
json: {
|
|
23195
|
+
json: {
|
|
23196
|
+
...status,
|
|
23197
|
+
provenance: provenance.kind,
|
|
23198
|
+
updated: false,
|
|
23199
|
+
reason: "update_in_progress",
|
|
23200
|
+
...started ? { started_at: started } : {},
|
|
23201
|
+
hint
|
|
23202
|
+
},
|
|
21932
23203
|
human: () => {
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
}
|
|
21938
|
-
printHuman(c.yellow(`Update did not complete: ${result.message}`));
|
|
21939
|
-
printHuman(c.gray(`Run it yourself: ${provenance.command}`));
|
|
23204
|
+
printHuman(
|
|
23205
|
+
`memgineering ${status.latest} is available, and an update is already installing${started ? ` (started ${started})` : ""} \u2014 not starting a second one.`
|
|
23206
|
+
);
|
|
23207
|
+
printHuman(c.gray(hint));
|
|
21940
23208
|
}
|
|
21941
23209
|
});
|
|
23210
|
+
return;
|
|
23211
|
+
}
|
|
23212
|
+
printHuman(c.gray(`Updating ${VERSION} \u2192 ${status.latest}\u2026`));
|
|
23213
|
+
let result;
|
|
23214
|
+
try {
|
|
23215
|
+
result = await performUpdate({ command: provenance.command, from: VERSION });
|
|
23216
|
+
await writeUpdateState(result);
|
|
23217
|
+
} finally {
|
|
23218
|
+
await releaseUpdateSlot(held);
|
|
23219
|
+
}
|
|
23220
|
+
printDual({
|
|
23221
|
+
json: { ...status, provenance: provenance.kind, updated: result.ok, result },
|
|
23222
|
+
human: () => {
|
|
23223
|
+
if (result.ok) {
|
|
23224
|
+
printHuman(c.green(`Updated to ${result.to}.`));
|
|
23225
|
+
printHuman(c.gray("Restart your agent session to pick up any changed guidance."));
|
|
23226
|
+
return;
|
|
23227
|
+
}
|
|
23228
|
+
printHuman(c.yellow(`Update did not complete: ${result.message}`));
|
|
23229
|
+
printHuman(c.gray(`Run it yourself: ${provenance.command}`));
|
|
23230
|
+
}
|
|
21942
23231
|
});
|
|
21943
23232
|
}
|
|
21944
|
-
async function backgroundUpdateNotice() {
|
|
23233
|
+
async function backgroundUpdateNotice(deps = {}) {
|
|
21945
23234
|
try {
|
|
23235
|
+
if (deps.verb !== void 0 && HOOK_VERBS.has(deps.verb)) return;
|
|
21946
23236
|
const cfg = await loadConfig();
|
|
21947
23237
|
const now = /* @__PURE__ */ new Date();
|
|
21948
23238
|
if (!shouldCheck(cfg.system.lastUpdateCheck, now)) {
|
|
@@ -21950,8 +23240,22 @@ async function backgroundUpdateNotice() {
|
|
|
21950
23240
|
return;
|
|
21951
23241
|
}
|
|
21952
23242
|
await saveConfig({ ...cfg, system: { ...cfg.system, lastUpdateCheck: now.toISOString() } });
|
|
21953
|
-
const status = await checkForUpdate(VERSION);
|
|
23243
|
+
const status = await (deps.check ?? checkForUpdate)(VERSION);
|
|
21954
23244
|
await reportPreviousAttempt();
|
|
23245
|
+
if (status.updateAvailable && cfg.system.autoUpdate === "on" && isSetUp(cfg) && !process.env["CI"]) {
|
|
23246
|
+
const provenance = await (deps.provenance ?? detectProvenance)();
|
|
23247
|
+
const token = newClaimToken();
|
|
23248
|
+
if (canSelfUpdate(provenance) && await claimUpdateSlot(token)) {
|
|
23249
|
+
(deps.startUpdate ?? ((t) => startDetachedUpdate(spawn3, t)))(token);
|
|
23250
|
+
notice(
|
|
23251
|
+
c.gray(
|
|
23252
|
+
`memgineering ${status.latest} is available \u2014 installing it in the background (auto-update is on). The next run uses it.`
|
|
23253
|
+
)
|
|
23254
|
+
);
|
|
23255
|
+
if (status.notice) notice(c.gray(`note from memgineering.com: "${status.notice}"`));
|
|
23256
|
+
return;
|
|
23257
|
+
}
|
|
23258
|
+
}
|
|
21955
23259
|
if (status.belowMinimum) {
|
|
21956
23260
|
notice(
|
|
21957
23261
|
c.yellow(
|
|
@@ -21965,10 +23269,73 @@ async function backgroundUpdateNotice() {
|
|
|
21965
23269
|
} catch {
|
|
21966
23270
|
}
|
|
21967
23271
|
}
|
|
23272
|
+
function startDetachedUpdate(spawnImpl = spawn3, token) {
|
|
23273
|
+
const child = spawnImpl(process.execPath, [process.argv[1] ?? "", "update"], {
|
|
23274
|
+
detached: true,
|
|
23275
|
+
stdio: "ignore",
|
|
23276
|
+
env: {
|
|
23277
|
+
...process.env,
|
|
23278
|
+
MEMGINEERING_NO_UPDATE: "1",
|
|
23279
|
+
// The claim this run took, so the child installs under it and gives it
|
|
23280
|
+
// back, instead of finding it held and standing down.
|
|
23281
|
+
...token === void 0 ? {} : { [UPDATE_CLAIM_ENV]: token }
|
|
23282
|
+
}
|
|
23283
|
+
});
|
|
23284
|
+
child.on("error", () => {
|
|
23285
|
+
if (token !== void 0) void releaseUpdateSlot(token);
|
|
23286
|
+
});
|
|
23287
|
+
child.unref();
|
|
23288
|
+
}
|
|
23289
|
+
var UPDATE_CLAIM_ENV = "MEMGINEERING_UPDATE_CLAIM";
|
|
23290
|
+
function newClaimToken() {
|
|
23291
|
+
return `${(/* @__PURE__ */ new Date()).toISOString()} pid=${process.pid} ${randomBytes2(6).toString("hex")}`;
|
|
23292
|
+
}
|
|
23293
|
+
async function claimOwner() {
|
|
23294
|
+
try {
|
|
23295
|
+
return (await readFile27(join28(brandHome(), "update-running"), "utf8")).trim() || null;
|
|
23296
|
+
} catch {
|
|
23297
|
+
return null;
|
|
23298
|
+
}
|
|
23299
|
+
}
|
|
23300
|
+
async function releaseUpdateSlot(token) {
|
|
23301
|
+
if (await claimOwner() !== token) return;
|
|
23302
|
+
await unlink4(join28(brandHome(), "update-running")).catch(() => void 0);
|
|
23303
|
+
}
|
|
23304
|
+
async function holdUpdateSlot(inherited) {
|
|
23305
|
+
if (inherited !== void 0 && await claimOwner() === inherited) return inherited;
|
|
23306
|
+
const token = newClaimToken();
|
|
23307
|
+
if (await claimUpdateSlot(token)) return token;
|
|
23308
|
+
if (await claimOwner() === null && await claimUpdateSlot(token)) return token;
|
|
23309
|
+
return null;
|
|
23310
|
+
}
|
|
23311
|
+
var HOOK_VERBS = /* @__PURE__ */ new Set(["guard", "resurface"]);
|
|
23312
|
+
var UPDATE_CLAIM_MS = 15 * 6e4;
|
|
23313
|
+
async function claimUpdateSlot(token = newClaimToken()) {
|
|
23314
|
+
const path = join28(brandHome(), "update-running");
|
|
23315
|
+
try {
|
|
23316
|
+
await mkdir18(brandHome(), { recursive: true });
|
|
23317
|
+
const handle = await open(path, "wx");
|
|
23318
|
+
await handle.writeFile(`${token}
|
|
23319
|
+
`);
|
|
23320
|
+
await handle.close();
|
|
23321
|
+
return true;
|
|
23322
|
+
} catch (err) {
|
|
23323
|
+
if (err?.code !== "EEXIST") return false;
|
|
23324
|
+
}
|
|
23325
|
+
try {
|
|
23326
|
+
if (Date.now() - (await stat7(path)).mtimeMs >= UPDATE_CLAIM_MS) await unlink4(path);
|
|
23327
|
+
} catch {
|
|
23328
|
+
}
|
|
23329
|
+
return false;
|
|
23330
|
+
}
|
|
21968
23331
|
async function reportPreviousAttempt() {
|
|
21969
23332
|
const previous = await readUpdateState();
|
|
21970
23333
|
if (!previous || previous.ok) return;
|
|
21971
|
-
notice(
|
|
23334
|
+
notice(
|
|
23335
|
+
c.yellow(`\u26A0 The last update attempt did not complete: ${previous.message}`) + c.gray(
|
|
23336
|
+
"\n `memgineering update` shows why. `memgineering setup --agent --auto-update off --hook <on|off>` stops\n these attempts \u2014 name the hook setting you want kept, because `--agent` sets that too."
|
|
23337
|
+
)
|
|
23338
|
+
);
|
|
21972
23339
|
}
|
|
21973
23340
|
function reportNotice(text) {
|
|
21974
23341
|
if (!text) return;
|
|
@@ -21986,16 +23353,16 @@ function reportBelowMinimum(below) {
|
|
|
21986
23353
|
|
|
21987
23354
|
// src/commands/undo.ts
|
|
21988
23355
|
init_src();
|
|
21989
|
-
import { readFile as
|
|
21990
|
-
import { join as
|
|
21991
|
-
import { Command as
|
|
23356
|
+
import { readFile as readFile28, rm as rm8 } from "fs/promises";
|
|
23357
|
+
import { join as join29 } from "path";
|
|
23358
|
+
import { Command as Command42 } from "commander";
|
|
21992
23359
|
init_atomic();
|
|
21993
23360
|
init_errors();
|
|
21994
23361
|
init_cloud_brain();
|
|
21995
23362
|
init_vault();
|
|
21996
23363
|
init_ui();
|
|
21997
23364
|
function undoCommand() {
|
|
21998
|
-
return new
|
|
23365
|
+
return new Command42("undo").description("take back the last change, or a named one").argument("[op]", "operation id (default: the most recent)").option("--reason <text>", "why it is being taken back \u2014 kept in the ledger").option("--vault <path>", "which brain to act on").option("--local", "act on the brain on this machine, even when signed in to a hosted one").action(
|
|
21999
23366
|
async (opId, opts) => {
|
|
22000
23367
|
const rationale = sanitizeRationale(opts.reason);
|
|
22001
23368
|
const where = await resolveTarget({
|
|
@@ -22084,8 +23451,8 @@ ${matches.slice(0, 8).map((id) => ` ${id}`).join("\n")}${matches.length > 8 ? "
|
|
|
22084
23451
|
);
|
|
22085
23452
|
}
|
|
22086
23453
|
const record = await withWriteLock(brain.root, async () => {
|
|
22087
|
-
const absolute =
|
|
22088
|
-
const current = await
|
|
23454
|
+
const absolute = join29(brain.root, target.target);
|
|
23455
|
+
const current = await readFile28(absolute, "utf8").catch(() => null);
|
|
22089
23456
|
const actualHash = current === null ? null : hashContent(current);
|
|
22090
23457
|
if (actualHash !== target.hash_after) {
|
|
22091
23458
|
throw memgError(
|
|
@@ -22147,7 +23514,7 @@ function minutesSince(iso) {
|
|
|
22147
23514
|
return Math.max(0, Math.floor((Date.now() - at) / 6e4));
|
|
22148
23515
|
}
|
|
22149
23516
|
async function reverse(brain, op, current) {
|
|
22150
|
-
const absolute =
|
|
23517
|
+
const absolute = join29(brain.root, op.target);
|
|
22151
23518
|
if (op.verb === "remember") {
|
|
22152
23519
|
if (current === null) {
|
|
22153
23520
|
throw memgError(
|
|
@@ -22200,11 +23567,11 @@ async function reverse(brain, op, current) {
|
|
|
22200
23567
|
return reverted;
|
|
22201
23568
|
}
|
|
22202
23569
|
async function conflictHint(vaultRoot, op) {
|
|
22203
|
-
const
|
|
23570
|
+
const base2 = `Nothing was changed. Someone \u2014 you, an editor, or a sync \u2014 has written to that file since.
|
|
22204
23571
|
Read what it says now, then decide what should stay.`;
|
|
22205
23572
|
const snapshot = await readSnapshot(vaultRoot, op.op_id);
|
|
22206
|
-
return snapshot === null ?
|
|
22207
|
-
The text from before that change is still on this machine: ${
|
|
23573
|
+
return snapshot === null ? base2 : `${base2}
|
|
23574
|
+
The text from before that change is still on this machine: ${join29(snapshotDir(vaultRoot), `${op.op_id}.txt`)}`;
|
|
22208
23575
|
}
|
|
22209
23576
|
|
|
22210
23577
|
// src/commands/brains.ts
|
|
@@ -22213,19 +23580,19 @@ init_api_client();
|
|
|
22213
23580
|
init_cloud_brain();
|
|
22214
23581
|
init_config();
|
|
22215
23582
|
init_ui();
|
|
22216
|
-
import { Command as
|
|
23583
|
+
import { Command as Command43 } from "commander";
|
|
22217
23584
|
import stringWidth from "string-width";
|
|
22218
23585
|
var NAME_WIDTH2 = 48;
|
|
22219
23586
|
function brainsCommand() {
|
|
22220
|
-
return new
|
|
23587
|
+
return new Command43("brains").description("list the hosted brains on your account, and which one this machine reads").addHelpText(
|
|
22221
23588
|
"after",
|
|
22222
23589
|
"\nReads only. `memgineering use --brain <name>` points this machine at one of\nthem. `memgineering create <name>` makes a new empty one, and\n`push --brain <name>` makes one from a folder on this machine."
|
|
22223
23590
|
).action(async () => {
|
|
22224
|
-
const
|
|
22225
|
-
const credentials = await requireCredentials(
|
|
22226
|
-
const brains = await listCloudBrains(credentials.token,
|
|
23591
|
+
const base2 = apiUrl();
|
|
23592
|
+
const credentials = await requireCredentials(base2);
|
|
23593
|
+
const brains = await listCloudBrains(credentials.token, base2);
|
|
22227
23594
|
const pointer = (await loadConfig()).brain.cloud;
|
|
22228
|
-
const currentId = pointer?.api_url ===
|
|
23595
|
+
const currentId = pointer?.api_url === base2 ? pointer.id : null;
|
|
22229
23596
|
if (brains.length === 0) {
|
|
22230
23597
|
throw memgError(
|
|
22231
23598
|
"invalid_input",
|
|
@@ -22253,7 +23620,7 @@ function brainsCommand() {
|
|
|
22253
23620
|
// that answered none of the rest. Two round-test agents ran all three.
|
|
22254
23621
|
// The name is free here; it came back with the token.
|
|
22255
23622
|
account: { id: credentials.account_id, name: credentials.display_name },
|
|
22256
|
-
server:
|
|
23623
|
+
server: base2,
|
|
22257
23624
|
brains: rows,
|
|
22258
23625
|
count: rows.length,
|
|
22259
23626
|
// Named at the top level too, so a caller does not have to scan the
|
|
@@ -22297,7 +23664,7 @@ init_credentials();
|
|
|
22297
23664
|
init_cloud_brain();
|
|
22298
23665
|
init_config();
|
|
22299
23666
|
import { isAbsolute as isAbsolute5, relative as relative2, resolve as resolve15 } from "path";
|
|
22300
|
-
import { Command as
|
|
23667
|
+
import { Command as Command44 } from "commander";
|
|
22301
23668
|
init_vault();
|
|
22302
23669
|
init_ui();
|
|
22303
23670
|
function lostDefaultLines(missing) {
|
|
@@ -22316,7 +23683,7 @@ var VIA_MEANS = {
|
|
|
22316
23683
|
default: "because it is your default brain (`memgineering use --default <path>` changes it)"
|
|
22317
23684
|
};
|
|
22318
23685
|
function useCommand() {
|
|
22319
|
-
return new
|
|
23686
|
+
return new Command44("use").description(
|
|
22320
23687
|
"say which brain answers here \u2014 `--brain <name>` for the whole machine, or a path to bind this folder"
|
|
22321
23688
|
).argument("[brain]", "path to the brain \u2014 omit to show what this folder resolves to").option("--brain <name>", "point this machine at a hosted brain, by its name or id").option("--here <dir>", "write the pointer in this directory instead of the cwd").option(
|
|
22322
23689
|
"--default",
|
|
@@ -22542,8 +23909,8 @@ async function pointAtHosted(named, opts, path) {
|
|
|
22542
23909
|
"They point different things. `--brain <name>` moves the one hosted brain this machine reads, which is a single setting for the whole machine; a path, `--default` and `--here` all bind a FOLDER on this disk. Run them separately."
|
|
22543
23910
|
);
|
|
22544
23911
|
}
|
|
22545
|
-
const
|
|
22546
|
-
const credentials = await requireCredentials(
|
|
23912
|
+
const base2 = apiUrl();
|
|
23913
|
+
const credentials = await requireCredentials(base2).catch((err) => {
|
|
22547
23914
|
if (err instanceof MemgError && err.code === "not_signed_in") {
|
|
22548
23915
|
throw memgError(
|
|
22549
23916
|
"not_signed_in",
|
|
@@ -22553,7 +23920,7 @@ async function pointAtHosted(named, opts, path) {
|
|
|
22553
23920
|
}
|
|
22554
23921
|
throw err;
|
|
22555
23922
|
});
|
|
22556
|
-
const brains = await listCloudBrains(credentials.token,
|
|
23923
|
+
const brains = await listCloudBrains(credentials.token, base2);
|
|
22557
23924
|
if (brains.length === 0) {
|
|
22558
23925
|
throw memgError(
|
|
22559
23926
|
"invalid_input",
|
|
@@ -22571,11 +23938,11 @@ async function pointAtHosted(named, opts, path) {
|
|
|
22571
23938
|
}
|
|
22572
23939
|
const config = await loadConfig();
|
|
22573
23940
|
const previous = config.brain.cloud ?? null;
|
|
22574
|
-
const unchanged = previous?.id === found.id && previous.api_url ===
|
|
23941
|
+
const unchanged = previous?.id === found.id && previous.api_url === base2 && previous.name === found.name;
|
|
22575
23942
|
if (!unchanged) {
|
|
22576
23943
|
await saveConfig({
|
|
22577
23944
|
...config,
|
|
22578
|
-
brain: { ...config.brain, cloud: { id: found.id, name: found.name, api_url:
|
|
23945
|
+
brain: { ...config.brain, cloud: { id: found.id, name: found.name, api_url: base2 } }
|
|
22579
23946
|
});
|
|
22580
23947
|
}
|
|
22581
23948
|
printDual({
|
|
@@ -22583,7 +23950,7 @@ async function pointAtHosted(named, opts, path) {
|
|
|
22583
23950
|
brain: {
|
|
22584
23951
|
id: found.id,
|
|
22585
23952
|
name: found.name,
|
|
22586
|
-
api_url:
|
|
23953
|
+
api_url: base2,
|
|
22587
23954
|
// Carried because the human line says it. A caller that has to ask a
|
|
22588
23955
|
// second time to learn the destination is empty is a caller that will
|
|
22589
23956
|
// not ask.
|
|
@@ -22624,16 +23991,16 @@ async function pointAtHosted(named, opts, path) {
|
|
|
22624
23991
|
// src/commands/whoami.ts
|
|
22625
23992
|
init_api_client();
|
|
22626
23993
|
init_ui();
|
|
22627
|
-
import { Command as
|
|
23994
|
+
import { Command as Command45 } from "commander";
|
|
22628
23995
|
function whoamiCommand() {
|
|
22629
|
-
return new
|
|
22630
|
-
const
|
|
22631
|
-
const credentials = await requireCredentials(
|
|
23996
|
+
return new Command45("whoami").description("show which account this machine is signed in as").action(async () => {
|
|
23997
|
+
const base2 = apiUrl();
|
|
23998
|
+
const credentials = await requireCredentials(base2);
|
|
22632
23999
|
const res = await apiRequest({
|
|
22633
24000
|
method: "GET",
|
|
22634
24001
|
path: "/v1/me",
|
|
22635
24002
|
token: credentials.token,
|
|
22636
|
-
baseUrl:
|
|
24003
|
+
baseUrl: base2
|
|
22637
24004
|
});
|
|
22638
24005
|
const account = res.body?.account ?? {};
|
|
22639
24006
|
const live = res.body?.tokens?.live ?? null;
|
|
@@ -22647,7 +24014,7 @@ function whoamiCommand() {
|
|
|
22647
24014
|
created_at: account.created_at ?? null
|
|
22648
24015
|
},
|
|
22649
24016
|
tokens: { live },
|
|
22650
|
-
api_url:
|
|
24017
|
+
api_url: base2,
|
|
22651
24018
|
signed_in_at: credentials.signed_in_at,
|
|
22652
24019
|
registry: credentials.registry ? {
|
|
22653
24020
|
username: credentials.registry.username,
|
|
@@ -22662,7 +24029,7 @@ function whoamiCommand() {
|
|
|
22662
24029
|
)
|
|
22663
24030
|
);
|
|
22664
24031
|
if (account.email) printHuman(c.gray(` ${oneLine(account.email, WIDTH.title)}`));
|
|
22665
|
-
printHuman(c.gray(` on ${
|
|
24032
|
+
printHuman(c.gray(` on ${base2}`));
|
|
22666
24033
|
if (credentials.registry) {
|
|
22667
24034
|
printHuman(
|
|
22668
24035
|
c.gray(
|
|
@@ -22688,94 +24055,6 @@ function whoamiCommand() {
|
|
|
22688
24055
|
});
|
|
22689
24056
|
}
|
|
22690
24057
|
|
|
22691
|
-
// src/lib/next-step.ts
|
|
22692
|
-
init_api_client();
|
|
22693
|
-
init_credentials();
|
|
22694
|
-
init_config();
|
|
22695
|
-
function isSetUp(cfg) {
|
|
22696
|
-
return Object.keys(cfg.system.installed).length > 0;
|
|
22697
|
-
}
|
|
22698
|
-
var FINISH_SETUP = `## memgineering is installed. It is NOT set up yet.
|
|
22699
|
-
|
|
22700
|
-
**TO THE AGENT READING THIS** \u2014 finish it now, in this same turn. Do not print
|
|
22701
|
-
this for your user and stop: the command is yours to run.
|
|
22702
|
-
|
|
22703
|
-
\`\`\`bash
|
|
22704
|
-
memgineering setup --web
|
|
22705
|
-
\`\`\`
|
|
22706
|
-
|
|
22707
|
-
**It does NOT wait for them.** It returns in a few seconds \u2014 measured \u2014 and
|
|
22708
|
-
leaves a setup screen open in their browser at memgineering.com, where they
|
|
22709
|
-
choose everything: signing in, where the memory lives, which notes. Nothing is
|
|
22710
|
-
installed until they finish there, and this machine picks it up on its own when
|
|
22711
|
-
they do, after your turn has ended.
|
|
22712
|
-
|
|
22713
|
-
So: run it, then tell them **in their language** that you opened the setup
|
|
22714
|
-
screen in their browser and they can finish it there. It prints a short code
|
|
22715
|
-
first \u2014 show them that code, because the screen asks whether they started this
|
|
22716
|
-
and expects them to compare it.
|
|
22717
|
-
|
|
22718
|
-
No browser on this machine (a server, a container, a sandbox)? Run
|
|
22719
|
-
\`memgineering setup --web --print-url\` and give them the URL to open on the
|
|
22720
|
-
computer they are actually sitting at.
|
|
22721
|
-
|
|
22722
|
-
\`setup --agent\` is NOT the safe fallback. It answers every one of those
|
|
22723
|
-
questions on the user's behalf, and \`--no-login\` answers the biggest one \u2014
|
|
22724
|
-
no account, so the memory cannot follow them to another machine, which they
|
|
22725
|
-
find out weeks later. Run it only when they have said they would rather you
|
|
22726
|
-
decided, or when this machine genuinely cannot reach a browser and
|
|
22727
|
-
\`--print-url\` is no use to them either.
|
|
22728
|
-
|
|
22729
|
-
Setup's output then says how to connect their brain \u2014 create one, pick one, or
|
|
22730
|
-
move existing notes up.
|
|
22731
|
-
|
|
22732
|
-
Then read \`memgineering-setup\` for what to do with the brain it points at.`;
|
|
22733
|
-
var CONNECT_A_BRAIN = `## memgineering is set up, and no brain is connected.
|
|
22734
|
-
|
|
22735
|
-
**TO THE AGENT READING THIS** \u2014 a brain is a folder of markdown notes. Until one
|
|
22736
|
-
is connected there is nothing to recall and nowhere to remember. Pick with your
|
|
22737
|
-
user, then run it yourself:
|
|
22738
|
-
|
|
22739
|
-
\`\`\`bash
|
|
22740
|
-
memgineering link <their-notes-folder> # they already keep notes
|
|
22741
|
-
memgineering init <path> # they do not \u2014 creates one, laid out
|
|
22742
|
-
\`\`\`
|
|
22743
|
-
|
|
22744
|
-
\`link\` reports what would be stored before anything is indexed. An existing
|
|
22745
|
-
folder needs no index and no frontmatter of its own \u2014 plain markdown is a brain.
|
|
22746
|
-
|
|
22747
|
-
Then run \`memgineering onboard\`: it prints what to ask so the brain knows whose
|
|
22748
|
-
it is. A brain whose \`01_BASE/\` is still template text answers almost nothing.
|
|
22749
|
-
|
|
22750
|
-
Read \`memgineering-setup\` for accounts and which of their tools this reaches.
|
|
22751
|
-
|
|
22752
|
-
Synced instead? \`memgineering login\`, then \`memgineering create <name>\`.`;
|
|
22753
|
-
var POINT_AT_A_BRAIN = `## Signed in, and this machine reads no brain yet.
|
|
22754
|
-
|
|
22755
|
-
\`memgineering brains\` lists what is on the account.
|
|
22756
|
-
\`memgineering use --brain <name>\` points this machine at one.
|
|
22757
|
-
\`memgineering create <name>\` makes one and points here, when the account has none yet.
|
|
22758
|
-
`;
|
|
22759
|
-
function nextStepFor(cfg, signedIn = false) {
|
|
22760
|
-
if (signedIn && cfg.brain.cloud === void 0) {
|
|
22761
|
-
return { id: "point_at_a_brain", markdown: POINT_AT_A_BRAIN };
|
|
22762
|
-
}
|
|
22763
|
-
if (cfg.brain.cloud !== void 0 && cfg.brain.cloud.api_url === apiUrl()) return null;
|
|
22764
|
-
if (!isSetUp(cfg)) return { id: "finish_setup", markdown: FINISH_SETUP };
|
|
22765
|
-
if (cfg.brain.brains.length === 0 && cfg.brain.cloud === void 0) {
|
|
22766
|
-
return { id: "connect_a_brain", markdown: CONNECT_A_BRAIN };
|
|
22767
|
-
}
|
|
22768
|
-
return null;
|
|
22769
|
-
}
|
|
22770
|
-
async function currentNextStep() {
|
|
22771
|
-
try {
|
|
22772
|
-
const stored = await readCredentials().catch(() => null);
|
|
22773
|
-
return nextStepFor(await loadConfig(), stored !== null && stored.api_url === apiUrl());
|
|
22774
|
-
} catch {
|
|
22775
|
-
return null;
|
|
22776
|
-
}
|
|
22777
|
-
}
|
|
22778
|
-
|
|
22779
24058
|
// src/program.ts
|
|
22780
24059
|
init_ui();
|
|
22781
24060
|
init_brand();
|
|
@@ -22785,11 +24064,12 @@ var HOMONYM_VERBS = ["pull", "push", "update"];
|
|
|
22785
24064
|
var NO_FLAT_ALIAS = new Set(HOMONYM_VERBS);
|
|
22786
24065
|
|
|
22787
24066
|
// src/program.ts
|
|
24067
|
+
init_version();
|
|
22788
24068
|
function buildProgram() {
|
|
22789
|
-
const program = new
|
|
24069
|
+
const program = new Command46("memgineering").version(VERSION).description("memgineering \u2014 one memory for the AI you connect").option("--json", "emit JSON to stdout instead of markdown", false).hook("preAction", async (thisCommand, actionCommand) => {
|
|
22790
24070
|
const opts = thisCommand.optsWithGlobals();
|
|
22791
24071
|
if (opts.json || process.env[BRAND.jsonEnvVar] === "1") setJsonMode(true);
|
|
22792
|
-
await announceNextStep(commandPath(actionCommand));
|
|
24072
|
+
await announceNextStep(commandPath(actionCommand), "verb");
|
|
22793
24073
|
});
|
|
22794
24074
|
configureGroupedHelp(program);
|
|
22795
24075
|
program.addCommand(recallCommand());
|
|
@@ -22815,7 +24095,7 @@ function buildProgram() {
|
|
|
22815
24095
|
diffCommand,
|
|
22816
24096
|
listCommand
|
|
22817
24097
|
];
|
|
22818
|
-
const registry = new
|
|
24098
|
+
const registry = new Command46("registry").description(
|
|
22819
24099
|
"the public layer: find, read, adapt, publish, install and keep packages current"
|
|
22820
24100
|
);
|
|
22821
24101
|
for (const factory of publicLayer) registry.addCommand(factory());
|
|
@@ -22842,6 +24122,8 @@ function buildProgram() {
|
|
|
22842
24122
|
program.addCommand(baseWriteCommand());
|
|
22843
24123
|
program.addCommand(retireCommand());
|
|
22844
24124
|
program.addCommand(unretireCommand());
|
|
24125
|
+
program.addCommand(rescopeCommand());
|
|
24126
|
+
program.addCommand(expireCommand());
|
|
22845
24127
|
program.addCommand(retractCommand());
|
|
22846
24128
|
program.addCommand(excludeCommand());
|
|
22847
24129
|
program.addCommand(unexcludeCommand());
|
|
@@ -22890,15 +24172,16 @@ function commandPath(cmd) {
|
|
|
22890
24172
|
const parent = cmd.parent;
|
|
22891
24173
|
return parent && parent.parent ? `${parent.name()} ${cmd.name()}` : cmd.name();
|
|
22892
24174
|
}
|
|
22893
|
-
async function announceNextStep(commandName,
|
|
24175
|
+
async function announceNextStep(commandName, reader) {
|
|
22894
24176
|
if (announced) return;
|
|
22895
24177
|
if (WITHOUT_NEXT_STEP.has(commandName)) return;
|
|
22896
24178
|
const step = await currentNextStep();
|
|
22897
24179
|
if (step === null) return;
|
|
22898
24180
|
announced = true;
|
|
22899
|
-
if (
|
|
24181
|
+
if (step.id === "finish_setup" && reader !== "full") {
|
|
24182
|
+
const text = reader === "person" ? SHORT_SETUP_NOTICE : reader === "verb" && await claimFullSetupNotice() ? step.markdown : SETUP_NUDGE;
|
|
22900
24183
|
process.stderr.write(`
|
|
22901
|
-
${
|
|
24184
|
+
${text}
|
|
22902
24185
|
|
|
22903
24186
|
`);
|
|
22904
24187
|
return;
|
|
@@ -22912,8 +24195,8 @@ function makeParseErrorsActionable(program) {
|
|
|
22912
24195
|
const decorate = (cmd) => {
|
|
22913
24196
|
cmd.showSuggestionAfterError(true);
|
|
22914
24197
|
cmd.configureOutput({
|
|
22915
|
-
outputError: (str,
|
|
22916
|
-
|
|
24198
|
+
outputError: (str, write2) => {
|
|
24199
|
+
write2(str);
|
|
22917
24200
|
if (!/unknown option|unknown command|too many arguments/i.test(str)) return;
|
|
22918
24201
|
const names = [];
|
|
22919
24202
|
for (let node = cmd; node !== null; node = node.parent) {
|
|
@@ -22922,12 +24205,12 @@ function makeParseErrorsActionable(program) {
|
|
|
22922
24205
|
const path = names.join(" ");
|
|
22923
24206
|
const runnable = cmd.commands.filter((sub) => sub._hidden !== true).map((sub) => sub.name()).sort();
|
|
22924
24207
|
if (/unknown command/i.test(str) && runnable.length > 0) {
|
|
22925
|
-
|
|
24208
|
+
write2(`hint: ${path} has ${runnable.join(" ")}
|
|
22926
24209
|
`);
|
|
22927
24210
|
return;
|
|
22928
24211
|
}
|
|
22929
24212
|
const flags = cmd.options.map((o) => o.long ?? o.short).filter(Boolean);
|
|
22930
|
-
|
|
24213
|
+
write2(
|
|
22931
24214
|
flags.length > 0 ? `hint: ${path} takes ${flags.join(" ")}
|
|
22932
24215
|
` : `hint: \`${path} --help\` lists what this takes.
|
|
22933
24216
|
`
|
|
@@ -22954,10 +24237,13 @@ async function run3(argv) {
|
|
|
22954
24237
|
return;
|
|
22955
24238
|
}
|
|
22956
24239
|
}
|
|
22957
|
-
await announceNextStep(
|
|
24240
|
+
await announceNextStep(
|
|
24241
|
+
"",
|
|
24242
|
+
process.stderr.isTTY === true ? "person" : cleaned[2] === void 0 ? "full" : "brief"
|
|
24243
|
+
);
|
|
22958
24244
|
}
|
|
22959
24245
|
await program.parseAsync(cleaned);
|
|
22960
|
-
if (argv[2] !== "update") await backgroundUpdateNotice();
|
|
24246
|
+
if (argv[2] !== "update") await backgroundUpdateNotice(argv[2] === void 0 ? {} : { verb: argv[2] });
|
|
22961
24247
|
}
|
|
22962
24248
|
async function handleError(err) {
|
|
22963
24249
|
const failure = adaptRegistryError(err) ?? err;
|