@massu/core 1.16.2 → 2.0.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 +158 -0
- package/dist/cli.js +2266 -863
- package/dist/hooks/auto-learning-pipeline.js +622 -60
- package/dist/hooks/classify-failure.js +622 -60
- package/dist/hooks/cost-tracker.js +631 -69
- package/dist/hooks/fix-detector.js +631 -69
- package/dist/hooks/incident-pipeline.js +616 -54
- package/dist/hooks/intent-suggester.js +631 -69
- package/dist/hooks/memory-recall.js +771 -75
- package/dist/hooks/post-edit-context.js +633 -71
- package/dist/hooks/post-tool-use.js +659 -197
- package/dist/hooks/pre-compact.js +631 -69
- package/dist/hooks/pre-delete-check.js +635 -73
- package/dist/hooks/pre-tool-use-gate.js +635 -73
- package/dist/hooks/quality-event.js +631 -69
- package/dist/hooks/rule-enforcement-pipeline.js +617 -55
- package/dist/hooks/security-gate.js +631 -69
- package/dist/hooks/session-end.js +1729 -281
- package/dist/hooks/session-start.js +1018 -425
- package/dist/hooks/user-prompt.js +666 -104
- package/package.json +2 -2
|
@@ -49,16 +49,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
49
49
|
));
|
|
50
50
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
51
51
|
|
|
52
|
-
// src/lib/memory-path.ts
|
|
53
|
-
function encodeMemoryDirName(projectRoot) {
|
|
54
|
-
return projectRoot.replace(/\//g, "-");
|
|
55
|
-
}
|
|
56
|
-
var init_memory_path = __esm({
|
|
57
|
-
"src/lib/memory-path.ts"() {
|
|
58
|
-
"use strict";
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
52
|
// src/credentials.ts
|
|
63
53
|
import { homedir } from "os";
|
|
64
54
|
import { resolve } from "path";
|
|
@@ -127,6 +117,475 @@ var init_credentials = __esm({
|
|
|
127
117
|
}
|
|
128
118
|
});
|
|
129
119
|
|
|
120
|
+
// src/lib/fileLock.ts
|
|
121
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
122
|
+
import { dirname } from "path";
|
|
123
|
+
import * as lockfile from "proper-lockfile";
|
|
124
|
+
function readLockHolderPid(lockPath) {
|
|
125
|
+
try {
|
|
126
|
+
const raw = readFileSync2(`${lockPath}.pid`, "utf-8").trim();
|
|
127
|
+
const pid = Number.parseInt(raw, 10);
|
|
128
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
129
|
+
return pid;
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function busyWaitSync(ms) {
|
|
135
|
+
if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`withFileLockSync requires SharedArrayBuffer + Atomics for its retry-loop wait. This Node runtime does not provide them \u2014 refusing to fall back to a CPU spinloop. If you hit this in a sandboxed serverless env, the fix is to perform the lock-protected operation in a host runtime that supports Atomics.`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const sab = new SharedArrayBuffer(4);
|
|
141
|
+
const view = new Int32Array(sab);
|
|
142
|
+
Atomics.wait(view, 0, 0, ms);
|
|
143
|
+
}
|
|
144
|
+
function withFileLockSync(lockPath, fn, opts = {}) {
|
|
145
|
+
mkdirSync2(dirname(lockPath), { recursive: true });
|
|
146
|
+
const staleMs = opts.staleMs ?? 3e4;
|
|
147
|
+
const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
|
|
148
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 100;
|
|
149
|
+
const now = opts.now ?? Date.now;
|
|
150
|
+
const sleep = opts.sleep ?? busyWaitSync;
|
|
151
|
+
const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
|
|
152
|
+
let release = null;
|
|
153
|
+
const deadline = now() + blockMs;
|
|
154
|
+
for (; ; ) {
|
|
155
|
+
try {
|
|
156
|
+
release = lockfile.lockSync(lockPath, {
|
|
157
|
+
stale: staleMs,
|
|
158
|
+
retries: 0,
|
|
159
|
+
realpath: false
|
|
160
|
+
});
|
|
161
|
+
try {
|
|
162
|
+
writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
} catch (err) {
|
|
167
|
+
const e = err;
|
|
168
|
+
const code = e.code;
|
|
169
|
+
if (code !== "ELOCKED" && code !== "EBUSY") {
|
|
170
|
+
throw err;
|
|
171
|
+
}
|
|
172
|
+
if (now() >= deadline) {
|
|
173
|
+
const holderPid = readLockHolderPid(lockPath);
|
|
174
|
+
const remainingMs = Math.max(0, deadline - now());
|
|
175
|
+
const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
|
|
176
|
+
throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
|
|
177
|
+
}
|
|
178
|
+
sleep(pollIntervalMs);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
return fn();
|
|
183
|
+
} finally {
|
|
184
|
+
try {
|
|
185
|
+
if (release) release();
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
rmSync2(`${lockPath}.pid`, { force: true });
|
|
190
|
+
} catch {
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
var FileLockBusyError;
|
|
195
|
+
var init_fileLock = __esm({
|
|
196
|
+
"src/lib/fileLock.ts"() {
|
|
197
|
+
"use strict";
|
|
198
|
+
FileLockBusyError = class extends Error {
|
|
199
|
+
constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
|
|
200
|
+
const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
|
|
201
|
+
super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
|
|
202
|
+
this.lockPath = lockPath;
|
|
203
|
+
this.holderPid = holderPid;
|
|
204
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
205
|
+
this.causeCode = causeCode;
|
|
206
|
+
this.name = "FileLockBusyError";
|
|
207
|
+
}
|
|
208
|
+
lockPath;
|
|
209
|
+
holderPid;
|
|
210
|
+
retryAfterSeconds;
|
|
211
|
+
causeCode;
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// src/lib/sqlite-loader.ts
|
|
217
|
+
import { createRequire } from "module";
|
|
218
|
+
import { spawnSync } from "child_process";
|
|
219
|
+
import { accessSync, appendFileSync, chmodSync as chmodSync2, constants as fsConstants, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
220
|
+
import { dirname as dirname2, join } from "path";
|
|
221
|
+
function isNativeAbiError(err) {
|
|
222
|
+
const code = err?.code;
|
|
223
|
+
if (code === "ERR_DLOPEN_FAILED") return true;
|
|
224
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
225
|
+
return /NODE_MODULE_VERSION/.test(msg) || /was compiled against a different Node\.js version/.test(msg) || /ERR_DLOPEN_FAILED/.test(msg) || /\.node\b/.test(msg) && /dlopen|invalid ELF|mach-o|image not found|no such file|not a valid Win32/i.test(msg);
|
|
226
|
+
}
|
|
227
|
+
function loadBetterSqlite3(opts = {}) {
|
|
228
|
+
if (_testCtor) return _testCtor;
|
|
229
|
+
if (opts.fresh) bustNativeCache();
|
|
230
|
+
return req("better-sqlite3");
|
|
231
|
+
}
|
|
232
|
+
function bustNativeCache() {
|
|
233
|
+
try {
|
|
234
|
+
const cache = req.cache;
|
|
235
|
+
if (!cache) return;
|
|
236
|
+
for (const key of Object.keys(cache)) {
|
|
237
|
+
if (/better[-_]sqlite3|better_sqlite3\.node|[\\/]bindings[\\/]/.test(key)) {
|
|
238
|
+
delete cache[key];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function openDatabase(dbPath, opts = {}) {
|
|
245
|
+
const { selfHeal: selfHealOpt = true, ...ctorOpts } = opts;
|
|
246
|
+
const selfHeal = selfHealOpt && process.env.MASSU_HOOK_RUNTIME !== "1";
|
|
247
|
+
const Ctor = loadBetterSqlite3();
|
|
248
|
+
try {
|
|
249
|
+
return new Ctor(dbPath, ctorOpts);
|
|
250
|
+
} catch (err) {
|
|
251
|
+
if (!isNativeAbiError(err)) {
|
|
252
|
+
throw err;
|
|
253
|
+
}
|
|
254
|
+
if (!selfHeal) {
|
|
255
|
+
recordHealEvent({ phase: "skipped", reason: "self-heal-disabled", abiTo: process.versions.modules });
|
|
256
|
+
throw new MemoryEngineUnusableError("abi-mismatch", detailOf(err));
|
|
257
|
+
}
|
|
258
|
+
const result = (_testHeal ?? attemptNativeHeal)(err);
|
|
259
|
+
if (!result.healed && !result.contended) {
|
|
260
|
+
throw new MemoryEngineUnusableError(result.reason ?? "heal-failed", result.detail ?? detailOf(err));
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const FreshCtor = loadBetterSqlite3({ fresh: true });
|
|
264
|
+
return new FreshCtor(dbPath, ctorOpts);
|
|
265
|
+
} catch (retryErr) {
|
|
266
|
+
throw new MemoryEngineUnusableError("heal-failed", detailOf(retryErr));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function attemptNativeHeal(err) {
|
|
271
|
+
const start = Date.now();
|
|
272
|
+
const abiTo = process.versions.modules;
|
|
273
|
+
const abiFrom = parseAbiFrom(err);
|
|
274
|
+
let pkgDir;
|
|
275
|
+
try {
|
|
276
|
+
pkgDir = dirname2(req.resolve("better-sqlite3/package.json"));
|
|
277
|
+
} catch {
|
|
278
|
+
recordHealEvent({ phase: "skipped", reason: "not-resolvable", abiFrom, abiTo });
|
|
279
|
+
return { healed: false, reason: "missing", abiFrom, abiTo, detail: "better-sqlite3 not resolvable" };
|
|
280
|
+
}
|
|
281
|
+
if (!isWritable(pkgDir)) {
|
|
282
|
+
recordHealEvent({ phase: "skipped", reason: "dir-not-writable", abiFrom, abiTo });
|
|
283
|
+
return { healed: false, reason: "heal-failed", abiFrom, abiTo, detail: `install dir not writable: ${pkgDir}` };
|
|
284
|
+
}
|
|
285
|
+
recordHealEvent({ phase: "attempt", reason: "abi-mismatch", abiFrom, abiTo });
|
|
286
|
+
try {
|
|
287
|
+
return withFileLockSync(
|
|
288
|
+
join(credentialsDir(), "native-heal.lock"),
|
|
289
|
+
() => runRebuild(pkgDir, abiFrom, abiTo, start),
|
|
290
|
+
{ blockMs: 6e4, staleMs: 3e5 }
|
|
291
|
+
);
|
|
292
|
+
} catch (lockErr) {
|
|
293
|
+
const detail = lockErr instanceof Error ? lockErr.message : String(lockErr);
|
|
294
|
+
recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, detail: `lock: ${detail}` });
|
|
295
|
+
return { healed: false, contended: true, reason: "heal-failed", abiFrom, abiTo, detail: `heal lock contended: ${detail}` };
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function runRebuild(pkgDir, abiFrom, abiTo, start) {
|
|
299
|
+
try {
|
|
300
|
+
const FreshCtor = loadBetterSqlite3({ fresh: true });
|
|
301
|
+
new FreshCtor(":memory:").close();
|
|
302
|
+
const res2 = {
|
|
303
|
+
healed: true,
|
|
304
|
+
abiFrom,
|
|
305
|
+
abiTo,
|
|
306
|
+
durationMs: Date.now() - start,
|
|
307
|
+
detail: "already healed by a concurrent process"
|
|
308
|
+
};
|
|
309
|
+
recordHealEvent({ phase: "success", reason: "already-healed", abiFrom, abiTo, durationMs: res2.durationMs });
|
|
310
|
+
return res2;
|
|
311
|
+
} catch (probeErr) {
|
|
312
|
+
if (!isNativeAbiError(probeErr)) {
|
|
313
|
+
return { healed: false, reason: "heal-failed", abiFrom, abiTo, durationMs: Date.now() - start, detail: detailOf(probeErr) };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
|
|
317
|
+
if (prebuildBin) {
|
|
318
|
+
const r = spawnSync(process.execPath, [prebuildBin], {
|
|
319
|
+
cwd: pkgDir,
|
|
320
|
+
encoding: "utf-8",
|
|
321
|
+
timeout: 12e4
|
|
322
|
+
});
|
|
323
|
+
if (r.status === 0) {
|
|
324
|
+
const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
|
|
325
|
+
recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
|
|
326
|
+
return res2;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const gypBin = resolveNodeGypBin();
|
|
330
|
+
if (gypBin && hasCompiler()) {
|
|
331
|
+
const r = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
|
|
332
|
+
cwd: pkgDir,
|
|
333
|
+
encoding: "utf-8",
|
|
334
|
+
timeout: 3e5
|
|
335
|
+
});
|
|
336
|
+
if (r.status === 0) {
|
|
337
|
+
const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
|
|
338
|
+
recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
|
|
339
|
+
return res2;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const res = {
|
|
343
|
+
healed: false,
|
|
344
|
+
reason: "heal-failed",
|
|
345
|
+
abiFrom,
|
|
346
|
+
abiTo,
|
|
347
|
+
durationMs: Date.now() - start,
|
|
348
|
+
detail: prebuildBin ? "prebuild-install failed; node-gyp fallback unavailable or failed" : "no prebuild-install bin resolvable"
|
|
349
|
+
};
|
|
350
|
+
recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, durationMs: res.durationMs });
|
|
351
|
+
return res;
|
|
352
|
+
}
|
|
353
|
+
function recordHealEvent(event) {
|
|
354
|
+
try {
|
|
355
|
+
const rec = {
|
|
356
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
357
|
+
node: process.versions.node,
|
|
358
|
+
platform: process.platform,
|
|
359
|
+
arch: process.arch,
|
|
360
|
+
...event
|
|
361
|
+
};
|
|
362
|
+
const dir = credentialsDir();
|
|
363
|
+
mkdirSync3(dir, { recursive: true });
|
|
364
|
+
const file = join(dir, "native-heal-events.jsonl");
|
|
365
|
+
appendFileSync(file, JSON.stringify(rec) + "\n", "utf-8");
|
|
366
|
+
try {
|
|
367
|
+
chmodSync2(file, 384);
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
} catch {
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function parseAbiFrom(err) {
|
|
374
|
+
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
375
|
+
const m = /NODE_MODULE_VERSION (\d+)/.exec(msg);
|
|
376
|
+
if (m && /^\d+$/.test(m[1])) return m[1];
|
|
377
|
+
return void 0;
|
|
378
|
+
}
|
|
379
|
+
function detailOf(err) {
|
|
380
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
381
|
+
return msg.slice(0, 300);
|
|
382
|
+
}
|
|
383
|
+
function isWritable(dir) {
|
|
384
|
+
try {
|
|
385
|
+
accessSync(dir, fsConstants.W_OK);
|
|
386
|
+
return true;
|
|
387
|
+
} catch {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
function resolveLocalBin(pkgDir, name) {
|
|
392
|
+
const candidates = [
|
|
393
|
+
join(pkgDir, "node_modules", ".bin", name),
|
|
394
|
+
// nested install
|
|
395
|
+
join(pkgDir, "..", ".bin", name),
|
|
396
|
+
// hoisted (node_modules/.bin)
|
|
397
|
+
join(pkgDir, "..", "..", "node_modules", ".bin", name)
|
|
398
|
+
// workspace hoist
|
|
399
|
+
];
|
|
400
|
+
for (const c of candidates) {
|
|
401
|
+
if (existsSync2(c)) return c;
|
|
402
|
+
}
|
|
403
|
+
return void 0;
|
|
404
|
+
}
|
|
405
|
+
function resolveNodeGypBin() {
|
|
406
|
+
try {
|
|
407
|
+
return req.resolve("node-gyp/bin/node-gyp.js");
|
|
408
|
+
} catch {
|
|
409
|
+
return void 0;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function hasCompiler() {
|
|
413
|
+
for (const cc of ["cc", "clang", "gcc"]) {
|
|
414
|
+
try {
|
|
415
|
+
const r = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
|
|
416
|
+
if (r.status === 0) return true;
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
var req, NATIVE_DB_REMEDY, MemoryEngineUnusableError, _testCtor, _testHeal;
|
|
423
|
+
var init_sqlite_loader = __esm({
|
|
424
|
+
"src/lib/sqlite-loader.ts"() {
|
|
425
|
+
"use strict";
|
|
426
|
+
init_credentials();
|
|
427
|
+
init_fileLock();
|
|
428
|
+
req = createRequire(import.meta.url);
|
|
429
|
+
NATIVE_DB_REMEDY = "Run 'massu heal' to rebuild the database engine for your Node version, then restart your MCP client / Claude Code.";
|
|
430
|
+
MemoryEngineUnusableError = class extends Error {
|
|
431
|
+
reason;
|
|
432
|
+
remedy;
|
|
433
|
+
detail;
|
|
434
|
+
constructor(reason, detail) {
|
|
435
|
+
super(
|
|
436
|
+
`Massu memory engine is unusable (${reason}). ${NATIVE_DB_REMEDY}` + (detail ? ` [${detail}]` : "")
|
|
437
|
+
);
|
|
438
|
+
this.name = "MemoryEngineUnusableError";
|
|
439
|
+
this.reason = reason;
|
|
440
|
+
this.remedy = NATIVE_DB_REMEDY;
|
|
441
|
+
this.detail = detail;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
_testCtor = null;
|
|
445
|
+
_testHeal = null;
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
// src/db-driver.ts
|
|
450
|
+
import { createRequire as createRequire2 } from "module";
|
|
451
|
+
import { closeSync, existsSync as existsSync3, mkdtempSync, openSync, readFileSync as readFileSync3, rmSync as rmSync3, fsyncSync } from "fs";
|
|
452
|
+
import { tmpdir } from "os";
|
|
453
|
+
import { join as join2 } from "path";
|
|
454
|
+
function resolveDbEngine() {
|
|
455
|
+
return process.env[DB_ENGINE_ENV] === "better-sqlite3" ? "better-sqlite3" : DEFAULT_DB_ENGINE;
|
|
456
|
+
}
|
|
457
|
+
function nodeSqliteCtor() {
|
|
458
|
+
if (!_nodeCtor) _nodeCtor = req2("node:sqlite").DatabaseSync;
|
|
459
|
+
return _nodeCtor;
|
|
460
|
+
}
|
|
461
|
+
function plainRow(row) {
|
|
462
|
+
if (row == null || typeof row !== "object") return row;
|
|
463
|
+
return { ...row };
|
|
464
|
+
}
|
|
465
|
+
function openNodeSqlite(dbPath, opts) {
|
|
466
|
+
const Ctor = nodeSqliteCtor();
|
|
467
|
+
const raw = new Ctor(dbPath, {
|
|
468
|
+
open: true,
|
|
469
|
+
readOnly: !!opts.readonly,
|
|
470
|
+
// FAITHFULNESS: better-sqlite3's ACTUAL default is foreign_keys=ON (empirically
|
|
471
|
+
// verified — SQLite's own default is OFF, but bs3 enables it). massu was written +
|
|
472
|
+
// tested against that default, so the adapter must match it, or a store that relies
|
|
473
|
+
// on FK-ON-by-default (without an explicit pragma) would silently lose enforcement.
|
|
474
|
+
// Explicit `foreign_keys=OFF/ON` pragmas (e.g. memory-db bulk ops) still override.
|
|
475
|
+
enableForeignKeyConstraints: true,
|
|
476
|
+
allowExtension: false
|
|
477
|
+
});
|
|
478
|
+
let savepointSeq = 0;
|
|
479
|
+
const wrapStmt = (sql) => {
|
|
480
|
+
const st = raw.prepare(sql);
|
|
481
|
+
return {
|
|
482
|
+
run: (...p) => {
|
|
483
|
+
const r = st.run(...p);
|
|
484
|
+
return {
|
|
485
|
+
changes: typeof r.changes === "bigint" ? Number(r.changes) : r.changes,
|
|
486
|
+
lastInsertRowid: r.lastInsertRowid
|
|
487
|
+
};
|
|
488
|
+
},
|
|
489
|
+
get: (...p) => plainRow(st.get(...p)),
|
|
490
|
+
all: (...p) => st.all(...p).map(plainRow)
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
const pragma = (source, options) => {
|
|
494
|
+
const s = String(source).trim();
|
|
495
|
+
if (s.includes("=")) {
|
|
496
|
+
raw.exec(`PRAGMA ${s}`);
|
|
497
|
+
return void 0;
|
|
498
|
+
}
|
|
499
|
+
const rows = raw.prepare(`PRAGMA ${s}`).all().map(plainRow);
|
|
500
|
+
if (options?.simple) {
|
|
501
|
+
const first = rows[0];
|
|
502
|
+
return first ? Object.values(first)[0] : void 0;
|
|
503
|
+
}
|
|
504
|
+
return rows;
|
|
505
|
+
};
|
|
506
|
+
const transaction = (fn) => {
|
|
507
|
+
const run = (...args) => {
|
|
508
|
+
const nested = raw.isTransaction;
|
|
509
|
+
const name = `msp_${savepointSeq++}`;
|
|
510
|
+
if (nested) raw.exec(`SAVEPOINT ${name}`);
|
|
511
|
+
else raw.exec("BEGIN");
|
|
512
|
+
try {
|
|
513
|
+
const out = fn(...args);
|
|
514
|
+
if (nested) raw.exec(`RELEASE ${name}`);
|
|
515
|
+
else raw.exec("COMMIT");
|
|
516
|
+
return out;
|
|
517
|
+
} catch (e) {
|
|
518
|
+
if (nested) {
|
|
519
|
+
raw.exec(`ROLLBACK TO ${name}`);
|
|
520
|
+
raw.exec(`RELEASE ${name}`);
|
|
521
|
+
} else {
|
|
522
|
+
raw.exec("ROLLBACK");
|
|
523
|
+
}
|
|
524
|
+
throw e;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
return run;
|
|
528
|
+
};
|
|
529
|
+
const serialize = () => {
|
|
530
|
+
if (raw.isTransaction) {
|
|
531
|
+
throw new Error(
|
|
532
|
+
"db-driver: .serialize() (node:sqlite engine) cannot run inside an open transaction \u2014 VACUUM is forbidden in a transaction. Serialize before BEGIN, or use MASSU_DB_ENGINE=better-sqlite3 for mid-transaction snapshots."
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const dir = mkdtempSync(join2(tmpdir(), "massu-serialize-"));
|
|
536
|
+
const out = join2(dir, "snapshot.db");
|
|
537
|
+
try {
|
|
538
|
+
raw.exec(`VACUUM INTO '${out.replace(/'/g, "''")}'`);
|
|
539
|
+
const fd = openSync(out, "r");
|
|
540
|
+
try {
|
|
541
|
+
fsyncSync(fd);
|
|
542
|
+
} finally {
|
|
543
|
+
closeSync(fd);
|
|
544
|
+
}
|
|
545
|
+
return readFileSync3(out);
|
|
546
|
+
} finally {
|
|
547
|
+
rmSync3(dir, { recursive: true, force: true });
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
const handle = {
|
|
551
|
+
prepare: (sql) => wrapStmt(sql),
|
|
552
|
+
exec: (sql) => raw.exec(sql),
|
|
553
|
+
pragma,
|
|
554
|
+
transaction,
|
|
555
|
+
serialize,
|
|
556
|
+
close: () => raw.close()
|
|
557
|
+
};
|
|
558
|
+
return handle;
|
|
559
|
+
}
|
|
560
|
+
function openBetterSqlite3(dbPath, opts) {
|
|
561
|
+
return openDatabase(dbPath, opts);
|
|
562
|
+
}
|
|
563
|
+
function openDatabase2(dbPath, opts = {}) {
|
|
564
|
+
return resolveDbEngine() === "better-sqlite3" ? openBetterSqlite3(dbPath, opts) : openNodeSqlite(dbPath, opts);
|
|
565
|
+
}
|
|
566
|
+
var req2, DEFAULT_DB_ENGINE, DB_ENGINE_ENV, _nodeCtor;
|
|
567
|
+
var init_db_driver = __esm({
|
|
568
|
+
"src/db-driver.ts"() {
|
|
569
|
+
"use strict";
|
|
570
|
+
init_sqlite_loader();
|
|
571
|
+
init_sqlite_loader();
|
|
572
|
+
req2 = createRequire2(import.meta.url);
|
|
573
|
+
DEFAULT_DB_ENGINE = "node-sqlite";
|
|
574
|
+
DB_ENGINE_ENV = "MASSU_DB_ENGINE";
|
|
575
|
+
_nodeCtor = null;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// src/lib/memory-path.ts
|
|
580
|
+
function encodeMemoryDirName(projectRoot) {
|
|
581
|
+
return projectRoot.replace(/\//g, "-");
|
|
582
|
+
}
|
|
583
|
+
var init_memory_path = __esm({
|
|
584
|
+
"src/lib/memory-path.ts"() {
|
|
585
|
+
"use strict";
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
130
589
|
// src/config-memory-schema.ts
|
|
131
590
|
import { z } from "zod";
|
|
132
591
|
var MemoryConfigSchema;
|
|
@@ -260,14 +719,40 @@ var init_config_memory_schema = __esm({
|
|
|
260
719
|
// turn of EVERY session, so an unbounded index is a permanent context tax — and the
|
|
261
720
|
// per-session cap bounds only the RATE, never the total.
|
|
262
721
|
indexMaxLines: z.number().int().min(1).default(50)
|
|
722
|
+
}).default({}),
|
|
723
|
+
// --- Cross-repo memory surfacing (plan-living-memory-slice-5-cross-repo-surfacing) ---
|
|
724
|
+
// A decision made in one of your repos can surface in another — opt-in per
|
|
725
|
+
// decision AND opt-in per repo, signed, verified, and materialized ONLY on
|
|
726
|
+
// explicit human acceptance. Local transport is FREE and zero-network.
|
|
727
|
+
//
|
|
728
|
+
// ⛔ BOTH switches default OFF, and OFF means NOTHING EXISTS — no registry, no
|
|
729
|
+
// keys, no inbox, no behavioural difference. Two INDEPENDENT opt-ins:
|
|
730
|
+
// • enabled — may this repo EXPORT its shareable decisions? (opt-in #1)
|
|
731
|
+
// • subscribe — which repo LABELS may this repo IMPORT from? (opt-in #2)
|
|
732
|
+
// `subscribe: []` means import NOTHING. There is deliberately NO `subscribe: all`
|
|
733
|
+
// — a repo you never named is a repo Massu never reads from.
|
|
734
|
+
share: z.object({
|
|
735
|
+
enabled: z.boolean().default(false),
|
|
736
|
+
subscribe: z.array(z.string()).default([]),
|
|
737
|
+
// C-04 — recall surfacing of cross-repo memories. `enabled` defaults true but is
|
|
738
|
+
// CONDITIONAL on `subscribe` being non-empty (empty by default), so the effective
|
|
739
|
+
// default is DORMANT: with `subscribe: []` the recall hook output is byte-identical
|
|
740
|
+
// to today's. `maxCrossRepoItems` caps how many accepted cross-repo items may appear
|
|
741
|
+
// per recall block (default 1); `minScore` is an OPTIONAL strictly-higher floor for
|
|
742
|
+
// cross-repo items (they are, by construction, less relevant than local ones).
|
|
743
|
+
recall: z.object({
|
|
744
|
+
enabled: z.boolean().default(true),
|
|
745
|
+
maxCrossRepoItems: z.number().int().min(0).default(1),
|
|
746
|
+
minScore: z.number().optional()
|
|
747
|
+
}).default({})
|
|
263
748
|
}).default({})
|
|
264
749
|
}).optional();
|
|
265
750
|
}
|
|
266
751
|
});
|
|
267
752
|
|
|
268
753
|
// src/config.ts
|
|
269
|
-
import { resolve as resolve2, dirname } from "path";
|
|
270
|
-
import { existsSync as
|
|
754
|
+
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
755
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
271
756
|
import { homedir as homedir2 } from "os";
|
|
272
757
|
import { parse as parseYaml } from "yaml";
|
|
273
758
|
import { z as z2 } from "zod";
|
|
@@ -275,22 +760,22 @@ function findProjectRoot() {
|
|
|
275
760
|
const cwd = process.cwd();
|
|
276
761
|
let dir = cwd;
|
|
277
762
|
while (true) {
|
|
278
|
-
if (
|
|
763
|
+
if (existsSync4(resolve2(dir, "massu.config.yaml"))) {
|
|
279
764
|
return dir;
|
|
280
765
|
}
|
|
281
|
-
const parent =
|
|
766
|
+
const parent = dirname3(dir);
|
|
282
767
|
if (parent === dir) break;
|
|
283
768
|
dir = parent;
|
|
284
769
|
}
|
|
285
770
|
dir = cwd;
|
|
286
771
|
while (true) {
|
|
287
|
-
if (
|
|
772
|
+
if (existsSync4(resolve2(dir, "package.json"))) {
|
|
288
773
|
return dir;
|
|
289
774
|
}
|
|
290
|
-
if (
|
|
775
|
+
if (existsSync4(resolve2(dir, ".git"))) {
|
|
291
776
|
return dir;
|
|
292
777
|
}
|
|
293
|
-
const parent =
|
|
778
|
+
const parent = dirname3(dir);
|
|
294
779
|
if (parent === dir) break;
|
|
295
780
|
dir = parent;
|
|
296
781
|
}
|
|
@@ -307,8 +792,8 @@ function getConfig() {
|
|
|
307
792
|
const root = getProjectRoot();
|
|
308
793
|
const configPath = resolve2(root, "massu.config.yaml");
|
|
309
794
|
let rawYaml = {};
|
|
310
|
-
if (
|
|
311
|
-
const content =
|
|
795
|
+
if (existsSync4(configPath)) {
|
|
796
|
+
const content = readFileSync4(configPath, "utf-8");
|
|
312
797
|
rawYaml = parseYaml(content) ?? {};
|
|
313
798
|
}
|
|
314
799
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -389,9 +874,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
389
874
|
const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
|
|
390
875
|
if (resolvedKey.apiKey) {
|
|
391
876
|
_config.cloud = {
|
|
392
|
-
enabled: true,
|
|
393
877
|
sync: { memory: true, analytics: true, audit: true },
|
|
394
878
|
..._config.cloud,
|
|
879
|
+
// Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
|
|
880
|
+
// block only to tune `requestTimeoutMs` does not disable its own sync. An
|
|
881
|
+
// EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
|
|
882
|
+
// "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
|
|
883
|
+
enabled: _config.cloud?.enabled ?? true,
|
|
395
884
|
apiKey: resolvedKey.apiKey,
|
|
396
885
|
endpoint: resolvedEndpoint
|
|
397
886
|
};
|
|
@@ -509,7 +998,6 @@ var init_config = __esm({
|
|
|
509
998
|
custom_patterns: z2.array(CustomPatternSchema).default([])
|
|
510
999
|
}).optional(),
|
|
511
1000
|
adr: z2.object({
|
|
512
|
-
detection_phrases: z2.array(z2.string()).default(["chose", "decided", "switching to", "moving from", "going with"]),
|
|
513
1001
|
template: z2.string().default("default"),
|
|
514
1002
|
storage: z2.string().default("database"),
|
|
515
1003
|
output_dir: z2.string().default("docs/adr")
|
|
@@ -611,9 +1099,22 @@ var init_config = __esm({
|
|
|
611
1099
|
})).default([])
|
|
612
1100
|
}).optional();
|
|
613
1101
|
CloudConfigSchema = z2.object({
|
|
614
|
-
|
|
1102
|
+
// OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
|
|
1103
|
+
// explicit value once parsed, so `.default(false)` meant that merely declaring a
|
|
1104
|
+
// `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
|
|
1105
|
+
// then overrode the `enabled: true` auto-enable below via object spread — turning
|
|
1106
|
+
// cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
|
|
1107
|
+
// and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
|
|
1108
|
+
enabled: z2.boolean().optional(),
|
|
615
1109
|
apiKey: z2.string().optional(),
|
|
616
1110
|
endpoint: z2.string().optional(),
|
|
1111
|
+
// Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
|
|
1112
|
+
// (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
|
|
1113
|
+
// from this schema, and zod strips unknown keys — so the knob was unreachable from
|
|
1114
|
+
// massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
|
|
1115
|
+
// 423-observation payload takes ~9.2s against the live ingest. Capped at
|
|
1116
|
+
// SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
|
|
1117
|
+
requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
|
|
617
1118
|
sync: z2.object({
|
|
618
1119
|
memory: z2.boolean().default(true),
|
|
619
1120
|
analytics: z2.boolean().default(true),
|
|
@@ -850,27 +1351,26 @@ var init_memory_vector = __esm({
|
|
|
850
1351
|
});
|
|
851
1352
|
|
|
852
1353
|
// src/db-backup.ts
|
|
853
|
-
import
|
|
854
|
-
import {
|
|
855
|
-
import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
|
|
1354
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
|
|
1355
|
+
import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
|
|
856
1356
|
import { homedir as homedir3 } from "os";
|
|
857
1357
|
function dbBackupsRoot(home = homedir3()) {
|
|
858
1358
|
return resolve3(home, ".massu", "db-backups");
|
|
859
1359
|
}
|
|
860
1360
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
861
1361
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
862
|
-
return
|
|
1362
|
+
return join3(dbBackupsRoot(home), slug);
|
|
863
1363
|
}
|
|
864
1364
|
function backupStamp(nowMs) {
|
|
865
1365
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
866
1366
|
}
|
|
867
1367
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
868
1368
|
const dir = projectBackupDir(projectRoot, home);
|
|
869
|
-
if (!
|
|
1369
|
+
if (!existsSync5(dir)) return [];
|
|
870
1370
|
const out = [];
|
|
871
1371
|
for (const f of readdirSync(dir)) {
|
|
872
1372
|
if (!f.endsWith(".db")) continue;
|
|
873
|
-
const p =
|
|
1373
|
+
const p = join3(dir, f);
|
|
874
1374
|
const st = statSync(p);
|
|
875
1375
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
876
1376
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -883,13 +1383,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
883
1383
|
if (backups.length === 0) return false;
|
|
884
1384
|
const newest = backups[0];
|
|
885
1385
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
886
|
-
if (
|
|
1386
|
+
if (existsSync5(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
887
1387
|
return true;
|
|
888
1388
|
}
|
|
889
1389
|
function integrityOk(dbPath) {
|
|
890
1390
|
let db = null;
|
|
891
1391
|
try {
|
|
892
|
-
db =
|
|
1392
|
+
db = openDatabase2(dbPath, { readonly: true });
|
|
893
1393
|
const rows = db.pragma("integrity_check");
|
|
894
1394
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
895
1395
|
} catch {
|
|
@@ -902,16 +1402,16 @@ function integrityOk(dbPath) {
|
|
|
902
1402
|
}
|
|
903
1403
|
}
|
|
904
1404
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
905
|
-
if (!
|
|
1405
|
+
if (!existsSync5(dbPath)) {
|
|
906
1406
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
907
1407
|
}
|
|
908
1408
|
const dir = projectBackupDir(projectRoot, home);
|
|
909
|
-
|
|
1409
|
+
mkdirSync4(dir, { recursive: true });
|
|
910
1410
|
const name = basename(dbPath, ".db");
|
|
911
|
-
const dest =
|
|
1411
|
+
const dest = join3(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
912
1412
|
let src = null;
|
|
913
1413
|
try {
|
|
914
|
-
src =
|
|
1414
|
+
src = openDatabase2(dbPath, { readonly: true });
|
|
915
1415
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
916
1416
|
} catch (err) {
|
|
917
1417
|
throw new DbBackupError(
|
|
@@ -950,7 +1450,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
950
1450
|
}
|
|
951
1451
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
952
1452
|
try {
|
|
953
|
-
if (!
|
|
1453
|
+
if (!existsSync5(dbPath)) return null;
|
|
954
1454
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
955
1455
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
956
1456
|
} catch (err) {
|
|
@@ -962,6 +1462,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
962
1462
|
var init_db_backup = __esm({
|
|
963
1463
|
"src/db-backup.ts"() {
|
|
964
1464
|
"use strict";
|
|
1465
|
+
init_db_driver();
|
|
965
1466
|
DEFAULT_RETENTION = 5;
|
|
966
1467
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
967
1468
|
DbBackupError = class extends Error {
|
|
@@ -1074,21 +1575,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1074
1575
|
});
|
|
1075
1576
|
|
|
1076
1577
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1077
|
-
import { appendFileSync, mkdirSync as
|
|
1078
|
-
import { join as
|
|
1578
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync6 } from "fs";
|
|
1579
|
+
import { join as join4, dirname as dirname5 } from "path";
|
|
1079
1580
|
function resolveFailureLogPath() {
|
|
1080
1581
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1081
1582
|
if (explicit) return explicit;
|
|
1082
1583
|
let dir = process.cwd();
|
|
1083
1584
|
for (let i = 0; i < 12; i++) {
|
|
1084
|
-
if (
|
|
1085
|
-
return
|
|
1585
|
+
if (existsSync6(join4(dir, ".massu")) || existsSync6(join4(dir, "massu.config.yaml"))) {
|
|
1586
|
+
return join4(dir, ".massu", "hook-failures.jsonl");
|
|
1086
1587
|
}
|
|
1087
|
-
const parent =
|
|
1588
|
+
const parent = dirname5(dir);
|
|
1088
1589
|
if (parent === dir) break;
|
|
1089
1590
|
dir = parent;
|
|
1090
1591
|
}
|
|
1091
|
-
return
|
|
1592
|
+
return join4(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1092
1593
|
}
|
|
1093
1594
|
function recordHookFailure(hook, error, context) {
|
|
1094
1595
|
const record = {
|
|
@@ -1101,8 +1602,8 @@ function recordHookFailure(hook, error, context) {
|
|
|
1101
1602
|
let wroteSomething = false;
|
|
1102
1603
|
try {
|
|
1103
1604
|
const path = resolveFailureLogPath();
|
|
1104
|
-
|
|
1105
|
-
|
|
1605
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
1606
|
+
appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
|
|
1106
1607
|
wroteSomething = true;
|
|
1107
1608
|
} catch {
|
|
1108
1609
|
}
|
|
@@ -1145,9 +1646,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1145
1646
|
});
|
|
1146
1647
|
|
|
1147
1648
|
// src/memory-embedder-tokenizer.ts
|
|
1148
|
-
import { readFileSync as
|
|
1649
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1149
1650
|
function loadVocab(vocabPath) {
|
|
1150
|
-
const lines =
|
|
1651
|
+
const lines = readFileSync5(vocabPath, "utf-8").split("\n");
|
|
1151
1652
|
const vocab = /* @__PURE__ */ new Map();
|
|
1152
1653
|
for (let i = 0; i < lines.length; i++) {
|
|
1153
1654
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1267,9 +1768,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1267
1768
|
|
|
1268
1769
|
// src/memory-embedder.ts
|
|
1269
1770
|
import { fileURLToPath } from "url";
|
|
1270
|
-
import { dirname as
|
|
1271
|
-
import { existsSync as
|
|
1272
|
-
import { createRequire } from "module";
|
|
1771
|
+
import { dirname as dirname6, join as join5 } from "path";
|
|
1772
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1773
|
+
import { createRequire as createRequire3 } from "module";
|
|
1273
1774
|
function getActiveEmbedModel() {
|
|
1274
1775
|
return _activeModel;
|
|
1275
1776
|
}
|
|
@@ -1290,21 +1791,21 @@ function embeddingsDisabled() {
|
|
|
1290
1791
|
}
|
|
1291
1792
|
function resolveModelDir() {
|
|
1292
1793
|
try {
|
|
1293
|
-
let dir =
|
|
1794
|
+
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1294
1795
|
let root = null;
|
|
1295
1796
|
for (let i = 0; i < 8; i++) {
|
|
1296
|
-
if (
|
|
1797
|
+
if (existsSync7(join5(dir, "package.json"))) {
|
|
1297
1798
|
root = dir;
|
|
1298
1799
|
break;
|
|
1299
1800
|
}
|
|
1300
|
-
const parent =
|
|
1801
|
+
const parent = dirname6(dir);
|
|
1301
1802
|
if (parent === dir) break;
|
|
1302
1803
|
dir = parent;
|
|
1303
1804
|
}
|
|
1304
1805
|
if (!root) return null;
|
|
1305
1806
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1306
|
-
const candidate =
|
|
1307
|
-
if (
|
|
1807
|
+
const candidate = join5(root, rel);
|
|
1808
|
+
if (existsSync7(join5(candidate, "model_quantized.onnx"))) return candidate;
|
|
1308
1809
|
}
|
|
1309
1810
|
return null;
|
|
1310
1811
|
} catch {
|
|
@@ -1313,9 +1814,9 @@ function resolveModelDir() {
|
|
|
1313
1814
|
}
|
|
1314
1815
|
function resolveWasmDir() {
|
|
1315
1816
|
try {
|
|
1316
|
-
const
|
|
1317
|
-
const main2 =
|
|
1318
|
-
return
|
|
1817
|
+
const req3 = createRequire3(import.meta.url);
|
|
1818
|
+
const main2 = req3.resolve(ORT_PKG);
|
|
1819
|
+
return dirname6(main2);
|
|
1319
1820
|
} catch {
|
|
1320
1821
|
return null;
|
|
1321
1822
|
}
|
|
@@ -1335,8 +1836,8 @@ async function getSession() {
|
|
|
1335
1836
|
ort.env.wasm.proxy = false;
|
|
1336
1837
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1337
1838
|
_ortTensor = ort.Tensor;
|
|
1338
|
-
const modelPath =
|
|
1339
|
-
const vocabPath =
|
|
1839
|
+
const modelPath = join5(modelDir, "model_quantized.onnx");
|
|
1840
|
+
const vocabPath = join5(modelDir, "vocab.txt");
|
|
1340
1841
|
_vocab = loadVocab(vocabPath);
|
|
1341
1842
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1342
1843
|
executionProviders: ["wasm"],
|
|
@@ -1657,6 +2158,7 @@ __export(memory_db_exports, {
|
|
|
1657
2158
|
migrateAuditLogCheckExtension: () => migrateAuditLogCheckExtension,
|
|
1658
2159
|
migrateMemoryFilesFor4B: () => migrateMemoryFilesFor4B,
|
|
1659
2160
|
migrateObservationEmbeddingChunks: () => migrateObservationEmbeddingChunks,
|
|
2161
|
+
migrateSharedMemoryFor5B: () => migrateSharedMemoryFor5B,
|
|
1660
2162
|
pruneOldConversationTurns: () => pruneOldConversationTurns,
|
|
1661
2163
|
pruneOldObservations: () => pruneOldObservations,
|
|
1662
2164
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
@@ -1673,9 +2175,8 @@ __export(memory_db_exports, {
|
|
|
1673
2175
|
upsertObservationEmbedding: () => upsertObservationEmbedding,
|
|
1674
2176
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
1675
2177
|
});
|
|
1676
|
-
import
|
|
1677
|
-
import {
|
|
1678
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2178
|
+
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2179
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
1679
2180
|
function sanitizeFts5Query(raw) {
|
|
1680
2181
|
const trimmed = raw.trim();
|
|
1681
2182
|
if (!trimmed) return '""';
|
|
@@ -1691,12 +2192,12 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
1691
2192
|
}
|
|
1692
2193
|
function getMemoryDb() {
|
|
1693
2194
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
1694
|
-
const dir =
|
|
1695
|
-
if (!
|
|
1696
|
-
|
|
2195
|
+
const dir = dirname7(dbPath);
|
|
2196
|
+
if (!existsSync8(dir)) {
|
|
2197
|
+
mkdirSync6(dir, { recursive: true });
|
|
1697
2198
|
}
|
|
1698
|
-
const preExisting =
|
|
1699
|
-
const db =
|
|
2199
|
+
const preExisting = existsSync8(dbPath);
|
|
2200
|
+
const db = openDatabase2(dbPath);
|
|
1700
2201
|
db.pragma("journal_mode = WAL");
|
|
1701
2202
|
db.pragma("foreign_keys = ON");
|
|
1702
2203
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -1772,6 +2273,39 @@ function migrateMemoryFilesFor4B(db) {
|
|
|
1772
2273
|
db.exec(`ALTER TABLE ${add.table} ADD COLUMN ${add.name} ${add.decl}`);
|
|
1773
2274
|
}
|
|
1774
2275
|
}
|
|
2276
|
+
function migrateSharedMemoryFor5B(db) {
|
|
2277
|
+
const cols = db.prepare(`PRAGMA table_info(observations)`).all();
|
|
2278
|
+
if (cols.length > 0 && !cols.some((c) => c.name === "shareable")) {
|
|
2279
|
+
db.exec(`ALTER TABLE observations ADD COLUMN shareable INTEGER NOT NULL DEFAULT 0`);
|
|
2280
|
+
}
|
|
2281
|
+
db.exec(`
|
|
2282
|
+
CREATE TABLE IF NOT EXISTS shared_memory_pending (
|
|
2283
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2284
|
+
record_hash TEXT NOT NULL UNIQUE, -- identity + idempotency key (hex sha256)
|
|
2285
|
+
origin_repo_id TEXT NOT NULL, -- the signed origin repo_id (v4 UUID)
|
|
2286
|
+
origin_repo_label TEXT NOT NULL, -- re-slugged on read; NEVER trusted from the wire
|
|
2287
|
+
envelope_raw TEXT NOT NULL, -- the VERBATIM signed bytes; B-05 re-verifies these
|
|
2288
|
+
record_json TEXT NOT NULL, -- the single record's canonical JSON
|
|
2289
|
+
received_at_epoch INTEGER NOT NULL, -- epoch SECONDS (Slice-2 convention)
|
|
2290
|
+
accepted_at_epoch INTEGER, -- set on accept (B-05); NULL while pending
|
|
2291
|
+
refused_at_epoch INTEGER, -- set on refuse (B-06)
|
|
2292
|
+
expired_at_epoch INTEGER -- revocation \u21D2 EXPIRE, never DELETE (B-07 / S-3)
|
|
2293
|
+
);
|
|
2294
|
+
CREATE INDEX IF NOT EXISTS idx_smp_origin ON shared_memory_pending(origin_repo_id);
|
|
2295
|
+
CREATE INDEX IF NOT EXISTS idx_smp_received ON shared_memory_pending(received_at_epoch DESC);
|
|
2296
|
+
`);
|
|
2297
|
+
db.exec(`
|
|
2298
|
+
CREATE TABLE IF NOT EXISTS shared_memory_outbound (
|
|
2299
|
+
record_hash TEXT PRIMARY KEY, -- the exported record's canonical hash
|
|
2300
|
+
observation_id INTEGER NOT NULL, -- the source observations.id (for B-07 revocation)
|
|
2301
|
+
origin_repo_id TEXT NOT NULL, -- this repo's own repo_id at export time
|
|
2302
|
+
seq INTEGER NOT NULL, -- the envelope seq this record first went out in
|
|
2303
|
+
exported_at_epoch INTEGER NOT NULL, -- epoch SECONDS
|
|
2304
|
+
revoked_at_epoch INTEGER -- set when the source row is superseded/expired (B-07)
|
|
2305
|
+
);
|
|
2306
|
+
CREATE INDEX IF NOT EXISTS idx_smo_obs ON shared_memory_outbound(observation_id);
|
|
2307
|
+
`);
|
|
2308
|
+
}
|
|
1775
2309
|
function migrateAuditLogCheckExtension(db) {
|
|
1776
2310
|
const row = db.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='audit_log'").get();
|
|
1777
2311
|
if (!row) return;
|
|
@@ -1794,7 +2328,16 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
1794
2328
|
"memory_file_adopted_human",
|
|
1795
2329
|
"memory_file_rendered",
|
|
1796
2330
|
"memory_file_render_refused",
|
|
1797
|
-
"memory_file_tombstoned"
|
|
2331
|
+
"memory_file_tombstoned",
|
|
2332
|
+
// B-10 (Slice 5) — the seven cross-repo shared-memory events. An existing DB whose
|
|
2333
|
+
// CHECK lacks any of these triggers the table rebuild below.
|
|
2334
|
+
"shared_memory_exported",
|
|
2335
|
+
"shared_memory_export_refused",
|
|
2336
|
+
"shared_memory_imported",
|
|
2337
|
+
"shared_memory_dropped",
|
|
2338
|
+
"shared_memory_accepted",
|
|
2339
|
+
"shared_memory_refused",
|
|
2340
|
+
"shared_memory_revoked"
|
|
1798
2341
|
];
|
|
1799
2342
|
const checkClauseMatch = row.sql.match(/event_type\s+TEXT\s+NOT\s+NULL\s+CHECK\s*\(\s*event_type\s+IN\s*\(([\s\S]*?)\)\s*\)/i);
|
|
1800
2343
|
if (checkClauseMatch) {
|
|
@@ -1813,7 +2356,12 @@ function migrateAuditLogCheckExtension(db) {
|
|
|
1813
2356
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
1814
2357
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
1815
2358
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
1816
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2359
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2360
|
+
-- B-10 (Slice 5) \u2014 cross-repo shared-memory events (must mirror the CREATE +
|
|
2361
|
+
-- the expected[] list above, or the migration would loop rebuilding forever).
|
|
2362
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2363
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2364
|
+
'shared_memory_revoked'
|
|
1817
2365
|
)),
|
|
1818
2366
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
1819
2367
|
model_id TEXT,
|
|
@@ -1890,6 +2438,12 @@ function initMemorySchema(db) {
|
|
|
1890
2438
|
-- credential, or takes a snapshot. A Slice-5 synced memory arrives here with
|
|
1891
2439
|
-- origin='team' and must never reach disk without CR-55's gates.
|
|
1892
2440
|
origin TEXT NOT NULL DEFAULT 'local',
|
|
2441
|
+
-- B-01 (Slice 5, cross-repo surfacing): the per-decision SHARE opt-in. Set to 1
|
|
2442
|
+
-- ONLY by an explicit human act (massu memory share <id>); NEVER by a heuristic,
|
|
2443
|
+
-- score, LLM, hook, or consolidation pass. Export (B-02) reads WHERE shareable=1
|
|
2444
|
+
-- AND origin='local'. Defaults 0 so every pre-existing and machine-written row is
|
|
2445
|
+
-- un-shareable until a human says otherwise (fail-closed).
|
|
2446
|
+
shareable INTEGER NOT NULL DEFAULT 0,
|
|
1893
2447
|
created_at TEXT NOT NULL,
|
|
1894
2448
|
created_at_epoch INTEGER NOT NULL,
|
|
1895
2449
|
FOREIGN KEY(session_id) REFERENCES sessions(session_id) ON DELETE CASCADE
|
|
@@ -2163,7 +2717,13 @@ function initMemorySchema(db) {
|
|
|
2163
2717
|
'code_change', 'rule_enforced', 'approval', 'review', 'commit', 'compaction',
|
|
2164
2718
|
'rule_candidate_emitted', 'rule_promoted', 'rule_dismissed',
|
|
2165
2719
|
'memory_file_ingested', 'memory_file_expired', 'memory_file_adopted_human',
|
|
2166
|
-
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned'
|
|
2720
|
+
'memory_file_rendered', 'memory_file_render_refused', 'memory_file_tombstoned',
|
|
2721
|
+
-- B-10 (Slice 5): cross-repo shared-memory observability. Without these seven,
|
|
2722
|
+
-- nobody could ever answer "what has crossed between my repos?" (and an ingest
|
|
2723
|
+
-- catch would swallow the CHECK violation, silently producing NOTHING).
|
|
2724
|
+
'shared_memory_exported', 'shared_memory_export_refused', 'shared_memory_imported',
|
|
2725
|
+
'shared_memory_dropped', 'shared_memory_accepted', 'shared_memory_refused',
|
|
2726
|
+
'shared_memory_revoked'
|
|
2167
2727
|
)),
|
|
2168
2728
|
actor TEXT NOT NULL DEFAULT 'ai' CHECK(actor IN ('ai', 'human', 'hook', 'agent')),
|
|
2169
2729
|
model_id TEXT,
|
|
@@ -2532,6 +3092,7 @@ function initMemorySchema(db) {
|
|
|
2532
3092
|
`);
|
|
2533
3093
|
migrateObservationEmbeddingChunks(db);
|
|
2534
3094
|
migrateMemoryFilesFor4B(db);
|
|
3095
|
+
migrateSharedMemoryFor5B(db);
|
|
2535
3096
|
ensureRuleCandidatesTable(db);
|
|
2536
3097
|
migrateRuleDelivery(db);
|
|
2537
3098
|
}
|
|
@@ -3396,54 +3957,133 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
|
|
|
3396
3957
|
};
|
|
3397
3958
|
}
|
|
3398
3959
|
}
|
|
3399
|
-
return bestMatch;
|
|
3960
|
+
return bestMatch;
|
|
3961
|
+
}
|
|
3962
|
+
var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
|
|
3963
|
+
var init_memory_db = __esm({
|
|
3964
|
+
"src/memory-db.ts"() {
|
|
3965
|
+
"use strict";
|
|
3966
|
+
init_db_driver();
|
|
3967
|
+
init_config();
|
|
3968
|
+
init_memory_vector();
|
|
3969
|
+
init_db_backup();
|
|
3970
|
+
init_rule_delivery();
|
|
3971
|
+
init_rule_candidate_store();
|
|
3972
|
+
init_hook_failure_signal();
|
|
3973
|
+
init_memory_embed_sweep();
|
|
3974
|
+
MEMORY_SCHEMA_VERSION = 1;
|
|
3975
|
+
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
3976
|
+
MAX_DRAFT_TEXT_LEN = 16384;
|
|
3977
|
+
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
3978
|
+
_temporalColCache = /* @__PURE__ */ new WeakMap();
|
|
3979
|
+
CONSOLIDATION_LESSON_EVIDENCE = "consolidation:session-summary";
|
|
3980
|
+
MEMORY_FILE_TITLE_PREFIX = "[memory-file] ";
|
|
3981
|
+
MEMORY_FILE_TITLE_LIKE = `${MEMORY_FILE_TITLE_PREFIX}%`;
|
|
3982
|
+
USAGE_COUNTER_ARMED_KEY = "usage_counter_armed_epoch";
|
|
3983
|
+
}
|
|
3984
|
+
});
|
|
3985
|
+
|
|
3986
|
+
// src/memory-authorship.ts
|
|
3987
|
+
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3988
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync7, chmodSync as chmodSync3, statSync as statSync2 } from "fs";
|
|
3989
|
+
import { homedir as homedir4 } from "os";
|
|
3990
|
+
import { resolve as resolve5 } from "path";
|
|
3991
|
+
function renderKeyPath(home = homedir4()) {
|
|
3992
|
+
return resolve5(home, ".massu", "render-key");
|
|
3993
|
+
}
|
|
3994
|
+
function readRenderKey(home = homedir4()) {
|
|
3995
|
+
const p = renderKeyPath(home);
|
|
3996
|
+
try {
|
|
3997
|
+
if (!existsSync9(p)) return void 0;
|
|
3998
|
+
const raw = readFileSync6(p);
|
|
3999
|
+
if (raw.length !== 32) return void 0;
|
|
4000
|
+
return raw;
|
|
4001
|
+
} catch {
|
|
4002
|
+
return void 0;
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
function ensureRenderKey(home = homedir4()) {
|
|
4006
|
+
const existing = readRenderKey(home);
|
|
4007
|
+
if (existing) return existing;
|
|
4008
|
+
const p = renderKeyPath(home);
|
|
4009
|
+
try {
|
|
4010
|
+
const dir = resolve5(home, ".massu");
|
|
4011
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
4012
|
+
try {
|
|
4013
|
+
chmodSync3(dir, 448);
|
|
4014
|
+
} catch {
|
|
4015
|
+
}
|
|
4016
|
+
const key = randomBytes(32);
|
|
4017
|
+
writeFileSync3(p, key, { mode: 384 });
|
|
4018
|
+
try {
|
|
4019
|
+
chmodSync3(p, 384);
|
|
4020
|
+
} catch {
|
|
4021
|
+
}
|
|
4022
|
+
return key;
|
|
4023
|
+
} catch {
|
|
4024
|
+
return void 0;
|
|
4025
|
+
}
|
|
4026
|
+
}
|
|
4027
|
+
function mintAuthorship(body, home = homedir4()) {
|
|
4028
|
+
try {
|
|
4029
|
+
const key = ensureRenderKey(home);
|
|
4030
|
+
if (!key) return null;
|
|
4031
|
+
return createHmac("sha256", key).update(body, "utf8").digest("hex");
|
|
4032
|
+
} catch {
|
|
4033
|
+
return null;
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
function verifyAuthorship(body, frontmatter, storeRow, home = homedir4()) {
|
|
4037
|
+
try {
|
|
4038
|
+
if (storeRow?.adopted_human_at_epoch != null) return false;
|
|
4039
|
+
const key = readRenderKey(home);
|
|
4040
|
+
if (!key) return false;
|
|
4041
|
+
const claimed = frontmatter?.[RENDER_MAC_KEY];
|
|
4042
|
+
if (typeof claimed !== "string" || claimed.length === 0) return false;
|
|
4043
|
+
const expected = createHmac("sha256", key).update(body, "utf8").digest("hex");
|
|
4044
|
+
const a = Buffer.from(claimed, "utf8");
|
|
4045
|
+
const b = Buffer.from(expected, "utf8");
|
|
4046
|
+
if (a.length !== b.length) return false;
|
|
4047
|
+
return timingSafeEqual(a, b);
|
|
4048
|
+
} catch {
|
|
4049
|
+
return false;
|
|
4050
|
+
}
|
|
3400
4051
|
}
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
"
|
|
4052
|
+
function extractRenderMac(frontmatter) {
|
|
4053
|
+
const v = frontmatter?.[RENDER_MAC_KEY];
|
|
4054
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
4055
|
+
}
|
|
4056
|
+
var RENDER_MAC_KEY;
|
|
4057
|
+
var init_memory_authorship = __esm({
|
|
4058
|
+
"src/memory-authorship.ts"() {
|
|
3404
4059
|
"use strict";
|
|
3405
|
-
|
|
3406
|
-
init_memory_vector();
|
|
3407
|
-
init_db_backup();
|
|
3408
|
-
init_rule_delivery();
|
|
3409
|
-
init_rule_candidate_store();
|
|
3410
|
-
init_hook_failure_signal();
|
|
3411
|
-
init_memory_embed_sweep();
|
|
3412
|
-
MEMORY_SCHEMA_VERSION = 1;
|
|
3413
|
-
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
3414
|
-
MAX_DRAFT_TEXT_LEN = 16384;
|
|
3415
|
-
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
3416
|
-
_temporalColCache = /* @__PURE__ */ new WeakMap();
|
|
3417
|
-
CONSOLIDATION_LESSON_EVIDENCE = "consolidation:session-summary";
|
|
3418
|
-
MEMORY_FILE_TITLE_PREFIX = "[memory-file] ";
|
|
3419
|
-
MEMORY_FILE_TITLE_LIKE = `${MEMORY_FILE_TITLE_PREFIX}%`;
|
|
3420
|
-
USAGE_COUNTER_ARMED_KEY = "usage_counter_armed_epoch";
|
|
4060
|
+
RENDER_MAC_KEY = "massu_render_mac";
|
|
3421
4061
|
}
|
|
3422
4062
|
});
|
|
3423
4063
|
|
|
3424
4064
|
// src/lib/safe-write.ts
|
|
3425
4065
|
import {
|
|
3426
|
-
writeFileSync as
|
|
4066
|
+
writeFileSync as writeFileSync4,
|
|
3427
4067
|
renameSync,
|
|
3428
|
-
openSync,
|
|
3429
|
-
fsyncSync,
|
|
3430
|
-
closeSync,
|
|
3431
|
-
existsSync as
|
|
4068
|
+
openSync as openSync2,
|
|
4069
|
+
fsyncSync as fsyncSync2,
|
|
4070
|
+
closeSync as closeSync2,
|
|
4071
|
+
existsSync as existsSync10,
|
|
3432
4072
|
realpathSync,
|
|
3433
4073
|
unlinkSync as unlinkSync2,
|
|
3434
|
-
accessSync,
|
|
3435
|
-
statSync as
|
|
3436
|
-
chmodSync as
|
|
4074
|
+
accessSync as accessSync2,
|
|
4075
|
+
statSync as statSync3,
|
|
4076
|
+
chmodSync as chmodSync4,
|
|
3437
4077
|
constants
|
|
3438
4078
|
} from "fs";
|
|
3439
|
-
import { dirname as
|
|
4079
|
+
import { dirname as dirname8, resolve as resolve6, relative, isAbsolute, basename as basename3, sep } from "path";
|
|
3440
4080
|
import { createHash as createHash2 } from "crypto";
|
|
3441
4081
|
function assertContainedIn(rootDir, candidate, opts = {}) {
|
|
3442
4082
|
if (candidate.includes("\0")) {
|
|
3443
4083
|
throw new PathEscapeError("path contains a NUL byte");
|
|
3444
4084
|
}
|
|
3445
|
-
const root =
|
|
3446
|
-
const abs =
|
|
4085
|
+
const root = resolve6(rootDir);
|
|
4086
|
+
const abs = resolve6(root, candidate);
|
|
3447
4087
|
if (!opts.allowNested) {
|
|
3448
4088
|
const rel = relative(root, abs);
|
|
3449
4089
|
if (rel.includes(sep)) {
|
|
@@ -3462,14 +4102,14 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
|
|
|
3462
4102
|
}
|
|
3463
4103
|
function isContainedIn(rootDir, candidate, opts = {}) {
|
|
3464
4104
|
if (candidate.includes("\0")) return false;
|
|
3465
|
-
const root =
|
|
3466
|
-
const abs =
|
|
4105
|
+
const root = resolve6(rootDir);
|
|
4106
|
+
const abs = resolve6(root, candidate);
|
|
3467
4107
|
const rel = relative(root, abs);
|
|
3468
4108
|
if (rel === "" && !opts.allowRoot) return false;
|
|
3469
4109
|
if (rel.startsWith("..") || isAbsolute(rel)) return false;
|
|
3470
4110
|
let probe = abs;
|
|
3471
|
-
while (!
|
|
3472
|
-
const parent =
|
|
4111
|
+
while (!existsSync10(probe) && probe.length > 1) {
|
|
4112
|
+
const parent = dirname8(probe);
|
|
3473
4113
|
if (parent === probe) break;
|
|
3474
4114
|
probe = parent;
|
|
3475
4115
|
}
|
|
@@ -3486,31 +4126,31 @@ function isContainedIn(rootDir, candidate, opts = {}) {
|
|
|
3486
4126
|
return !realRel.startsWith("..") && !isAbsolute(realRel);
|
|
3487
4127
|
}
|
|
3488
4128
|
function atomicWriteFileSync(destPath, contents) {
|
|
3489
|
-
const dir =
|
|
3490
|
-
const tmp =
|
|
4129
|
+
const dir = dirname8(destPath);
|
|
4130
|
+
const tmp = resolve6(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
|
|
3491
4131
|
let destMode;
|
|
3492
|
-
if (
|
|
3493
|
-
|
|
3494
|
-
destMode =
|
|
4132
|
+
if (existsSync10(destPath)) {
|
|
4133
|
+
accessSync2(destPath, constants.W_OK);
|
|
4134
|
+
destMode = statSync3(destPath).mode & 511;
|
|
3495
4135
|
}
|
|
3496
4136
|
let fd;
|
|
3497
4137
|
try {
|
|
3498
|
-
|
|
3499
|
-
if (destMode !== void 0)
|
|
3500
|
-
fd =
|
|
3501
|
-
|
|
3502
|
-
|
|
4138
|
+
writeFileSync4(tmp, contents, "utf-8");
|
|
4139
|
+
if (destMode !== void 0) chmodSync4(tmp, destMode);
|
|
4140
|
+
fd = openSync2(tmp, "r+");
|
|
4141
|
+
fsyncSync2(fd);
|
|
4142
|
+
closeSync2(fd);
|
|
3503
4143
|
fd = void 0;
|
|
3504
4144
|
renameSync(tmp, destPath);
|
|
3505
4145
|
} catch (err) {
|
|
3506
4146
|
if (fd !== void 0) {
|
|
3507
4147
|
try {
|
|
3508
|
-
|
|
4148
|
+
closeSync2(fd);
|
|
3509
4149
|
} catch {
|
|
3510
4150
|
}
|
|
3511
4151
|
}
|
|
3512
4152
|
try {
|
|
3513
|
-
if (
|
|
4153
|
+
if (existsSync10(tmp)) unlinkSync2(tmp);
|
|
3514
4154
|
} catch {
|
|
3515
4155
|
}
|
|
3516
4156
|
throw err;
|
|
@@ -3587,183 +4227,9 @@ var init_safe_write = __esm({
|
|
|
3587
4227
|
}
|
|
3588
4228
|
});
|
|
3589
4229
|
|
|
3590
|
-
// src/memory-authorship.ts
|
|
3591
|
-
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3592
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync5, chmodSync as chmodSync3, statSync as statSync3 } from "fs";
|
|
3593
|
-
import { homedir as homedir4 } from "os";
|
|
3594
|
-
import { resolve as resolve6 } from "path";
|
|
3595
|
-
function renderKeyPath(home = homedir4()) {
|
|
3596
|
-
return resolve6(home, ".massu", "render-key");
|
|
3597
|
-
}
|
|
3598
|
-
function readRenderKey(home = homedir4()) {
|
|
3599
|
-
const p = renderKeyPath(home);
|
|
3600
|
-
try {
|
|
3601
|
-
if (!existsSync8(p)) return void 0;
|
|
3602
|
-
const raw = readFileSync4(p);
|
|
3603
|
-
if (raw.length !== 32) return void 0;
|
|
3604
|
-
return raw;
|
|
3605
|
-
} catch {
|
|
3606
|
-
return void 0;
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
function ensureRenderKey(home = homedir4()) {
|
|
3610
|
-
const existing = readRenderKey(home);
|
|
3611
|
-
if (existing) return existing;
|
|
3612
|
-
const p = renderKeyPath(home);
|
|
3613
|
-
try {
|
|
3614
|
-
const dir = resolve6(home, ".massu");
|
|
3615
|
-
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
3616
|
-
try {
|
|
3617
|
-
chmodSync3(dir, 448);
|
|
3618
|
-
} catch {
|
|
3619
|
-
}
|
|
3620
|
-
const key = randomBytes(32);
|
|
3621
|
-
writeFileSync3(p, key, { mode: 384 });
|
|
3622
|
-
try {
|
|
3623
|
-
chmodSync3(p, 384);
|
|
3624
|
-
} catch {
|
|
3625
|
-
}
|
|
3626
|
-
return key;
|
|
3627
|
-
} catch {
|
|
3628
|
-
return void 0;
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
function mintAuthorship(body, home = homedir4()) {
|
|
3632
|
-
try {
|
|
3633
|
-
const key = ensureRenderKey(home);
|
|
3634
|
-
if (!key) return null;
|
|
3635
|
-
return createHmac("sha256", key).update(body, "utf8").digest("hex");
|
|
3636
|
-
} catch {
|
|
3637
|
-
return null;
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
function verifyAuthorship(body, frontmatter, storeRow, home = homedir4()) {
|
|
3641
|
-
try {
|
|
3642
|
-
if (storeRow?.adopted_human_at_epoch != null) return false;
|
|
3643
|
-
const key = readRenderKey(home);
|
|
3644
|
-
if (!key) return false;
|
|
3645
|
-
const claimed = frontmatter?.[RENDER_MAC_KEY];
|
|
3646
|
-
if (typeof claimed !== "string" || claimed.length === 0) return false;
|
|
3647
|
-
const expected = createHmac("sha256", key).update(body, "utf8").digest("hex");
|
|
3648
|
-
const a = Buffer.from(claimed, "utf8");
|
|
3649
|
-
const b = Buffer.from(expected, "utf8");
|
|
3650
|
-
if (a.length !== b.length) return false;
|
|
3651
|
-
return timingSafeEqual(a, b);
|
|
3652
|
-
} catch {
|
|
3653
|
-
return false;
|
|
3654
|
-
}
|
|
3655
|
-
}
|
|
3656
|
-
function extractRenderMac(frontmatter) {
|
|
3657
|
-
const v = frontmatter?.[RENDER_MAC_KEY];
|
|
3658
|
-
return typeof v === "string" && v.length > 0 ? v : null;
|
|
3659
|
-
}
|
|
3660
|
-
var RENDER_MAC_KEY;
|
|
3661
|
-
var init_memory_authorship = __esm({
|
|
3662
|
-
"src/memory-authorship.ts"() {
|
|
3663
|
-
"use strict";
|
|
3664
|
-
RENDER_MAC_KEY = "massu_render_mac";
|
|
3665
|
-
}
|
|
3666
|
-
});
|
|
3667
|
-
|
|
3668
|
-
// src/lib/fileLock.ts
|
|
3669
|
-
import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3670
|
-
import { dirname as dirname7 } from "path";
|
|
3671
|
-
import * as lockfile from "proper-lockfile";
|
|
3672
|
-
function readLockHolderPid(lockPath) {
|
|
3673
|
-
try {
|
|
3674
|
-
const raw = readFileSync5(`${lockPath}.pid`, "utf-8").trim();
|
|
3675
|
-
const pid = Number.parseInt(raw, 10);
|
|
3676
|
-
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
3677
|
-
return pid;
|
|
3678
|
-
} catch {
|
|
3679
|
-
return null;
|
|
3680
|
-
}
|
|
3681
|
-
}
|
|
3682
|
-
function busyWaitSync(ms) {
|
|
3683
|
-
if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
|
|
3684
|
-
throw new Error(
|
|
3685
|
-
`withFileLockSync requires SharedArrayBuffer + Atomics for its retry-loop wait. This Node runtime does not provide them \u2014 refusing to fall back to a CPU spinloop. If you hit this in a sandboxed serverless env, the fix is to perform the lock-protected operation in a host runtime that supports Atomics.`
|
|
3686
|
-
);
|
|
3687
|
-
}
|
|
3688
|
-
const sab = new SharedArrayBuffer(4);
|
|
3689
|
-
const view = new Int32Array(sab);
|
|
3690
|
-
Atomics.wait(view, 0, 0, ms);
|
|
3691
|
-
}
|
|
3692
|
-
function withFileLockSync(lockPath, fn, opts = {}) {
|
|
3693
|
-
mkdirSync6(dirname7(lockPath), { recursive: true });
|
|
3694
|
-
const staleMs = opts.staleMs ?? 3e4;
|
|
3695
|
-
const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
|
|
3696
|
-
const pollIntervalMs = opts.pollIntervalMs ?? 100;
|
|
3697
|
-
const now = opts.now ?? Date.now;
|
|
3698
|
-
const sleep = opts.sleep ?? busyWaitSync;
|
|
3699
|
-
const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
|
|
3700
|
-
let release = null;
|
|
3701
|
-
const deadline = now() + blockMs;
|
|
3702
|
-
for (; ; ) {
|
|
3703
|
-
try {
|
|
3704
|
-
release = lockfile.lockSync(lockPath, {
|
|
3705
|
-
stale: staleMs,
|
|
3706
|
-
retries: 0,
|
|
3707
|
-
realpath: false
|
|
3708
|
-
});
|
|
3709
|
-
try {
|
|
3710
|
-
writeFileSync4(`${lockPath}.pid`, String(process.pid), "utf-8");
|
|
3711
|
-
} catch {
|
|
3712
|
-
}
|
|
3713
|
-
break;
|
|
3714
|
-
} catch (err) {
|
|
3715
|
-
const e = err;
|
|
3716
|
-
const code = e.code;
|
|
3717
|
-
if (code !== "ELOCKED" && code !== "EBUSY") {
|
|
3718
|
-
throw err;
|
|
3719
|
-
}
|
|
3720
|
-
if (now() >= deadline) {
|
|
3721
|
-
const holderPid = readLockHolderPid(lockPath);
|
|
3722
|
-
const remainingMs = Math.max(0, deadline - now());
|
|
3723
|
-
const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
|
|
3724
|
-
throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
|
|
3725
|
-
}
|
|
3726
|
-
sleep(pollIntervalMs);
|
|
3727
|
-
}
|
|
3728
|
-
}
|
|
3729
|
-
try {
|
|
3730
|
-
return fn();
|
|
3731
|
-
} finally {
|
|
3732
|
-
try {
|
|
3733
|
-
if (release) release();
|
|
3734
|
-
} catch {
|
|
3735
|
-
}
|
|
3736
|
-
try {
|
|
3737
|
-
rmSync3(`${lockPath}.pid`, { force: true });
|
|
3738
|
-
} catch {
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
}
|
|
3742
|
-
var FileLockBusyError;
|
|
3743
|
-
var init_fileLock = __esm({
|
|
3744
|
-
"src/lib/fileLock.ts"() {
|
|
3745
|
-
"use strict";
|
|
3746
|
-
FileLockBusyError = class extends Error {
|
|
3747
|
-
constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
|
|
3748
|
-
const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
|
|
3749
|
-
super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
|
|
3750
|
-
this.lockPath = lockPath;
|
|
3751
|
-
this.holderPid = holderPid;
|
|
3752
|
-
this.retryAfterSeconds = retryAfterSeconds;
|
|
3753
|
-
this.causeCode = causeCode;
|
|
3754
|
-
this.name = "FileLockBusyError";
|
|
3755
|
-
}
|
|
3756
|
-
lockPath;
|
|
3757
|
-
holderPid;
|
|
3758
|
-
retryAfterSeconds;
|
|
3759
|
-
causeCode;
|
|
3760
|
-
};
|
|
3761
|
-
}
|
|
3762
|
-
});
|
|
3763
|
-
|
|
3764
4230
|
// src/memory-index-region.ts
|
|
3765
|
-
import { readFileSync as
|
|
3766
|
-
import { dirname as
|
|
4231
|
+
import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
|
|
4232
|
+
import { dirname as dirname9, join as join6 } from "path";
|
|
3767
4233
|
function parseRegion(content) {
|
|
3768
4234
|
const begins = countOccurrences(content, BEGIN_SENTINEL);
|
|
3769
4235
|
const ends = countOccurrences(content, END_SENTINEL);
|
|
@@ -3849,7 +4315,7 @@ function assertOutsideRegionUnchanged(pre, post) {
|
|
|
3849
4315
|
}
|
|
3850
4316
|
}
|
|
3851
4317
|
function memoryIndexLockPath(memoryDir) {
|
|
3852
|
-
return
|
|
4318
|
+
return join6(dirname9(memoryDir), ".massu-memory-index.lock");
|
|
3853
4319
|
}
|
|
3854
4320
|
function withMemoryIndexLock(memoryDir, fn) {
|
|
3855
4321
|
return withFileLockSync(memoryIndexLockPath(memoryDir), fn, {
|
|
@@ -3861,9 +4327,9 @@ function withMemoryIndexLock(memoryDir, fn) {
|
|
|
3861
4327
|
});
|
|
3862
4328
|
}
|
|
3863
4329
|
function readMemoryIndex(indexPath) {
|
|
3864
|
-
if (!
|
|
4330
|
+
if (!existsSync11(indexPath)) return void 0;
|
|
3865
4331
|
try {
|
|
3866
|
-
return
|
|
4332
|
+
return readFileSync7(indexPath, "utf8");
|
|
3867
4333
|
} catch {
|
|
3868
4334
|
return void 0;
|
|
3869
4335
|
}
|
|
@@ -3899,12 +4365,24 @@ var init_memory_index_region = __esm({
|
|
|
3899
4365
|
}
|
|
3900
4366
|
});
|
|
3901
4367
|
|
|
4368
|
+
// src/memory-origin.ts
|
|
4369
|
+
function isLocalOrigin(o) {
|
|
4370
|
+
return o === LOCAL_ORIGIN;
|
|
4371
|
+
}
|
|
4372
|
+
var LOCAL_ORIGIN;
|
|
4373
|
+
var init_memory_origin = __esm({
|
|
4374
|
+
"src/memory-origin.ts"() {
|
|
4375
|
+
"use strict";
|
|
4376
|
+
LOCAL_ORIGIN = "local";
|
|
4377
|
+
}
|
|
4378
|
+
});
|
|
4379
|
+
|
|
3902
4380
|
// src/rule-candidate-snapshot.ts
|
|
3903
|
-
import { existsSync as
|
|
4381
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
3904
4382
|
function takeSnapshots(paths) {
|
|
3905
4383
|
const out = /* @__PURE__ */ new Map();
|
|
3906
4384
|
for (const p of paths) {
|
|
3907
|
-
if (
|
|
4385
|
+
if (existsSync12(p)) out.set(p, readFileSync8(p, "utf-8"));
|
|
3908
4386
|
else out.set(p, null);
|
|
3909
4387
|
}
|
|
3910
4388
|
return out;
|
|
@@ -3914,7 +4392,7 @@ function restoreSnapshots(snapshot) {
|
|
|
3914
4392
|
for (const [path, content] of snapshot) {
|
|
3915
4393
|
try {
|
|
3916
4394
|
if (content === null) {
|
|
3917
|
-
if (
|
|
4395
|
+
if (existsSync12(path)) unlinkSync3(path);
|
|
3918
4396
|
} else {
|
|
3919
4397
|
writeFileSync5(path, content, "utf-8");
|
|
3920
4398
|
}
|
|
@@ -3931,8 +4409,8 @@ var init_rule_candidate_snapshot = __esm({
|
|
|
3931
4409
|
});
|
|
3932
4410
|
|
|
3933
4411
|
// src/memory-file-ingest.ts
|
|
3934
|
-
import { readFileSync as
|
|
3935
|
-
import { join as
|
|
4412
|
+
import { readFileSync as readFileSync10, existsSync as existsSync14, readdirSync as readdirSync2 } from "fs";
|
|
4413
|
+
import { join as join8 } from "path";
|
|
3936
4414
|
import { parse as parseYaml2 } from "yaml";
|
|
3937
4415
|
import { basename as pathBasename } from "path";
|
|
3938
4416
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -4038,8 +4516,8 @@ function upsertMemoryFileMirror(db, f) {
|
|
|
4038
4516
|
return "inserted";
|
|
4039
4517
|
}
|
|
4040
4518
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
4041
|
-
if (!
|
|
4042
|
-
const content =
|
|
4519
|
+
if (!existsSync14(filePath)) return "skipped";
|
|
4520
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4043
4521
|
if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
|
|
4044
4522
|
process.stderr.write(
|
|
4045
4523
|
`[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
|
|
@@ -4109,21 +4587,21 @@ ${body}` : body;
|
|
|
4109
4587
|
}
|
|
4110
4588
|
function backfillMemoryFiles(db, memoryDir, sessionId) {
|
|
4111
4589
|
const stats = { inserted: 0, updated: 0, skipped: 0, total: 0 };
|
|
4112
|
-
if (!
|
|
4590
|
+
if (!existsSync14(memoryDir)) return stats;
|
|
4113
4591
|
const files = readdirSync2(memoryDir).filter(
|
|
4114
4592
|
(f) => f.endsWith(".md") && f !== "MEMORY.md"
|
|
4115
4593
|
);
|
|
4116
4594
|
stats.total = files.length;
|
|
4117
4595
|
const sid = sessionId ?? `backfill-${Date.now()}`;
|
|
4118
4596
|
for (const file of files) {
|
|
4119
|
-
const result = ingestMemoryFile(db, sid,
|
|
4597
|
+
const result = ingestMemoryFile(db, sid, join8(memoryDir, file));
|
|
4120
4598
|
stats[result]++;
|
|
4121
4599
|
}
|
|
4122
4600
|
return stats;
|
|
4123
4601
|
}
|
|
4124
4602
|
function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
|
|
4125
4603
|
try {
|
|
4126
|
-
if (!
|
|
4604
|
+
if (!existsSync14(memoryDir)) return 0;
|
|
4127
4605
|
let entries;
|
|
4128
4606
|
try {
|
|
4129
4607
|
entries = readdirSync2(memoryDir);
|
|
@@ -4135,7 +4613,7 @@ function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
|
|
|
4135
4613
|
for (const file of files) {
|
|
4136
4614
|
liveNames.add(stripMdExtension(file));
|
|
4137
4615
|
try {
|
|
4138
|
-
const content =
|
|
4616
|
+
const content = readFileSync10(join8(memoryDir, file), "utf-8");
|
|
4139
4617
|
const fm = readMemoryFileFrontmatter(content);
|
|
4140
4618
|
if (fm && typeof fm.name === "string" && fm.name) liveNames.add(fm.name);
|
|
4141
4619
|
} catch {
|
|
@@ -4289,18 +4767,18 @@ var init_memory_render_path = __esm({
|
|
|
4289
4767
|
});
|
|
4290
4768
|
|
|
4291
4769
|
// src/memory-tombstones.ts
|
|
4292
|
-
import { readFileSync as
|
|
4293
|
-
import { join as
|
|
4770
|
+
import { readFileSync as readFileSync13, appendFileSync as appendFileSync3, existsSync as existsSync17 } from "fs";
|
|
4771
|
+
import { join as join11 } from "path";
|
|
4294
4772
|
function tombstoneLedgerPath(memoryDir) {
|
|
4295
|
-
return
|
|
4773
|
+
return join11(memoryDir, TOMBSTONE_LEDGER);
|
|
4296
4774
|
}
|
|
4297
4775
|
function readTombstones(memoryDir) {
|
|
4298
4776
|
const out = /* @__PURE__ */ new Map();
|
|
4299
4777
|
const p = tombstoneLedgerPath(memoryDir);
|
|
4300
|
-
if (!
|
|
4778
|
+
if (!existsSync17(p)) return out;
|
|
4301
4779
|
let raw;
|
|
4302
4780
|
try {
|
|
4303
|
-
raw =
|
|
4781
|
+
raw = readFileSync13(p, "utf8");
|
|
4304
4782
|
} catch {
|
|
4305
4783
|
return out;
|
|
4306
4784
|
}
|
|
@@ -4368,26 +4846,26 @@ var init_memory_llm = __esm({
|
|
|
4368
4846
|
// src/memory-backup.ts
|
|
4369
4847
|
import {
|
|
4370
4848
|
readdirSync as readdirSync3,
|
|
4371
|
-
mkdirSync as
|
|
4849
|
+
mkdirSync as mkdirSync11,
|
|
4372
4850
|
copyFileSync as copyFileSync2,
|
|
4373
4851
|
statSync as statSync4,
|
|
4374
|
-
existsSync as
|
|
4375
|
-
rmSync as
|
|
4376
|
-
readFileSync as
|
|
4852
|
+
existsSync as existsSync18,
|
|
4853
|
+
rmSync as rmSync5,
|
|
4854
|
+
readFileSync as readFileSync14
|
|
4377
4855
|
} from "fs";
|
|
4378
|
-
import { homedir as
|
|
4379
|
-
import { join as
|
|
4380
|
-
function backupsRoot(home =
|
|
4856
|
+
import { homedir as homedir10 } from "os";
|
|
4857
|
+
import { join as join12, resolve as resolve7 } from "path";
|
|
4858
|
+
function backupsRoot(home = homedir10()) {
|
|
4381
4859
|
return resolve7(home, ".massu", "memory-backups");
|
|
4382
4860
|
}
|
|
4383
4861
|
function backupStamp2(nowMs) {
|
|
4384
4862
|
return new Date(nowMs).toISOString().replace(/:/g, "-");
|
|
4385
4863
|
}
|
|
4386
4864
|
function newestMtimeMs(dir) {
|
|
4387
|
-
if (!
|
|
4865
|
+
if (!existsSync18(dir)) return 0;
|
|
4388
4866
|
let newest = 0;
|
|
4389
4867
|
for (const entry of readdirSync3(dir)) {
|
|
4390
|
-
const p =
|
|
4868
|
+
const p = join12(dir, entry);
|
|
4391
4869
|
try {
|
|
4392
4870
|
const st = statSync4(p);
|
|
4393
4871
|
if (st.isFile() && st.mtimeMs > newest) newest = st.mtimeMs;
|
|
@@ -4397,12 +4875,12 @@ function newestMtimeMs(dir) {
|
|
|
4397
4875
|
}
|
|
4398
4876
|
return newest;
|
|
4399
4877
|
}
|
|
4400
|
-
function listBackups(home =
|
|
4878
|
+
function listBackups(home = homedir10()) {
|
|
4401
4879
|
const root = backupsRoot(home);
|
|
4402
|
-
if (!
|
|
4880
|
+
if (!existsSync18(root)) return [];
|
|
4403
4881
|
const out = [];
|
|
4404
4882
|
for (const stamp of readdirSync3(root)) {
|
|
4405
|
-
const dir =
|
|
4883
|
+
const dir = join12(root, stamp);
|
|
4406
4884
|
try {
|
|
4407
4885
|
const st = statSync4(dir);
|
|
4408
4886
|
if (!st.isDirectory()) continue;
|
|
@@ -4413,28 +4891,28 @@ function listBackups(home = homedir6()) {
|
|
|
4413
4891
|
}
|
|
4414
4892
|
return out.sort((a, b) => b.createdMs - a.createdMs);
|
|
4415
4893
|
}
|
|
4416
|
-
function hasFreshBackup(memoryDir, home =
|
|
4894
|
+
function hasFreshBackup(memoryDir, home = homedir10()) {
|
|
4417
4895
|
const backups = listBackups(home);
|
|
4418
4896
|
if (backups.length === 0) return false;
|
|
4419
4897
|
const newestChange = newestMtimeMs(memoryDir);
|
|
4420
4898
|
return backups[0].createdMs >= newestChange;
|
|
4421
4899
|
}
|
|
4422
|
-
function takeBackup(memoryDir, nowMs, home =
|
|
4423
|
-
if (!
|
|
4900
|
+
function takeBackup(memoryDir, nowMs, home = homedir10(), retention = DEFAULT_RETENTION2) {
|
|
4901
|
+
if (!existsSync18(memoryDir)) {
|
|
4424
4902
|
throw new BackupError(`memory directory does not exist: ${memoryDir}`);
|
|
4425
4903
|
}
|
|
4426
4904
|
const stamp = backupStamp2(nowMs);
|
|
4427
|
-
const dir =
|
|
4905
|
+
const dir = join12(backupsRoot(home), stamp);
|
|
4428
4906
|
try {
|
|
4429
|
-
|
|
4907
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
4430
4908
|
for (const entry of readdirSync3(memoryDir)) {
|
|
4431
|
-
const srcPath =
|
|
4909
|
+
const srcPath = join12(memoryDir, entry);
|
|
4432
4910
|
try {
|
|
4433
4911
|
if (!statSync4(srcPath).isFile()) continue;
|
|
4434
4912
|
} catch {
|
|
4435
4913
|
continue;
|
|
4436
4914
|
}
|
|
4437
|
-
copyFileSync2(srcPath,
|
|
4915
|
+
copyFileSync2(srcPath, join12(dir, entry));
|
|
4438
4916
|
}
|
|
4439
4917
|
} catch (err) {
|
|
4440
4918
|
throw new BackupError(`backup failed: ${err.message}`);
|
|
@@ -4442,11 +4920,11 @@ function takeBackup(memoryDir, nowMs, home = homedir6(), retention = DEFAULT_RET
|
|
|
4442
4920
|
pruneBackups(home, retention);
|
|
4443
4921
|
return { stamp, dir, createdMs: nowMs };
|
|
4444
4922
|
}
|
|
4445
|
-
function pruneBackups(home =
|
|
4923
|
+
function pruneBackups(home = homedir10(), retention = DEFAULT_RETENTION2) {
|
|
4446
4924
|
const backups = listBackups(home);
|
|
4447
4925
|
for (const stale of backups.slice(Math.max(retention, 1))) {
|
|
4448
4926
|
try {
|
|
4449
|
-
|
|
4927
|
+
rmSync5(stale.dir, { recursive: true, force: true });
|
|
4450
4928
|
} catch {
|
|
4451
4929
|
}
|
|
4452
4930
|
}
|
|
@@ -4499,9 +4977,9 @@ __export(memory_renderer_exports, {
|
|
|
4499
4977
|
renderMemoryFiles: () => renderMemoryFiles,
|
|
4500
4978
|
stripFrontmatter: () => stripFrontmatter
|
|
4501
4979
|
});
|
|
4502
|
-
import { existsSync as
|
|
4503
|
-
import { join as
|
|
4504
|
-
import { homedir as
|
|
4980
|
+
import { existsSync as existsSync19, readFileSync as readFileSync15 } from "fs";
|
|
4981
|
+
import { join as join13 } from "path";
|
|
4982
|
+
import { homedir as homedir11 } from "os";
|
|
4505
4983
|
import { createHash as createHash5 } from "crypto";
|
|
4506
4984
|
function renderMemoryFiles(db, candidates, opts) {
|
|
4507
4985
|
const config = opts.config ?? resolveMemoryFilesConfig();
|
|
@@ -4510,11 +4988,11 @@ function renderMemoryFiles(db, candidates, opts) {
|
|
|
4510
4988
|
}
|
|
4511
4989
|
const dryRun = opts.dryRun === true;
|
|
4512
4990
|
const now = opts.now ?? Date.now();
|
|
4513
|
-
const home = opts.home ??
|
|
4991
|
+
const home = opts.home ?? homedir11();
|
|
4514
4992
|
const audit = opts.audit ?? (() => {
|
|
4515
4993
|
});
|
|
4516
4994
|
const { memoryDir } = opts;
|
|
4517
|
-
if (!
|
|
4995
|
+
if (!existsSync19(memoryDir)) {
|
|
4518
4996
|
return EMPTY("no_memory_dir");
|
|
4519
4997
|
}
|
|
4520
4998
|
try {
|
|
@@ -4542,7 +5020,7 @@ function renderLocked(db, candidates, o) {
|
|
|
4542
5020
|
const ownerOf = relPathOwnerLookup(db);
|
|
4543
5021
|
const planned = [];
|
|
4544
5022
|
for (const c of candidates) {
|
|
4545
|
-
if (c.origin
|
|
5023
|
+
if (!isLocalOrigin(c.origin)) {
|
|
4546
5024
|
refusals.push({ name: c.name, reason: "non_local_origin", detail: c.origin || "unknown" });
|
|
4547
5025
|
audit("memory_file_render_refused", { name: c.name, reason: "non_local_origin" });
|
|
4548
5026
|
continue;
|
|
@@ -4582,8 +5060,8 @@ function renderLocked(db, candidates, o) {
|
|
|
4582
5060
|
refusals.push({ name: c.name, reason: "tombstoned" });
|
|
4583
5061
|
continue;
|
|
4584
5062
|
}
|
|
4585
|
-
if (
|
|
4586
|
-
const existing =
|
|
5063
|
+
if (existsSync19(absPath)) {
|
|
5064
|
+
const existing = readFileSync15(absPath, "utf8");
|
|
4587
5065
|
const fm = readMemoryFileFrontmatter(existing);
|
|
4588
5066
|
const storeRow = db.prepare(
|
|
4589
5067
|
`SELECT massu_authored, massu_render_mac, adopted_human_at_epoch
|
|
@@ -4632,7 +5110,7 @@ function renderLocked(db, candidates, o) {
|
|
|
4632
5110
|
return EMPTY("backup_failed");
|
|
4633
5111
|
}
|
|
4634
5112
|
}
|
|
4635
|
-
const indexPath = o.indexPath ??
|
|
5113
|
+
const indexPath = o.indexPath ?? join13(memoryDir, "MEMORY.md");
|
|
4636
5114
|
const targets = [...planned.map((p) => p.absPath), indexPath];
|
|
4637
5115
|
const snapshot = takeSnapshots(targets);
|
|
4638
5116
|
let bytesWritten = 0;
|
|
@@ -4786,6 +5264,7 @@ var init_memory_renderer = __esm({
|
|
|
4786
5264
|
init_memory_authorship();
|
|
4787
5265
|
init_memory_render_path();
|
|
4788
5266
|
init_memory_tombstones();
|
|
5267
|
+
init_memory_origin();
|
|
4789
5268
|
init_memory_llm();
|
|
4790
5269
|
init_memory_backup();
|
|
4791
5270
|
init_memory_index_region();
|
|
@@ -4806,18 +5285,21 @@ var init_memory_renderer = __esm({
|
|
|
4806
5285
|
// src/memory-render-candidates.ts
|
|
4807
5286
|
var memory_render_candidates_exports = {};
|
|
4808
5287
|
__export(memory_render_candidates_exports, {
|
|
5288
|
+
RENDERABLE_MEMORY_TYPES: () => RENDERABLE_MEMORY_TYPES,
|
|
4809
5289
|
loadRenderCandidates: () => loadRenderCandidates
|
|
4810
5290
|
});
|
|
4811
5291
|
function loadRenderCandidates(db) {
|
|
4812
5292
|
const cfg = resolveMemoryFilesConfig();
|
|
5293
|
+
const typePlaceholders = RENDERABLE_MEMORY_TYPES.map(() => "?").join(", ");
|
|
4813
5294
|
const rows = db.prepare(
|
|
4814
5295
|
`SELECT id, title, detail, importance, COALESCE(origin, 'local') AS origin
|
|
4815
5296
|
FROM observations
|
|
4816
5297
|
WHERE importance >= ?
|
|
4817
5298
|
AND COALESCE(expired_at_epoch, 0) = 0
|
|
5299
|
+
AND type IN (${typePlaceholders})
|
|
4818
5300
|
ORDER BY importance DESC, created_at_epoch DESC
|
|
4819
5301
|
LIMIT 50`
|
|
4820
|
-
).all(cfg.renderMinImportance);
|
|
5302
|
+
).all(cfg.renderMinImportance, ...RENDERABLE_MEMORY_TYPES);
|
|
4821
5303
|
return rows.filter((r) => !r.title.startsWith("[memory-file]")).map((r) => ({
|
|
4822
5304
|
observationId: r.id,
|
|
4823
5305
|
name: r.title,
|
|
@@ -4827,10 +5309,17 @@ function loadRenderCandidates(db) {
|
|
|
4827
5309
|
origin: r.origin
|
|
4828
5310
|
}));
|
|
4829
5311
|
}
|
|
5312
|
+
var RENDERABLE_MEMORY_TYPES;
|
|
4830
5313
|
var init_memory_render_candidates = __esm({
|
|
4831
5314
|
"src/memory-render-candidates.ts"() {
|
|
4832
5315
|
"use strict";
|
|
4833
5316
|
init_memory_files_config();
|
|
5317
|
+
RENDERABLE_MEMORY_TYPES = [
|
|
5318
|
+
"decision",
|
|
5319
|
+
"failed_attempt",
|
|
5320
|
+
"incident_near_miss",
|
|
5321
|
+
"cr_violation"
|
|
5322
|
+
];
|
|
4834
5323
|
}
|
|
4835
5324
|
});
|
|
4836
5325
|
|
|
@@ -4996,17 +5485,17 @@ var init_manifest_registry = __esm({
|
|
|
4996
5485
|
});
|
|
4997
5486
|
|
|
4998
5487
|
// src/detect/package-detector.ts
|
|
4999
|
-
import { readFileSync as
|
|
5000
|
-
import { join as
|
|
5488
|
+
import { readFileSync as readFileSync16, existsSync as existsSync20, statSync as statSync5, lstatSync, readdirSync as readdirSync4 } from "fs";
|
|
5489
|
+
import { join as join14, relative as relative2 } from "path";
|
|
5001
5490
|
import { parse as parseToml } from "smol-toml";
|
|
5002
5491
|
function safeRead(path) {
|
|
5003
5492
|
try {
|
|
5004
|
-
if (!
|
|
5493
|
+
if (!existsSync20(path)) return null;
|
|
5005
5494
|
const ls = lstatSync(path);
|
|
5006
5495
|
if (ls.isSymbolicLink()) return null;
|
|
5007
5496
|
const st = statSync5(path);
|
|
5008
5497
|
if (!st.isFile()) return null;
|
|
5009
|
-
return
|
|
5498
|
+
return readFileSync16(path, "utf-8");
|
|
5010
5499
|
} catch {
|
|
5011
5500
|
return null;
|
|
5012
5501
|
}
|
|
@@ -5037,7 +5526,7 @@ function parsePackageJson(path, directory, root, warnings) {
|
|
|
5037
5526
|
const peer = Object.keys(
|
|
5038
5527
|
pkg.peerDependencies ?? {}
|
|
5039
5528
|
);
|
|
5040
|
-
const hasTs = deps.includes("typescript") || devDeps.includes("typescript") ||
|
|
5529
|
+
const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync20(join14(directory, "tsconfig.json"));
|
|
5041
5530
|
const language = hasTs ? "typescript" : "javascript";
|
|
5042
5531
|
const scripts = Object.keys(
|
|
5043
5532
|
pkg.scripts ?? {}
|
|
@@ -5463,8 +5952,8 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
5463
5952
|
let dirEntries = null;
|
|
5464
5953
|
for (const entry of getManifestRegistry2()) {
|
|
5465
5954
|
if (!entry.pattern.startsWith("*")) {
|
|
5466
|
-
const path =
|
|
5467
|
-
if (!
|
|
5955
|
+
const path = join14(dir, entry.pattern);
|
|
5956
|
+
if (!existsSync20(path)) continue;
|
|
5468
5957
|
const m = entry.parse(path, dir, root, warnings);
|
|
5469
5958
|
if (m !== null) out.push(m);
|
|
5470
5959
|
} else {
|
|
@@ -5477,8 +5966,8 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
5477
5966
|
}
|
|
5478
5967
|
for (const fname of dirEntries) {
|
|
5479
5968
|
if (!matchManifestPattern2(fname, entry.pattern)) continue;
|
|
5480
|
-
const path =
|
|
5481
|
-
if (!
|
|
5969
|
+
const path = join14(dir, fname);
|
|
5970
|
+
if (!existsSync20(path)) continue;
|
|
5482
5971
|
const m = entry.parse(path, dir, root, warnings);
|
|
5483
5972
|
if (m !== null) out.push(m);
|
|
5484
5973
|
}
|
|
@@ -5488,7 +5977,7 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
5488
5977
|
}
|
|
5489
5978
|
function listSubdirs(dir) {
|
|
5490
5979
|
try {
|
|
5491
|
-
return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) =>
|
|
5980
|
+
return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join14(dir, e.name));
|
|
5492
5981
|
} catch {
|
|
5493
5982
|
return [];
|
|
5494
5983
|
}
|
|
@@ -5498,8 +5987,8 @@ function detectPackageManifests(projectRoot) {
|
|
|
5498
5987
|
const manifests = [];
|
|
5499
5988
|
manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
|
|
5500
5989
|
for (const ws of WORKSPACE_DIRS) {
|
|
5501
|
-
const wsRoot =
|
|
5502
|
-
if (!
|
|
5990
|
+
const wsRoot = join14(projectRoot, ws);
|
|
5991
|
+
if (!existsSync20(wsRoot)) continue;
|
|
5503
5992
|
for (const sub of listSubdirs(wsRoot)) {
|
|
5504
5993
|
manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
|
|
5505
5994
|
for (const sub2 of listSubdirs(sub)) {
|
|
@@ -11678,38 +12167,38 @@ var init_source_dir_detector = __esm({
|
|
|
11678
12167
|
});
|
|
11679
12168
|
|
|
11680
12169
|
// src/detect/monorepo-detector.ts
|
|
11681
|
-
import { readFileSync as
|
|
11682
|
-
import { join as
|
|
12170
|
+
import { readFileSync as readFileSync17, existsSync as existsSync21, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync5 } from "fs";
|
|
12171
|
+
import { join as join15, relative as relative3 } from "path";
|
|
11683
12172
|
import { parse as parseYaml3 } from "yaml";
|
|
11684
12173
|
import { parse as parseToml2 } from "smol-toml";
|
|
11685
12174
|
function safeReadText(path) {
|
|
11686
12175
|
try {
|
|
11687
|
-
if (!
|
|
12176
|
+
if (!existsSync21(path)) return null;
|
|
11688
12177
|
const ls = lstatSync2(path);
|
|
11689
12178
|
if (ls.isSymbolicLink()) return null;
|
|
11690
12179
|
const st = statSync6(path);
|
|
11691
12180
|
if (!st.isFile()) return null;
|
|
11692
|
-
return
|
|
12181
|
+
return readFileSync17(path, "utf-8");
|
|
11693
12182
|
} catch {
|
|
11694
12183
|
return null;
|
|
11695
12184
|
}
|
|
11696
12185
|
}
|
|
11697
12186
|
function firstManifestIn(dir) {
|
|
11698
12187
|
for (const m of MANIFEST_PRIORITY) {
|
|
11699
|
-
if (
|
|
12188
|
+
if (existsSync21(join15(dir, m))) return m;
|
|
11700
12189
|
}
|
|
11701
12190
|
return null;
|
|
11702
12191
|
}
|
|
11703
12192
|
function manifestName(dir, manifest) {
|
|
11704
12193
|
try {
|
|
11705
12194
|
if (manifest === "package.json") {
|
|
11706
|
-
const raw = safeReadText(
|
|
12195
|
+
const raw = safeReadText(join15(dir, "package.json"));
|
|
11707
12196
|
if (!raw) return null;
|
|
11708
12197
|
const pkg = JSON.parse(raw);
|
|
11709
12198
|
return typeof pkg.name === "string" ? pkg.name : null;
|
|
11710
12199
|
}
|
|
11711
12200
|
if (manifest === "pyproject.toml") {
|
|
11712
|
-
const raw = safeReadText(
|
|
12201
|
+
const raw = safeReadText(join15(dir, "pyproject.toml"));
|
|
11713
12202
|
if (!raw) return null;
|
|
11714
12203
|
const toml = parseToml2(raw);
|
|
11715
12204
|
const project = toml.project;
|
|
@@ -11720,7 +12209,7 @@ function manifestName(dir, manifest) {
|
|
|
11720
12209
|
return null;
|
|
11721
12210
|
}
|
|
11722
12211
|
if (manifest === "Cargo.toml") {
|
|
11723
|
-
const raw = safeReadText(
|
|
12212
|
+
const raw = safeReadText(join15(dir, "Cargo.toml"));
|
|
11724
12213
|
if (!raw) return null;
|
|
11725
12214
|
const toml = parseToml2(raw);
|
|
11726
12215
|
const pkg = toml.package;
|
|
@@ -11728,7 +12217,7 @@ function manifestName(dir, manifest) {
|
|
|
11728
12217
|
return null;
|
|
11729
12218
|
}
|
|
11730
12219
|
if (manifest === "go.mod") {
|
|
11731
|
-
const raw = safeReadText(
|
|
12220
|
+
const raw = safeReadText(join15(dir, "go.mod"));
|
|
11732
12221
|
if (!raw) return null;
|
|
11733
12222
|
for (const line of raw.split(/\r?\n/)) {
|
|
11734
12223
|
const trimmed = line.trim();
|
|
@@ -11752,7 +12241,7 @@ function pkgFromDir(root, dir) {
|
|
|
11752
12241
|
}
|
|
11753
12242
|
function listSubdirs2(dir) {
|
|
11754
12243
|
try {
|
|
11755
|
-
return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) =>
|
|
12244
|
+
return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join15(dir, e.name));
|
|
11756
12245
|
} catch {
|
|
11757
12246
|
return [];
|
|
11758
12247
|
}
|
|
@@ -11760,8 +12249,8 @@ function listSubdirs2(dir) {
|
|
|
11760
12249
|
function genericWorkspaces(root) {
|
|
11761
12250
|
const out = [];
|
|
11762
12251
|
for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
|
|
11763
|
-
const p =
|
|
11764
|
-
if (!
|
|
12252
|
+
const p = join15(root, parent);
|
|
12253
|
+
if (!existsSync21(p)) continue;
|
|
11765
12254
|
for (const sub of listSubdirs2(p)) {
|
|
11766
12255
|
const pkg = pkgFromDir(root, sub);
|
|
11767
12256
|
if (pkg) out.push(pkg);
|
|
@@ -11770,7 +12259,7 @@ function genericWorkspaces(root) {
|
|
|
11770
12259
|
return out;
|
|
11771
12260
|
}
|
|
11772
12261
|
function detectYarnWorkspaces(root) {
|
|
11773
|
-
const raw = safeReadText(
|
|
12262
|
+
const raw = safeReadText(join15(root, "package.json"));
|
|
11774
12263
|
if (!raw) return null;
|
|
11775
12264
|
let pkg;
|
|
11776
12265
|
try {
|
|
@@ -11785,7 +12274,7 @@ function detectYarnWorkspaces(root) {
|
|
|
11785
12274
|
return expandWorkspaceGlobs(root, globs);
|
|
11786
12275
|
}
|
|
11787
12276
|
function detectPnpmWorkspaces(root) {
|
|
11788
|
-
const raw = safeReadText(
|
|
12277
|
+
const raw = safeReadText(join15(root, "pnpm-workspace.yaml"));
|
|
11789
12278
|
if (!raw) return null;
|
|
11790
12279
|
try {
|
|
11791
12280
|
const parsed = parseYaml3(raw);
|
|
@@ -11801,8 +12290,8 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
11801
12290
|
for (const pattern of globs) {
|
|
11802
12291
|
const parts = pattern.split("/");
|
|
11803
12292
|
if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
|
|
11804
|
-
const parent =
|
|
11805
|
-
if (!
|
|
12293
|
+
const parent = join15(root, parts[0]);
|
|
12294
|
+
if (!existsSync21(parent)) continue;
|
|
11806
12295
|
for (const sub of listSubdirs2(parent)) {
|
|
11807
12296
|
const pkg = pkgFromDir(root, sub);
|
|
11808
12297
|
if (pkg && !seen.has(pkg.path)) {
|
|
@@ -11812,8 +12301,8 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
11812
12301
|
}
|
|
11813
12302
|
continue;
|
|
11814
12303
|
}
|
|
11815
|
-
const direct =
|
|
11816
|
-
if (
|
|
12304
|
+
const direct = join15(root, pattern);
|
|
12305
|
+
if (existsSync21(direct)) {
|
|
11817
12306
|
const pkg = pkgFromDir(root, direct);
|
|
11818
12307
|
if (pkg && !seen.has(pkg.path)) {
|
|
11819
12308
|
seen.add(pkg.path);
|
|
@@ -11824,16 +12313,16 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
11824
12313
|
return out;
|
|
11825
12314
|
}
|
|
11826
12315
|
function hasTurbo(root) {
|
|
11827
|
-
return
|
|
12316
|
+
return existsSync21(join15(root, "turbo.json"));
|
|
11828
12317
|
}
|
|
11829
12318
|
function hasNx(root) {
|
|
11830
|
-
return
|
|
12319
|
+
return existsSync21(join15(root, "nx.json"));
|
|
11831
12320
|
}
|
|
11832
12321
|
function hasLerna(root) {
|
|
11833
|
-
return
|
|
12322
|
+
return existsSync21(join15(root, "lerna.json"));
|
|
11834
12323
|
}
|
|
11835
12324
|
function hasBazel(root) {
|
|
11836
|
-
return
|
|
12325
|
+
return existsSync21(join15(root, "WORKSPACE")) || existsSync21(join15(root, "WORKSPACE.bazel")) || existsSync21(join15(root, "MODULE.bazel"));
|
|
11837
12326
|
}
|
|
11838
12327
|
function detectMonorepo(projectRoot) {
|
|
11839
12328
|
const nested = [];
|
|
@@ -12024,8 +12513,8 @@ var init_vr_command_map = __esm({
|
|
|
12024
12513
|
});
|
|
12025
12514
|
|
|
12026
12515
|
// src/detect/domain-inferrer.ts
|
|
12027
|
-
import { existsSync as
|
|
12028
|
-
import { join as
|
|
12516
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6 } from "fs";
|
|
12517
|
+
import { join as join16 } from "path";
|
|
12029
12518
|
function titleCase(s) {
|
|
12030
12519
|
if (!s) return s;
|
|
12031
12520
|
return s.split(/[-_\s]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
|
|
@@ -12045,8 +12534,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
|
|
|
12045
12534
|
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
12046
12535
|
const seen = /* @__PURE__ */ new Set();
|
|
12047
12536
|
for (const rel of effective) {
|
|
12048
|
-
const abs =
|
|
12049
|
-
if (!
|
|
12537
|
+
const abs = join16(root, rel);
|
|
12538
|
+
if (!existsSync22(abs)) continue;
|
|
12050
12539
|
try {
|
|
12051
12540
|
for (const e of readdirSync6(abs, { withFileTypes: true })) {
|
|
12052
12541
|
if (!e.isDirectory()) continue;
|
|
@@ -12133,8 +12622,8 @@ var init_domain_inferrer = __esm({
|
|
|
12133
12622
|
});
|
|
12134
12623
|
|
|
12135
12624
|
// src/detect/regex-fallback.ts
|
|
12136
|
-
import { existsSync as
|
|
12137
|
-
import { resolve as resolve8, join as
|
|
12625
|
+
import { existsSync as existsSync23, readdirSync as readdirSync7, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
|
|
12626
|
+
import { resolve as resolve8, join as join17, basename as basename4 } from "path";
|
|
12138
12627
|
function introspectPython(detection, projectRoot) {
|
|
12139
12628
|
const sourceDir = resolveSourceDir(detection, "python", projectRoot);
|
|
12140
12629
|
if (!sourceDir) return null;
|
|
@@ -12301,9 +12790,9 @@ function resolveSourceDir(detection, lang, projectRoot) {
|
|
|
12301
12790
|
if (list.length > 0) {
|
|
12302
12791
|
const first = list[0];
|
|
12303
12792
|
const abs = resolve8(projectRoot, first);
|
|
12304
|
-
return
|
|
12793
|
+
return existsSync23(abs) ? abs : null;
|
|
12305
12794
|
}
|
|
12306
|
-
return
|
|
12795
|
+
return existsSync23(projectRoot) ? projectRoot : null;
|
|
12307
12796
|
}
|
|
12308
12797
|
function sampleFiles(dir, nameRegex, pathFilter) {
|
|
12309
12798
|
const out = [];
|
|
@@ -12323,7 +12812,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
|
|
|
12323
12812
|
if (entry === "__pycache__") continue;
|
|
12324
12813
|
if (entry === "venv" || entry === ".venv") continue;
|
|
12325
12814
|
if (entry === "dist" || entry === "build") continue;
|
|
12326
|
-
const child =
|
|
12815
|
+
const child = join17(path, entry);
|
|
12327
12816
|
let st;
|
|
12328
12817
|
try {
|
|
12329
12818
|
st = statSync7(child);
|
|
@@ -12348,7 +12837,7 @@ function readSafe(path) {
|
|
|
12348
12837
|
try {
|
|
12349
12838
|
const st = statSync7(path);
|
|
12350
12839
|
if (st.size > MAX_FILE_BYTES) return null;
|
|
12351
|
-
return
|
|
12840
|
+
return readFileSync18(path, "utf-8");
|
|
12352
12841
|
} catch {
|
|
12353
12842
|
return null;
|
|
12354
12843
|
}
|
|
@@ -12676,9 +13165,9 @@ function detectDrift(currentConfig, actualDetection) {
|
|
|
12676
13165
|
}
|
|
12677
13166
|
const detectedManifestPaths = new Set(actualDetection.manifests.map((m) => m.relativePath));
|
|
12678
13167
|
const declaredManifestPaths = /* @__PURE__ */ new Set();
|
|
12679
|
-
const
|
|
12680
|
-
if (
|
|
12681
|
-
for (const p of
|
|
13168
|
+
const canonical2 = currentConfig.canonical_paths;
|
|
13169
|
+
if (canonical2 && typeof canonical2.manifest_paths === "string") {
|
|
13170
|
+
for (const p of canonical2.manifest_paths.split(",").map((s) => s.trim())) {
|
|
12682
13171
|
if (p) declaredManifestPaths.add(p);
|
|
12683
13172
|
}
|
|
12684
13173
|
}
|
|
@@ -12725,10 +13214,34 @@ var init_drift = __esm({
|
|
|
12725
13214
|
// src/hooks/session-start.ts
|
|
12726
13215
|
init_memory_db();
|
|
12727
13216
|
|
|
13217
|
+
// src/team-rule-sync.ts
|
|
13218
|
+
import { existsSync as existsSync13, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
|
|
13219
|
+
import { homedir as homedir6 } from "os";
|
|
13220
|
+
import { join as join7, dirname as dirname10 } from "path";
|
|
13221
|
+
|
|
13222
|
+
// src/security/promotion-apply-mac.ts
|
|
13223
|
+
init_memory_authorship();
|
|
13224
|
+
import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
13225
|
+
import { homedir as homedir5 } from "os";
|
|
13226
|
+
var DOMAIN = "massu.promotion-apply.v1";
|
|
13227
|
+
function canonical(f) {
|
|
13228
|
+
return [DOMAIN, f.origin, f.org_id, f.prompt_hash, f.destination, f.draft_text].join("\n");
|
|
13229
|
+
}
|
|
13230
|
+
function deriveApplyKey(renderKey) {
|
|
13231
|
+
return createHmac2("sha256", renderKey).update(DOMAIN, "utf8").digest();
|
|
13232
|
+
}
|
|
13233
|
+
function computePromotionApplyMac(fields, home = homedir5()) {
|
|
13234
|
+
try {
|
|
13235
|
+
const key = ensureRenderKey(home);
|
|
13236
|
+
if (!key) return null;
|
|
13237
|
+
return createHmac2("sha256", deriveApplyKey(key)).update(canonical(fields), "utf8").digest("hex");
|
|
13238
|
+
} catch {
|
|
13239
|
+
return null;
|
|
13240
|
+
}
|
|
13241
|
+
}
|
|
13242
|
+
|
|
12728
13243
|
// src/team-rule-sync.ts
|
|
12729
13244
|
init_config();
|
|
12730
|
-
import { existsSync as existsSync11, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
12731
|
-
import { join as join5, dirname as dirname9 } from "path";
|
|
12732
13245
|
|
|
12733
13246
|
// src/license.ts
|
|
12734
13247
|
init_config();
|
|
@@ -12779,13 +13292,13 @@ function verifyEd25519SignedEnvelope(key, payload) {
|
|
|
12779
13292
|
if (typeof k !== "string") continue;
|
|
12780
13293
|
canonicalObj[k] = payload[k];
|
|
12781
13294
|
}
|
|
12782
|
-
const
|
|
13295
|
+
const canonical2 = JSON.stringify(canonicalObj, [...payloadKeys].sort());
|
|
12783
13296
|
try {
|
|
12784
13297
|
const der = Buffer.concat([SPKI_ED25519_PREFIX, Buffer.from(key.pubkeyBytes)]);
|
|
12785
13298
|
const pubkey = createPublicKey({ key: der, format: "der", type: "spki" });
|
|
12786
13299
|
const ok = cryptoVerify(
|
|
12787
13300
|
null,
|
|
12788
|
-
Buffer.from(
|
|
13301
|
+
Buffer.from(canonical2, "utf-8"),
|
|
12789
13302
|
pubkey,
|
|
12790
13303
|
Buffer.from(sig, "base64")
|
|
12791
13304
|
);
|
|
@@ -12998,6 +13511,7 @@ init_config();
|
|
|
12998
13511
|
|
|
12999
13512
|
// src/rule-candidate-applier.ts
|
|
13000
13513
|
init_memory_db();
|
|
13514
|
+
init_memory_origin();
|
|
13001
13515
|
|
|
13002
13516
|
// src/rule-candidate-funnel.ts
|
|
13003
13517
|
init_memory_db();
|
|
@@ -13125,11 +13639,11 @@ async function pullTeamPromotions(db, opts = {}) {
|
|
|
13125
13639
|
result.revoked_handled += 1;
|
|
13126
13640
|
continue;
|
|
13127
13641
|
}
|
|
13128
|
-
if (
|
|
13642
|
+
if (existsSync13(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
|
|
13129
13643
|
result.skipped += 1;
|
|
13130
13644
|
continue;
|
|
13131
13645
|
}
|
|
13132
|
-
materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId);
|
|
13646
|
+
materializeCandidate(db, projectRoot, candidatePath, p, signedOrgId, opts.home ?? homedir6());
|
|
13133
13647
|
result.materialized += 1;
|
|
13134
13648
|
}
|
|
13135
13649
|
const serverCursor = typeof envelope.cursor === "number" ? envelope.cursor : 0;
|
|
@@ -13161,7 +13675,7 @@ function isValidWirePromotion(p) {
|
|
|
13161
13675
|
return typeof r.prompt_hash === "string" && PROMPT_HASH_RE.test(r.prompt_hash) && typeof r.destination === "string" && typeof r.draft_text === "string" && typeof r.promoted_by === "string" && typeof r.promoted_at === "string";
|
|
13162
13676
|
}
|
|
13163
13677
|
function sidecarPath(projectRoot, promptHash) {
|
|
13164
|
-
return
|
|
13678
|
+
return join7(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
|
|
13165
13679
|
}
|
|
13166
13680
|
function alreadyApplied(db, promptHash) {
|
|
13167
13681
|
try {
|
|
@@ -13175,7 +13689,7 @@ function alreadyApplied(db, promptHash) {
|
|
|
13175
13689
|
}
|
|
13176
13690
|
}
|
|
13177
13691
|
function handleRevocation(db, projectRoot, candidatePath, promptHash) {
|
|
13178
|
-
if (
|
|
13692
|
+
if (existsSync13(candidatePath)) {
|
|
13179
13693
|
try {
|
|
13180
13694
|
unlinkSync4(candidatePath);
|
|
13181
13695
|
} catch {
|
|
@@ -13189,8 +13703,12 @@ function handleRevocation(db, projectRoot, candidatePath, promptHash) {
|
|
|
13189
13703
|
);
|
|
13190
13704
|
}
|
|
13191
13705
|
}
|
|
13192
|
-
function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
|
|
13706
|
+
function materializeCandidate(db, projectRoot, candidatePath, p, orgId, home) {
|
|
13193
13707
|
const promptText = p.draft_text.replace(/\n+/g, " ").slice(0, 200) || `team rule ${p.prompt_hash}`;
|
|
13708
|
+
const applyMac = computePromotionApplyMac(
|
|
13709
|
+
{ origin: "team", org_id: orgId, prompt_hash: p.prompt_hash, destination: p.destination, draft_text: p.draft_text },
|
|
13710
|
+
home
|
|
13711
|
+
);
|
|
13194
13712
|
const sidecar = {
|
|
13195
13713
|
// Standard RuleCandidatePayload fields (so `/massu-rule approve` → readCandidate
|
|
13196
13714
|
// → validateCandidatePayload passes), synthesized from the promotion.
|
|
@@ -13213,6 +13731,7 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
|
|
|
13213
13731
|
promoted_by: p.promoted_by,
|
|
13214
13732
|
promoted_at: p.promoted_at,
|
|
13215
13733
|
signature_verified: true,
|
|
13734
|
+
...applyMac ? { apply_mac: applyMac } : {},
|
|
13216
13735
|
...p.hardened === true ? { hardened: true } : {}
|
|
13217
13736
|
},
|
|
13218
13737
|
// Extra fields the `/massu-rule approve` flow reads to drive the apply (the
|
|
@@ -13223,8 +13742,8 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
|
|
|
13223
13742
|
draft_text: p.draft_text,
|
|
13224
13743
|
...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
|
|
13225
13744
|
};
|
|
13226
|
-
const dir =
|
|
13227
|
-
if (!
|
|
13745
|
+
const dir = dirname10(candidatePath);
|
|
13746
|
+
if (!existsSync13(dir)) mkdirSync8(dir, { recursive: true });
|
|
13228
13747
|
writeFileSync6(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
13229
13748
|
try {
|
|
13230
13749
|
shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
|
|
@@ -13279,23 +13798,24 @@ function failSync(db, reason) {
|
|
|
13279
13798
|
// src/hooks/session-start.ts
|
|
13280
13799
|
init_memory_file_ingest();
|
|
13281
13800
|
init_config();
|
|
13282
|
-
|
|
13283
|
-
import {
|
|
13801
|
+
init_db_driver();
|
|
13802
|
+
import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
|
|
13803
|
+
import { join as join18, resolve as resolve9 } from "path";
|
|
13284
13804
|
import { parse as parseYaml4 } from "yaml";
|
|
13285
13805
|
|
|
13286
13806
|
// src/capability-advisor.ts
|
|
13287
13807
|
import { createHash as createHash4 } from "crypto";
|
|
13288
|
-
import { existsSync as
|
|
13289
|
-
import { homedir as
|
|
13290
|
-
import { dirname as
|
|
13291
|
-
function advisorStatePath(home =
|
|
13292
|
-
return
|
|
13808
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
13809
|
+
import { homedir as homedir7 } from "os";
|
|
13810
|
+
import { dirname as dirname11, join as join9 } from "path";
|
|
13811
|
+
function advisorStatePath(home = homedir7()) {
|
|
13812
|
+
return join9(home, ".massu", "advisor-state.json");
|
|
13293
13813
|
}
|
|
13294
13814
|
function readAdvisorState(home) {
|
|
13295
13815
|
try {
|
|
13296
13816
|
const p = advisorStatePath(home);
|
|
13297
|
-
if (!
|
|
13298
|
-
return JSON.parse(
|
|
13817
|
+
if (!existsSync15(p)) return {};
|
|
13818
|
+
return JSON.parse(readFileSync11(p, "utf-8"));
|
|
13299
13819
|
} catch {
|
|
13300
13820
|
return {};
|
|
13301
13821
|
}
|
|
@@ -13303,7 +13823,7 @@ function readAdvisorState(home) {
|
|
|
13303
13823
|
function writeAdvisorState(state, home) {
|
|
13304
13824
|
try {
|
|
13305
13825
|
const p = advisorStatePath(home);
|
|
13306
|
-
|
|
13826
|
+
mkdirSync9(dirname11(p), { recursive: true });
|
|
13307
13827
|
writeFileSync7(p, JSON.stringify(state, null, 2), { mode: 384 });
|
|
13308
13828
|
} catch {
|
|
13309
13829
|
}
|
|
@@ -13453,11 +13973,85 @@ var localModelAdvisor = {
|
|
|
13453
13973
|
}
|
|
13454
13974
|
};
|
|
13455
13975
|
|
|
13976
|
+
// src/advisors/cross-repo-share-advisor.ts
|
|
13977
|
+
init_config();
|
|
13978
|
+
import { homedir as homedir9 } from "os";
|
|
13979
|
+
|
|
13980
|
+
// src/memory-repos-registry.ts
|
|
13981
|
+
import {
|
|
13982
|
+
chmodSync as chmodSync5,
|
|
13983
|
+
existsSync as existsSync16,
|
|
13984
|
+
mkdirSync as mkdirSync10,
|
|
13985
|
+
readFileSync as readFileSync12,
|
|
13986
|
+
writeFileSync as writeFileSync8
|
|
13987
|
+
} from "fs";
|
|
13988
|
+
import { homedir as homedir8 } from "os";
|
|
13989
|
+
import { dirname as dirname12, join as join10 } from "path";
|
|
13990
|
+
var REPOS_REGISTRY_VERSION = 1;
|
|
13991
|
+
function reposRegistryPath(home = homedir8()) {
|
|
13992
|
+
return join10(home, ".massu", "repos.json");
|
|
13993
|
+
}
|
|
13994
|
+
function emptyRegistry() {
|
|
13995
|
+
return { version: REPOS_REGISTRY_VERSION, repos: [] };
|
|
13996
|
+
}
|
|
13997
|
+
function readReposRegistry(home = homedir8()) {
|
|
13998
|
+
const p = reposRegistryPath(home);
|
|
13999
|
+
if (!existsSync16(p)) return emptyRegistry();
|
|
14000
|
+
try {
|
|
14001
|
+
const parsed = JSON.parse(readFileSync12(p, "utf-8"));
|
|
14002
|
+
if (!parsed || typeof parsed !== "object") return emptyRegistry();
|
|
14003
|
+
const r = parsed;
|
|
14004
|
+
if (!Array.isArray(r.repos)) return emptyRegistry();
|
|
14005
|
+
const repos = r.repos.filter(
|
|
14006
|
+
(e) => !!e && typeof e === "object" && typeof e.repo_id === "string" && typeof e.label === "string"
|
|
14007
|
+
);
|
|
14008
|
+
return { version: typeof r.version === "number" ? r.version : REPOS_REGISTRY_VERSION, repos };
|
|
14009
|
+
} catch {
|
|
14010
|
+
return emptyRegistry();
|
|
14011
|
+
}
|
|
14012
|
+
}
|
|
14013
|
+
|
|
14014
|
+
// src/advisors/cross-repo-share-advisor.ts
|
|
14015
|
+
var CROSS_REPO_SHARE_ADVISOR_ID = "cross-repo-sharing";
|
|
14016
|
+
var CROSS_REPO_SHARE_REMEDY_KEYS = ["memory.share.enabled", "memory.share.subscribe"];
|
|
14017
|
+
function detectShareableRepos(home) {
|
|
14018
|
+
const repos = readReposRegistry(home).repos.filter((r) => r.share_enabled);
|
|
14019
|
+
if (repos.length === 0) return null;
|
|
14020
|
+
const labels = repos.map((r) => r.label);
|
|
14021
|
+
return {
|
|
14022
|
+
// Fingerprint covers the set of shareable repos, so registering a NEW repo
|
|
14023
|
+
// re-triggers the offer (the operator's setup changed).
|
|
14024
|
+
fingerprint: fingerprintOf(repos.map((r) => r.repo_id)),
|
|
14025
|
+
render: () => renderOffer(labels)
|
|
14026
|
+
};
|
|
14027
|
+
}
|
|
14028
|
+
function renderOffer(repoLabels) {
|
|
14029
|
+
const others = repoLabels.length === 1 ? `\`${repoLabels[0]}\`` : `${repoLabels.length} of your repos`;
|
|
14030
|
+
return [
|
|
14031
|
+
"\u{1F4A1} Massu can surface a decision from one of your repos in another \u2014 off by default, local-only, zero-network.",
|
|
14032
|
+
` ${others} already share memory on this machine. To let THIS repo see their accepted decisions:`,
|
|
14033
|
+
" 1. add the repo label(s) to `memory.share.subscribe` in massu.config.yaml",
|
|
14034
|
+
" 2. run `massu memory review` at your next session \u2014 you accept each decision explicitly.",
|
|
14035
|
+
" Nothing crosses without you accepting it, and nothing is ever an instruction. `massu memory share --help`."
|
|
14036
|
+
].join("\n");
|
|
14037
|
+
}
|
|
14038
|
+
var crossRepoShareAdvisor = {
|
|
14039
|
+
id: CROSS_REPO_SHARE_ADVISOR_ID,
|
|
14040
|
+
remedyKeys: CROSS_REPO_SHARE_REMEDY_KEYS,
|
|
14041
|
+
isConfigured() {
|
|
14042
|
+
const share = getConfig().memory?.share;
|
|
14043
|
+
return Boolean(share?.enabled) || Array.isArray(share?.subscribe) && share.subscribe.length > 0;
|
|
14044
|
+
},
|
|
14045
|
+
async detect() {
|
|
14046
|
+
return detectShareableRepos(homedir9());
|
|
14047
|
+
}
|
|
14048
|
+
};
|
|
14049
|
+
|
|
13456
14050
|
// src/hooks/session-start.ts
|
|
13457
14051
|
init_consolidation_config();
|
|
13458
14052
|
|
|
13459
14053
|
// src/lib/pidLiveness.ts
|
|
13460
|
-
import { spawnSync } from "child_process";
|
|
14054
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
13461
14055
|
function isPidAlive(pid, opts = {}) {
|
|
13462
14056
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
13463
14057
|
const platform = opts.platformOverride ?? process.platform;
|
|
@@ -13482,7 +14076,7 @@ function checkPosix(pid, killOverride) {
|
|
|
13482
14076
|
}
|
|
13483
14077
|
function checkWindows(pid) {
|
|
13484
14078
|
try {
|
|
13485
|
-
const res =
|
|
14079
|
+
const res = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH"], {
|
|
13486
14080
|
encoding: "utf-8",
|
|
13487
14081
|
windowsHide: true
|
|
13488
14082
|
});
|
|
@@ -13556,7 +14150,7 @@ Session memory, code intelligence, and governance are now active.
|
|
|
13556
14150
|
}
|
|
13557
14151
|
try {
|
|
13558
14152
|
const consolidationCfg = resolveConsolidationConfig();
|
|
13559
|
-
const advisorBlock = await runAdvisors([localModelAdvisor], {
|
|
14153
|
+
const advisorBlock = await runAdvisors([localModelAdvisor, crossRepoShareAdvisor], {
|
|
13560
14154
|
enabled: consolidationCfg.enabled && consolidationCfg.suggestUpgrades,
|
|
13561
14155
|
suggestIntervalDays: consolidationCfg.suggestIntervalDays
|
|
13562
14156
|
});
|
|
@@ -13665,9 +14259,8 @@ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
|
|
|
13665
14259
|
}
|
|
13666
14260
|
try {
|
|
13667
14261
|
const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
|
|
13668
|
-
if (
|
|
13669
|
-
const
|
|
13670
|
-
const kdb = new Database3(knowledgeDbPath, { readonly: true });
|
|
14262
|
+
if (existsSync24(knowledgeDbPath)) {
|
|
14263
|
+
const kdb = openDatabase2(knowledgeDbPath, { readonly: true, selfHeal: false });
|
|
13671
14264
|
try {
|
|
13672
14265
|
const stats = kdb.prepare(
|
|
13673
14266
|
"SELECT COUNT(*) as doc_count, MAX(indexed_at) as last_indexed FROM knowledge_documents"
|
|
@@ -13729,8 +14322,8 @@ function estimateTokens(text) {
|
|
|
13729
14322
|
}
|
|
13730
14323
|
async function getGitBranch() {
|
|
13731
14324
|
try {
|
|
13732
|
-
const { spawnSync:
|
|
13733
|
-
const result =
|
|
14325
|
+
const { spawnSync: spawnSync3 } = await import("child_process");
|
|
14326
|
+
const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
13734
14327
|
encoding: "utf-8",
|
|
13735
14328
|
timeout: 5e3
|
|
13736
14329
|
});
|
|
@@ -13756,8 +14349,8 @@ async function buildDriftBanner() {
|
|
|
13756
14349
|
if (process.env.MASSU_DRIFT_QUIET === "1") return "";
|
|
13757
14350
|
if (watcherIsLiveAndFresh()) return "";
|
|
13758
14351
|
const configPath = resolve9(process.cwd(), "massu.config.yaml");
|
|
13759
|
-
if (!
|
|
13760
|
-
const content =
|
|
14352
|
+
if (!existsSync24(configPath)) return "";
|
|
14353
|
+
const content = readFileSync19(configPath, "utf-8");
|
|
13761
14354
|
const parsed = parseYaml4(content);
|
|
13762
14355
|
if (!parsed || typeof parsed !== "object") return "";
|
|
13763
14356
|
const det = parsed.detection;
|
|
@@ -13800,9 +14393,9 @@ function getMemoryDir() {
|
|
|
13800
14393
|
}
|
|
13801
14394
|
function loadCorrectionsPreventionRules() {
|
|
13802
14395
|
try {
|
|
13803
|
-
const correctionsPath =
|
|
13804
|
-
if (!
|
|
13805
|
-
return parseCorrectionRules(
|
|
14396
|
+
const correctionsPath = join18(getResolvedPaths().memoryDir, "corrections.md");
|
|
14397
|
+
if (!existsSync24(correctionsPath)) return [];
|
|
14398
|
+
return parseCorrectionRules(readFileSync19(correctionsPath, "utf-8"));
|
|
13806
14399
|
} catch (_e) {
|
|
13807
14400
|
return [];
|
|
13808
14401
|
}
|
|
@@ -13810,8 +14403,8 @@ function loadCorrectionsPreventionRules() {
|
|
|
13810
14403
|
function readWatchStateRaw(cwd) {
|
|
13811
14404
|
try {
|
|
13812
14405
|
const path = resolve9(cwd, ".massu", "watch-state.json");
|
|
13813
|
-
if (!
|
|
13814
|
-
const obj = JSON.parse(
|
|
14406
|
+
if (!existsSync24(path)) return null;
|
|
14407
|
+
const obj = JSON.parse(readFileSync19(path, "utf-8"));
|
|
13815
14408
|
if (!obj || typeof obj !== "object") return null;
|
|
13816
14409
|
return obj;
|
|
13817
14410
|
} catch {
|