@massu/core 1.16.1 → 1.16.3
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 +95 -0
- package/dist/cli.js +1090 -697
- package/dist/hooks/auto-learning-pipeline.js +401 -55
- package/dist/hooks/classify-failure.js +399 -53
- package/dist/hooks/cost-tracker.js +411 -65
- package/dist/hooks/fix-detector.js +410 -64
- package/dist/hooks/incident-pipeline.js +396 -50
- package/dist/hooks/intent-suggester.js +411 -65
- package/dist/hooks/memory-recall.js +415 -69
- package/dist/hooks/post-edit-context.js +413 -67
- package/dist/hooks/post-tool-use.js +439 -93
- package/dist/hooks/pre-compact.js +411 -65
- package/dist/hooks/pre-delete-check.js +415 -69
- package/dist/hooks/pre-tool-use-gate.js +415 -69
- package/dist/hooks/quality-event.js +411 -65
- package/dist/hooks/rule-enforcement-pipeline.js +397 -51
- package/dist/hooks/security-gate.js +411 -65
- package/dist/hooks/session-end.js +441 -95
- package/dist/hooks/session-start.js +533 -283
- package/dist/hooks/user-prompt.js +445 -99
- package/package.json +1 -1
|
@@ -32,16 +32,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
32
32
|
};
|
|
33
33
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
34
34
|
|
|
35
|
-
// src/lib/memory-path.ts
|
|
36
|
-
function encodeMemoryDirName(projectRoot) {
|
|
37
|
-
return projectRoot.replace(/\//g, "-");
|
|
38
|
-
}
|
|
39
|
-
var init_memory_path = __esm({
|
|
40
|
-
"src/lib/memory-path.ts"() {
|
|
41
|
-
"use strict";
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
35
|
// src/credentials.ts
|
|
46
36
|
import { homedir } from "os";
|
|
47
37
|
import { resolve } from "path";
|
|
@@ -110,6 +100,345 @@ var init_credentials = __esm({
|
|
|
110
100
|
}
|
|
111
101
|
});
|
|
112
102
|
|
|
103
|
+
// src/lib/fileLock.ts
|
|
104
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
105
|
+
import { dirname } from "path";
|
|
106
|
+
import * as lockfile from "proper-lockfile";
|
|
107
|
+
function readLockHolderPid(lockPath) {
|
|
108
|
+
try {
|
|
109
|
+
const raw = readFileSync2(`${lockPath}.pid`, "utf-8").trim();
|
|
110
|
+
const pid = Number.parseInt(raw, 10);
|
|
111
|
+
if (!Number.isFinite(pid) || pid <= 0) return null;
|
|
112
|
+
return pid;
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function busyWaitSync(ms) {
|
|
118
|
+
if (typeof SharedArrayBuffer === "undefined" || typeof Atomics === "undefined") {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`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.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const sab = new SharedArrayBuffer(4);
|
|
124
|
+
const view = new Int32Array(sab);
|
|
125
|
+
Atomics.wait(view, 0, 0, ms);
|
|
126
|
+
}
|
|
127
|
+
function withFileLockSync(lockPath, fn, opts = {}) {
|
|
128
|
+
mkdirSync2(dirname(lockPath), { recursive: true });
|
|
129
|
+
const staleMs = opts.staleMs ?? 3e4;
|
|
130
|
+
const blockMs = opts.retries === 0 ? 0 : opts.blockMs ?? 3e4;
|
|
131
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 100;
|
|
132
|
+
const now = opts.now ?? Date.now;
|
|
133
|
+
const sleep = opts.sleep ?? busyWaitSync;
|
|
134
|
+
const makeBusyError = opts.errorFactory ?? ((path, pid, retrySeconds, code) => new FileLockBusyError(path, pid, retrySeconds, code));
|
|
135
|
+
let release = null;
|
|
136
|
+
const deadline = now() + blockMs;
|
|
137
|
+
for (; ; ) {
|
|
138
|
+
try {
|
|
139
|
+
release = lockfile.lockSync(lockPath, {
|
|
140
|
+
stale: staleMs,
|
|
141
|
+
retries: 0,
|
|
142
|
+
realpath: false
|
|
143
|
+
});
|
|
144
|
+
try {
|
|
145
|
+
writeFileSync2(`${lockPath}.pid`, String(process.pid), "utf-8");
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
break;
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const e = err;
|
|
151
|
+
const code = e.code;
|
|
152
|
+
if (code !== "ELOCKED" && code !== "EBUSY") {
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
if (now() >= deadline) {
|
|
156
|
+
const holderPid = readLockHolderPid(lockPath);
|
|
157
|
+
const remainingMs = Math.max(0, deadline - now());
|
|
158
|
+
const retryAfterSeconds = blockMs === 0 ? Math.round(staleMs / 1e3) : Math.round(remainingMs / 1e3);
|
|
159
|
+
throw makeBusyError(lockPath, holderPid, retryAfterSeconds, code);
|
|
160
|
+
}
|
|
161
|
+
sleep(pollIntervalMs);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
return fn();
|
|
166
|
+
} finally {
|
|
167
|
+
try {
|
|
168
|
+
if (release) release();
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
rmSync2(`${lockPath}.pid`, { force: true });
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
var FileLockBusyError;
|
|
178
|
+
var init_fileLock = __esm({
|
|
179
|
+
"src/lib/fileLock.ts"() {
|
|
180
|
+
"use strict";
|
|
181
|
+
FileLockBusyError = class extends Error {
|
|
182
|
+
constructor(lockPath, holderPid, retryAfterSeconds, causeCode) {
|
|
183
|
+
const pidPart = holderPid != null ? `(PID=${holderPid})` : "(PID=unknown)";
|
|
184
|
+
super(`File lock at ${lockPath} held by another process ${pidPart} \u2014 try again in ${retryAfterSeconds}s`);
|
|
185
|
+
this.lockPath = lockPath;
|
|
186
|
+
this.holderPid = holderPid;
|
|
187
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
188
|
+
this.causeCode = causeCode;
|
|
189
|
+
this.name = "FileLockBusyError";
|
|
190
|
+
}
|
|
191
|
+
lockPath;
|
|
192
|
+
holderPid;
|
|
193
|
+
retryAfterSeconds;
|
|
194
|
+
causeCode;
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// src/lib/sqlite-loader.ts
|
|
200
|
+
import { createRequire } from "module";
|
|
201
|
+
import { spawnSync } from "child_process";
|
|
202
|
+
import { accessSync, appendFileSync, chmodSync as chmodSync2, constants as fsConstants, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
203
|
+
import { dirname as dirname2, join } from "path";
|
|
204
|
+
function isNativeAbiError(err) {
|
|
205
|
+
const code = err?.code;
|
|
206
|
+
if (code === "ERR_DLOPEN_FAILED") return true;
|
|
207
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
208
|
+
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);
|
|
209
|
+
}
|
|
210
|
+
function loadBetterSqlite3(opts = {}) {
|
|
211
|
+
if (_testCtor) return _testCtor;
|
|
212
|
+
if (opts.fresh) bustNativeCache();
|
|
213
|
+
return req("better-sqlite3");
|
|
214
|
+
}
|
|
215
|
+
function bustNativeCache() {
|
|
216
|
+
try {
|
|
217
|
+
const cache = req.cache;
|
|
218
|
+
if (!cache) return;
|
|
219
|
+
for (const key of Object.keys(cache)) {
|
|
220
|
+
if (/better[-_]sqlite3|better_sqlite3\.node|[\\/]bindings[\\/]/.test(key)) {
|
|
221
|
+
delete cache[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function openDatabase(dbPath, opts = {}) {
|
|
228
|
+
const { selfHeal: selfHealOpt = true, ...ctorOpts } = opts;
|
|
229
|
+
const selfHeal = selfHealOpt && process.env.MASSU_HOOK_RUNTIME !== "1";
|
|
230
|
+
const Ctor = loadBetterSqlite3();
|
|
231
|
+
try {
|
|
232
|
+
return new Ctor(dbPath, ctorOpts);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
if (!isNativeAbiError(err)) {
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
if (!selfHeal) {
|
|
238
|
+
recordHealEvent({ phase: "skipped", reason: "self-heal-disabled", abiTo: process.versions.modules });
|
|
239
|
+
throw new MemoryEngineUnusableError("abi-mismatch", detailOf(err));
|
|
240
|
+
}
|
|
241
|
+
const result = (_testHeal ?? attemptNativeHeal)(err);
|
|
242
|
+
if (!result.healed && !result.contended) {
|
|
243
|
+
throw new MemoryEngineUnusableError(result.reason ?? "heal-failed", result.detail ?? detailOf(err));
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const FreshCtor = loadBetterSqlite3({ fresh: true });
|
|
247
|
+
return new FreshCtor(dbPath, ctorOpts);
|
|
248
|
+
} catch (retryErr) {
|
|
249
|
+
throw new MemoryEngineUnusableError("heal-failed", detailOf(retryErr));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function attemptNativeHeal(err) {
|
|
254
|
+
const start = Date.now();
|
|
255
|
+
const abiTo = process.versions.modules;
|
|
256
|
+
const abiFrom = parseAbiFrom(err);
|
|
257
|
+
let pkgDir;
|
|
258
|
+
try {
|
|
259
|
+
pkgDir = dirname2(req.resolve("better-sqlite3/package.json"));
|
|
260
|
+
} catch {
|
|
261
|
+
recordHealEvent({ phase: "skipped", reason: "not-resolvable", abiFrom, abiTo });
|
|
262
|
+
return { healed: false, reason: "missing", abiFrom, abiTo, detail: "better-sqlite3 not resolvable" };
|
|
263
|
+
}
|
|
264
|
+
if (!isWritable(pkgDir)) {
|
|
265
|
+
recordHealEvent({ phase: "skipped", reason: "dir-not-writable", abiFrom, abiTo });
|
|
266
|
+
return { healed: false, reason: "heal-failed", abiFrom, abiTo, detail: `install dir not writable: ${pkgDir}` };
|
|
267
|
+
}
|
|
268
|
+
recordHealEvent({ phase: "attempt", reason: "abi-mismatch", abiFrom, abiTo });
|
|
269
|
+
try {
|
|
270
|
+
return withFileLockSync(
|
|
271
|
+
join(credentialsDir(), "native-heal.lock"),
|
|
272
|
+
() => runRebuild(pkgDir, abiFrom, abiTo, start),
|
|
273
|
+
{ blockMs: 6e4, staleMs: 3e5 }
|
|
274
|
+
);
|
|
275
|
+
} catch (lockErr) {
|
|
276
|
+
const detail = lockErr instanceof Error ? lockErr.message : String(lockErr);
|
|
277
|
+
recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, detail: `lock: ${detail}` });
|
|
278
|
+
return { healed: false, contended: true, reason: "heal-failed", abiFrom, abiTo, detail: `heal lock contended: ${detail}` };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function runRebuild(pkgDir, abiFrom, abiTo, start) {
|
|
282
|
+
try {
|
|
283
|
+
const FreshCtor = loadBetterSqlite3({ fresh: true });
|
|
284
|
+
new FreshCtor(":memory:").close();
|
|
285
|
+
const res2 = {
|
|
286
|
+
healed: true,
|
|
287
|
+
abiFrom,
|
|
288
|
+
abiTo,
|
|
289
|
+
durationMs: Date.now() - start,
|
|
290
|
+
detail: "already healed by a concurrent process"
|
|
291
|
+
};
|
|
292
|
+
recordHealEvent({ phase: "success", reason: "already-healed", abiFrom, abiTo, durationMs: res2.durationMs });
|
|
293
|
+
return res2;
|
|
294
|
+
} catch (probeErr) {
|
|
295
|
+
if (!isNativeAbiError(probeErr)) {
|
|
296
|
+
return { healed: false, reason: "heal-failed", abiFrom, abiTo, durationMs: Date.now() - start, detail: detailOf(probeErr) };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const prebuildBin = resolveLocalBin(pkgDir, "prebuild-install");
|
|
300
|
+
if (prebuildBin) {
|
|
301
|
+
const r = spawnSync(process.execPath, [prebuildBin], {
|
|
302
|
+
cwd: pkgDir,
|
|
303
|
+
encoding: "utf-8",
|
|
304
|
+
timeout: 12e4
|
|
305
|
+
});
|
|
306
|
+
if (r.status === 0) {
|
|
307
|
+
const res2 = { healed: true, method: "prebuild-install", abiFrom, abiTo, durationMs: Date.now() - start };
|
|
308
|
+
recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
|
|
309
|
+
return res2;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const gypBin = resolveNodeGypBin();
|
|
313
|
+
if (gypBin && hasCompiler()) {
|
|
314
|
+
const r = spawnSync(process.execPath, [gypBin, "rebuild", "--release"], {
|
|
315
|
+
cwd: pkgDir,
|
|
316
|
+
encoding: "utf-8",
|
|
317
|
+
timeout: 3e5
|
|
318
|
+
});
|
|
319
|
+
if (r.status === 0) {
|
|
320
|
+
const res2 = { healed: true, method: "node-gyp", abiFrom, abiTo, durationMs: Date.now() - start };
|
|
321
|
+
recordHealEvent({ phase: "success", reason: "abi-mismatch", ...res2 });
|
|
322
|
+
return res2;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const res = {
|
|
326
|
+
healed: false,
|
|
327
|
+
reason: "heal-failed",
|
|
328
|
+
abiFrom,
|
|
329
|
+
abiTo,
|
|
330
|
+
durationMs: Date.now() - start,
|
|
331
|
+
detail: prebuildBin ? "prebuild-install failed; node-gyp fallback unavailable or failed" : "no prebuild-install bin resolvable"
|
|
332
|
+
};
|
|
333
|
+
recordHealEvent({ phase: "failed", reason: "heal-failed", abiFrom, abiTo, durationMs: res.durationMs });
|
|
334
|
+
return res;
|
|
335
|
+
}
|
|
336
|
+
function recordHealEvent(event) {
|
|
337
|
+
try {
|
|
338
|
+
const rec = {
|
|
339
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
340
|
+
node: process.versions.node,
|
|
341
|
+
platform: process.platform,
|
|
342
|
+
arch: process.arch,
|
|
343
|
+
...event
|
|
344
|
+
};
|
|
345
|
+
const dir = credentialsDir();
|
|
346
|
+
mkdirSync3(dir, { recursive: true });
|
|
347
|
+
const file = join(dir, "native-heal-events.jsonl");
|
|
348
|
+
appendFileSync(file, JSON.stringify(rec) + "\n", "utf-8");
|
|
349
|
+
try {
|
|
350
|
+
chmodSync2(file, 384);
|
|
351
|
+
} catch {
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function parseAbiFrom(err) {
|
|
357
|
+
const msg = err instanceof Error ? err.message : String(err ?? "");
|
|
358
|
+
const m = /NODE_MODULE_VERSION (\d+)/.exec(msg);
|
|
359
|
+
if (m && /^\d+$/.test(m[1])) return m[1];
|
|
360
|
+
return void 0;
|
|
361
|
+
}
|
|
362
|
+
function detailOf(err) {
|
|
363
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
364
|
+
return msg.slice(0, 300);
|
|
365
|
+
}
|
|
366
|
+
function isWritable(dir) {
|
|
367
|
+
try {
|
|
368
|
+
accessSync(dir, fsConstants.W_OK);
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function resolveLocalBin(pkgDir, name) {
|
|
375
|
+
const candidates = [
|
|
376
|
+
join(pkgDir, "node_modules", ".bin", name),
|
|
377
|
+
// nested install
|
|
378
|
+
join(pkgDir, "..", ".bin", name),
|
|
379
|
+
// hoisted (node_modules/.bin)
|
|
380
|
+
join(pkgDir, "..", "..", "node_modules", ".bin", name)
|
|
381
|
+
// workspace hoist
|
|
382
|
+
];
|
|
383
|
+
for (const c of candidates) {
|
|
384
|
+
if (existsSync2(c)) return c;
|
|
385
|
+
}
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
function resolveNodeGypBin() {
|
|
389
|
+
try {
|
|
390
|
+
return req.resolve("node-gyp/bin/node-gyp.js");
|
|
391
|
+
} catch {
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function hasCompiler() {
|
|
396
|
+
for (const cc of ["cc", "clang", "gcc"]) {
|
|
397
|
+
try {
|
|
398
|
+
const r = spawnSync(cc, ["--version"], { encoding: "utf-8", timeout: 5e3 });
|
|
399
|
+
if (r.status === 0) return true;
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
var req, NATIVE_DB_REMEDY, MemoryEngineUnusableError, _testCtor, _testHeal;
|
|
406
|
+
var init_sqlite_loader = __esm({
|
|
407
|
+
"src/lib/sqlite-loader.ts"() {
|
|
408
|
+
"use strict";
|
|
409
|
+
init_credentials();
|
|
410
|
+
init_fileLock();
|
|
411
|
+
req = createRequire(import.meta.url);
|
|
412
|
+
NATIVE_DB_REMEDY = "Run 'massu heal' to rebuild the database engine for your Node version, then restart your MCP client / Claude Code.";
|
|
413
|
+
MemoryEngineUnusableError = class extends Error {
|
|
414
|
+
reason;
|
|
415
|
+
remedy;
|
|
416
|
+
detail;
|
|
417
|
+
constructor(reason, detail) {
|
|
418
|
+
super(
|
|
419
|
+
`Massu memory engine is unusable (${reason}). ${NATIVE_DB_REMEDY}` + (detail ? ` [${detail}]` : "")
|
|
420
|
+
);
|
|
421
|
+
this.name = "MemoryEngineUnusableError";
|
|
422
|
+
this.reason = reason;
|
|
423
|
+
this.remedy = NATIVE_DB_REMEDY;
|
|
424
|
+
this.detail = detail;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
_testCtor = null;
|
|
428
|
+
_testHeal = null;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// src/lib/memory-path.ts
|
|
433
|
+
function encodeMemoryDirName(projectRoot) {
|
|
434
|
+
return projectRoot.replace(/\//g, "-");
|
|
435
|
+
}
|
|
436
|
+
var init_memory_path = __esm({
|
|
437
|
+
"src/lib/memory-path.ts"() {
|
|
438
|
+
"use strict";
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
113
442
|
// src/config-memory-schema.ts
|
|
114
443
|
import { z } from "zod";
|
|
115
444
|
var MemoryConfigSchema;
|
|
@@ -249,8 +578,8 @@ var init_config_memory_schema = __esm({
|
|
|
249
578
|
});
|
|
250
579
|
|
|
251
580
|
// src/config.ts
|
|
252
|
-
import { resolve as resolve2, dirname } from "path";
|
|
253
|
-
import { existsSync as
|
|
581
|
+
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
582
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
254
583
|
import { homedir as homedir2 } from "os";
|
|
255
584
|
import { parse as parseYaml } from "yaml";
|
|
256
585
|
import { z as z2 } from "zod";
|
|
@@ -258,22 +587,22 @@ function findProjectRoot() {
|
|
|
258
587
|
const cwd = process.cwd();
|
|
259
588
|
let dir = cwd;
|
|
260
589
|
while (true) {
|
|
261
|
-
if (
|
|
590
|
+
if (existsSync3(resolve2(dir, "massu.config.yaml"))) {
|
|
262
591
|
return dir;
|
|
263
592
|
}
|
|
264
|
-
const parent =
|
|
593
|
+
const parent = dirname3(dir);
|
|
265
594
|
if (parent === dir) break;
|
|
266
595
|
dir = parent;
|
|
267
596
|
}
|
|
268
597
|
dir = cwd;
|
|
269
598
|
while (true) {
|
|
270
|
-
if (
|
|
599
|
+
if (existsSync3(resolve2(dir, "package.json"))) {
|
|
271
600
|
return dir;
|
|
272
601
|
}
|
|
273
|
-
if (
|
|
602
|
+
if (existsSync3(resolve2(dir, ".git"))) {
|
|
274
603
|
return dir;
|
|
275
604
|
}
|
|
276
|
-
const parent =
|
|
605
|
+
const parent = dirname3(dir);
|
|
277
606
|
if (parent === dir) break;
|
|
278
607
|
dir = parent;
|
|
279
608
|
}
|
|
@@ -290,8 +619,8 @@ function getConfig() {
|
|
|
290
619
|
const root = getProjectRoot();
|
|
291
620
|
const configPath = resolve2(root, "massu.config.yaml");
|
|
292
621
|
let rawYaml = {};
|
|
293
|
-
if (
|
|
294
|
-
const content =
|
|
622
|
+
if (existsSync3(configPath)) {
|
|
623
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
295
624
|
rawYaml = parseYaml(content) ?? {};
|
|
296
625
|
}
|
|
297
626
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -372,9 +701,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
372
701
|
const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
|
|
373
702
|
if (resolvedKey.apiKey) {
|
|
374
703
|
_config.cloud = {
|
|
375
|
-
enabled: true,
|
|
376
704
|
sync: { memory: true, analytics: true, audit: true },
|
|
377
705
|
..._config.cloud,
|
|
706
|
+
// Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
|
|
707
|
+
// block only to tune `requestTimeoutMs` does not disable its own sync. An
|
|
708
|
+
// EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
|
|
709
|
+
// "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
|
|
710
|
+
enabled: _config.cloud?.enabled ?? true,
|
|
378
711
|
apiKey: resolvedKey.apiKey,
|
|
379
712
|
endpoint: resolvedEndpoint
|
|
380
713
|
};
|
|
@@ -594,9 +927,22 @@ var init_config = __esm({
|
|
|
594
927
|
})).default([])
|
|
595
928
|
}).optional();
|
|
596
929
|
CloudConfigSchema = z2.object({
|
|
597
|
-
|
|
930
|
+
// OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
|
|
931
|
+
// explicit value once parsed, so `.default(false)` meant that merely declaring a
|
|
932
|
+
// `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
|
|
933
|
+
// then overrode the `enabled: true` auto-enable below via object spread — turning
|
|
934
|
+
// cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
|
|
935
|
+
// and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
|
|
936
|
+
enabled: z2.boolean().optional(),
|
|
598
937
|
apiKey: z2.string().optional(),
|
|
599
938
|
endpoint: z2.string().optional(),
|
|
939
|
+
// Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
|
|
940
|
+
// (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
|
|
941
|
+
// from this schema, and zod strips unknown keys — so the knob was unreachable from
|
|
942
|
+
// massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
|
|
943
|
+
// 423-observation payload takes ~9.2s against the live ingest. Capped at
|
|
944
|
+
// SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
|
|
945
|
+
requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
|
|
600
946
|
sync: z2.object({
|
|
601
947
|
memory: z2.boolean().default(true),
|
|
602
948
|
analytics: z2.boolean().default(true),
|
|
@@ -833,27 +1179,26 @@ var init_memory_vector = __esm({
|
|
|
833
1179
|
});
|
|
834
1180
|
|
|
835
1181
|
// src/db-backup.ts
|
|
836
|
-
import
|
|
837
|
-
import {
|
|
838
|
-
import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
|
|
1182
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
|
|
1183
|
+
import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
|
|
839
1184
|
import { homedir as homedir3 } from "os";
|
|
840
1185
|
function dbBackupsRoot(home = homedir3()) {
|
|
841
1186
|
return resolve3(home, ".massu", "db-backups");
|
|
842
1187
|
}
|
|
843
1188
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
844
1189
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
845
|
-
return
|
|
1190
|
+
return join2(dbBackupsRoot(home), slug);
|
|
846
1191
|
}
|
|
847
1192
|
function backupStamp(nowMs) {
|
|
848
1193
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
849
1194
|
}
|
|
850
1195
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
851
1196
|
const dir = projectBackupDir(projectRoot, home);
|
|
852
|
-
if (!
|
|
1197
|
+
if (!existsSync4(dir)) return [];
|
|
853
1198
|
const out = [];
|
|
854
1199
|
for (const f of readdirSync(dir)) {
|
|
855
1200
|
if (!f.endsWith(".db")) continue;
|
|
856
|
-
const p =
|
|
1201
|
+
const p = join2(dir, f);
|
|
857
1202
|
const st = statSync(p);
|
|
858
1203
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
859
1204
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -866,13 +1211,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
866
1211
|
if (backups.length === 0) return false;
|
|
867
1212
|
const newest = backups[0];
|
|
868
1213
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
869
|
-
if (
|
|
1214
|
+
if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
870
1215
|
return true;
|
|
871
1216
|
}
|
|
872
1217
|
function integrityOk(dbPath) {
|
|
873
1218
|
let db = null;
|
|
874
1219
|
try {
|
|
875
|
-
db =
|
|
1220
|
+
db = openDatabase(dbPath, { readonly: true });
|
|
876
1221
|
const rows = db.pragma("integrity_check");
|
|
877
1222
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
878
1223
|
} catch {
|
|
@@ -885,16 +1230,16 @@ function integrityOk(dbPath) {
|
|
|
885
1230
|
}
|
|
886
1231
|
}
|
|
887
1232
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
888
|
-
if (!
|
|
1233
|
+
if (!existsSync4(dbPath)) {
|
|
889
1234
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
890
1235
|
}
|
|
891
1236
|
const dir = projectBackupDir(projectRoot, home);
|
|
892
|
-
|
|
1237
|
+
mkdirSync4(dir, { recursive: true });
|
|
893
1238
|
const name = basename(dbPath, ".db");
|
|
894
|
-
const dest =
|
|
1239
|
+
const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
895
1240
|
let src = null;
|
|
896
1241
|
try {
|
|
897
|
-
src =
|
|
1242
|
+
src = openDatabase(dbPath, { readonly: true });
|
|
898
1243
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
899
1244
|
} catch (err) {
|
|
900
1245
|
throw new DbBackupError(
|
|
@@ -933,7 +1278,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
933
1278
|
}
|
|
934
1279
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
935
1280
|
try {
|
|
936
|
-
if (!
|
|
1281
|
+
if (!existsSync4(dbPath)) return null;
|
|
937
1282
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
938
1283
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
939
1284
|
} catch (err) {
|
|
@@ -945,6 +1290,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
945
1290
|
var init_db_backup = __esm({
|
|
946
1291
|
"src/db-backup.ts"() {
|
|
947
1292
|
"use strict";
|
|
1293
|
+
init_sqlite_loader();
|
|
948
1294
|
DEFAULT_RETENTION = 5;
|
|
949
1295
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
950
1296
|
DbBackupError = class extends Error {
|
|
@@ -1057,21 +1403,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1057
1403
|
});
|
|
1058
1404
|
|
|
1059
1405
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1060
|
-
import { appendFileSync, mkdirSync as
|
|
1061
|
-
import { join as
|
|
1406
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
|
|
1407
|
+
import { join as join3, dirname as dirname5 } from "path";
|
|
1062
1408
|
function resolveFailureLogPath() {
|
|
1063
1409
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1064
1410
|
if (explicit) return explicit;
|
|
1065
1411
|
let dir = process.cwd();
|
|
1066
1412
|
for (let i = 0; i < 12; i++) {
|
|
1067
|
-
if (
|
|
1068
|
-
return
|
|
1413
|
+
if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
|
|
1414
|
+
return join3(dir, ".massu", "hook-failures.jsonl");
|
|
1069
1415
|
}
|
|
1070
|
-
const parent =
|
|
1416
|
+
const parent = dirname5(dir);
|
|
1071
1417
|
if (parent === dir) break;
|
|
1072
1418
|
dir = parent;
|
|
1073
1419
|
}
|
|
1074
|
-
return
|
|
1420
|
+
return join3(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1075
1421
|
}
|
|
1076
1422
|
function recordHookFailure(hook, error, context) {
|
|
1077
1423
|
const record = {
|
|
@@ -1084,8 +1430,8 @@ function recordHookFailure(hook, error, context) {
|
|
|
1084
1430
|
let wroteSomething = false;
|
|
1085
1431
|
try {
|
|
1086
1432
|
const path = resolveFailureLogPath();
|
|
1087
|
-
|
|
1088
|
-
|
|
1433
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
1434
|
+
appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
|
|
1089
1435
|
wroteSomething = true;
|
|
1090
1436
|
} catch {
|
|
1091
1437
|
}
|
|
@@ -1128,9 +1474,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1128
1474
|
});
|
|
1129
1475
|
|
|
1130
1476
|
// src/memory-embedder-tokenizer.ts
|
|
1131
|
-
import { readFileSync as
|
|
1477
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1132
1478
|
function loadVocab(vocabPath) {
|
|
1133
|
-
const lines =
|
|
1479
|
+
const lines = readFileSync4(vocabPath, "utf-8").split("\n");
|
|
1134
1480
|
const vocab = /* @__PURE__ */ new Map();
|
|
1135
1481
|
for (let i = 0; i < lines.length; i++) {
|
|
1136
1482
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1250,9 +1596,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1250
1596
|
|
|
1251
1597
|
// src/memory-embedder.ts
|
|
1252
1598
|
import { fileURLToPath } from "url";
|
|
1253
|
-
import { dirname as
|
|
1254
|
-
import { existsSync as
|
|
1255
|
-
import { createRequire } from "module";
|
|
1599
|
+
import { dirname as dirname6, join as join4 } from "path";
|
|
1600
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1601
|
+
import { createRequire as createRequire2 } from "module";
|
|
1256
1602
|
function getActiveEmbedModel() {
|
|
1257
1603
|
return _activeModel;
|
|
1258
1604
|
}
|
|
@@ -1273,21 +1619,21 @@ function embeddingsDisabled() {
|
|
|
1273
1619
|
}
|
|
1274
1620
|
function resolveModelDir() {
|
|
1275
1621
|
try {
|
|
1276
|
-
let dir =
|
|
1622
|
+
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1277
1623
|
let root = null;
|
|
1278
1624
|
for (let i = 0; i < 8; i++) {
|
|
1279
|
-
if (
|
|
1625
|
+
if (existsSync6(join4(dir, "package.json"))) {
|
|
1280
1626
|
root = dir;
|
|
1281
1627
|
break;
|
|
1282
1628
|
}
|
|
1283
|
-
const parent =
|
|
1629
|
+
const parent = dirname6(dir);
|
|
1284
1630
|
if (parent === dir) break;
|
|
1285
1631
|
dir = parent;
|
|
1286
1632
|
}
|
|
1287
1633
|
if (!root) return null;
|
|
1288
1634
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1289
|
-
const candidate =
|
|
1290
|
-
if (
|
|
1635
|
+
const candidate = join4(root, rel);
|
|
1636
|
+
if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
|
|
1291
1637
|
}
|
|
1292
1638
|
return null;
|
|
1293
1639
|
} catch {
|
|
@@ -1296,9 +1642,9 @@ function resolveModelDir() {
|
|
|
1296
1642
|
}
|
|
1297
1643
|
function resolveWasmDir() {
|
|
1298
1644
|
try {
|
|
1299
|
-
const
|
|
1300
|
-
const main2 =
|
|
1301
|
-
return
|
|
1645
|
+
const req2 = createRequire2(import.meta.url);
|
|
1646
|
+
const main2 = req2.resolve(ORT_PKG);
|
|
1647
|
+
return dirname6(main2);
|
|
1302
1648
|
} catch {
|
|
1303
1649
|
return null;
|
|
1304
1650
|
}
|
|
@@ -1318,8 +1664,8 @@ async function getSession() {
|
|
|
1318
1664
|
ort.env.wasm.proxy = false;
|
|
1319
1665
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1320
1666
|
_ortTensor = ort.Tensor;
|
|
1321
|
-
const modelPath =
|
|
1322
|
-
const vocabPath =
|
|
1667
|
+
const modelPath = join4(modelDir, "model_quantized.onnx");
|
|
1668
|
+
const vocabPath = join4(modelDir, "vocab.txt");
|
|
1323
1669
|
_vocab = loadVocab(vocabPath);
|
|
1324
1670
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1325
1671
|
executionProviders: ["wasm"],
|
|
@@ -1656,9 +2002,8 @@ __export(memory_db_exports, {
|
|
|
1656
2002
|
upsertObservationEmbedding: () => upsertObservationEmbedding,
|
|
1657
2003
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
1658
2004
|
});
|
|
1659
|
-
import
|
|
1660
|
-
import {
|
|
1661
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2005
|
+
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2006
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
1662
2007
|
function sanitizeFts5Query(raw) {
|
|
1663
2008
|
const trimmed = raw.trim();
|
|
1664
2009
|
if (!trimmed) return '""';
|
|
@@ -1674,12 +2019,12 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
1674
2019
|
}
|
|
1675
2020
|
function getMemoryDb() {
|
|
1676
2021
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
1677
|
-
const dir =
|
|
1678
|
-
if (!
|
|
1679
|
-
|
|
2022
|
+
const dir = dirname7(dbPath);
|
|
2023
|
+
if (!existsSync7(dir)) {
|
|
2024
|
+
mkdirSync6(dir, { recursive: true });
|
|
1680
2025
|
}
|
|
1681
|
-
const preExisting =
|
|
1682
|
-
const db =
|
|
2026
|
+
const preExisting = existsSync7(dbPath);
|
|
2027
|
+
const db = openDatabase(dbPath);
|
|
1683
2028
|
db.pragma("journal_mode = WAL");
|
|
1684
2029
|
db.pragma("foreign_keys = ON");
|
|
1685
2030
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -3385,6 +3730,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
|
3385
3730
|
var init_memory_db = __esm({
|
|
3386
3731
|
"src/memory-db.ts"() {
|
|
3387
3732
|
"use strict";
|
|
3733
|
+
init_sqlite_loader();
|
|
3388
3734
|
init_config();
|
|
3389
3735
|
init_memory_vector();
|
|
3390
3736
|
init_db_backup();
|
|
@@ -3911,8 +4257,8 @@ init_config();
|
|
|
3911
4257
|
|
|
3912
4258
|
// src/import-resolver.ts
|
|
3913
4259
|
init_config();
|
|
3914
|
-
import { readFileSync as
|
|
3915
|
-
import { resolve as resolve6, dirname as
|
|
4260
|
+
import { readFileSync as readFileSync5, existsSync as existsSync8, statSync as statSync2 } from "fs";
|
|
4261
|
+
import { resolve as resolve6, dirname as dirname8, join as join5 } from "path";
|
|
3916
4262
|
|
|
3917
4263
|
// src/security-utils.ts
|
|
3918
4264
|
import { resolve as resolve5, normalize } from "path";
|
|
@@ -3973,21 +4319,21 @@ function resolveImportPath(specifier, fromFile) {
|
|
|
3973
4319
|
const paths = getResolvedPaths();
|
|
3974
4320
|
basePath = resolve6(paths.pathAlias["@"] ?? paths.srcDir, specifier.slice(2));
|
|
3975
4321
|
} else {
|
|
3976
|
-
basePath = resolve6(
|
|
4322
|
+
basePath = resolve6(dirname8(fromFile), specifier);
|
|
3977
4323
|
}
|
|
3978
|
-
if (
|
|
4324
|
+
if (existsSync8(basePath) && !isDirectory(basePath)) {
|
|
3979
4325
|
return toRelative(basePath);
|
|
3980
4326
|
}
|
|
3981
4327
|
const resolvedPaths = getResolvedPaths();
|
|
3982
4328
|
for (const ext of resolvedPaths.extensions) {
|
|
3983
4329
|
const withExt = basePath + ext;
|
|
3984
|
-
if (
|
|
4330
|
+
if (existsSync8(withExt)) {
|
|
3985
4331
|
return toRelative(withExt);
|
|
3986
4332
|
}
|
|
3987
4333
|
}
|
|
3988
4334
|
for (const indexFile of resolvedPaths.indexFiles) {
|
|
3989
|
-
const indexPath =
|
|
3990
|
-
if (
|
|
4335
|
+
const indexPath = join5(basePath, indexFile);
|
|
4336
|
+
if (existsSync8(indexPath)) {
|
|
3991
4337
|
return toRelative(indexPath);
|
|
3992
4338
|
}
|
|
3993
4339
|
}
|
|
@@ -4009,7 +4355,7 @@ function toRelative(absPath) {
|
|
|
4009
4355
|
}
|
|
4010
4356
|
|
|
4011
4357
|
// src/validation-engine.ts
|
|
4012
|
-
import { existsSync as
|
|
4358
|
+
import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
|
|
4013
4359
|
function getValidationChecks() {
|
|
4014
4360
|
return getConfig().governance?.validation?.checks ?? {
|
|
4015
4361
|
rule_compliance: true,
|
|
@@ -4037,7 +4383,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
4037
4383
|
});
|
|
4038
4384
|
return checks;
|
|
4039
4385
|
}
|
|
4040
|
-
if (!
|
|
4386
|
+
if (!existsSync9(absPath)) {
|
|
4041
4387
|
checks.push({
|
|
4042
4388
|
name: "file_exists",
|
|
4043
4389
|
severity: "error",
|
|
@@ -4046,7 +4392,7 @@ function validateFile(filePath, projectRoot) {
|
|
|
4046
4392
|
});
|
|
4047
4393
|
return checks;
|
|
4048
4394
|
}
|
|
4049
|
-
const source =
|
|
4395
|
+
const source = readFileSync6(absPath, "utf-8");
|
|
4050
4396
|
const lines = source.split("\n");
|
|
4051
4397
|
if (activeChecks.rule_compliance !== false) {
|
|
4052
4398
|
for (const ruleSet of config.rules) {
|
|
@@ -4145,7 +4491,7 @@ function storeValidationResult(db, filePath, checks, sessionId, validationType =
|
|
|
4145
4491
|
|
|
4146
4492
|
// src/security-scorer.ts
|
|
4147
4493
|
init_config();
|
|
4148
|
-
import { existsSync as
|
|
4494
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
|
|
4149
4495
|
var DEFAULT_SECURITY_PATTERNS = [
|
|
4150
4496
|
{
|
|
4151
4497
|
regex: /\bexec\s*\(\s*[`"'].*\$\{/,
|
|
@@ -4236,12 +4582,12 @@ function scoreFileSecurity(filePath, projectRoot) {
|
|
|
4236
4582
|
}]
|
|
4237
4583
|
};
|
|
4238
4584
|
}
|
|
4239
|
-
if (!
|
|
4585
|
+
if (!existsSync10(absPath)) {
|
|
4240
4586
|
return { riskScore: 0, findings: [] };
|
|
4241
4587
|
}
|
|
4242
4588
|
let source;
|
|
4243
4589
|
try {
|
|
4244
|
-
source =
|
|
4590
|
+
source = readFileSync7(absPath, "utf-8");
|
|
4245
4591
|
} catch {
|
|
4246
4592
|
return { riskScore: 0, findings: [] };
|
|
4247
4593
|
}
|
|
@@ -4281,8 +4627,8 @@ function storeSecurityScore(db, sessionId, filePath, riskScore, findings) {
|
|
|
4281
4627
|
|
|
4282
4628
|
// src/hooks/post-tool-use.ts
|
|
4283
4629
|
init_config();
|
|
4284
|
-
import { readFileSync as
|
|
4285
|
-
import { join as
|
|
4630
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync12, mkdirSync as mkdirSync7, statSync as statSync3 } from "fs";
|
|
4631
|
+
import { join as join6, basename as pathBasename2, dirname as pathDirname, resolve as pathResolve } from "path";
|
|
4286
4632
|
|
|
4287
4633
|
// src/lib/recurrence-incrementer.ts
|
|
4288
4634
|
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
@@ -4346,7 +4692,7 @@ function incrementRecurrenceCountsForScannerFailures(db, sessionId, scannerStdou
|
|
|
4346
4692
|
init_hook_failure_signal();
|
|
4347
4693
|
|
|
4348
4694
|
// src/memory-file-ingest.ts
|
|
4349
|
-
import { readFileSync as
|
|
4695
|
+
import { readFileSync as readFileSync8, existsSync as existsSync11, readdirSync as readdirSync2 } from "fs";
|
|
4350
4696
|
init_memory_db();
|
|
4351
4697
|
import { parse as parseYaml2 } from "yaml";
|
|
4352
4698
|
import { basename as pathBasename } from "path";
|
|
@@ -4455,8 +4801,8 @@ function upsertMemoryFileMirror(db, f) {
|
|
|
4455
4801
|
return "inserted";
|
|
4456
4802
|
}
|
|
4457
4803
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
4458
|
-
if (!
|
|
4459
|
-
const content =
|
|
4804
|
+
if (!existsSync11(filePath)) return "skipped";
|
|
4805
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
4460
4806
|
if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
|
|
4461
4807
|
process.stderr.write(
|
|
4462
4808
|
`[massu] memory file exceeds ${MAX_MEMORY_FILE_BYTES} bytes, not ingested: ${filePath}
|
|
@@ -4730,12 +5076,12 @@ async function main() {
|
|
|
4730
5076
|
} catch (err) {
|
|
4731
5077
|
try {
|
|
4732
5078
|
const projectRoot = process.cwd();
|
|
4733
|
-
const dir =
|
|
4734
|
-
if (!
|
|
4735
|
-
const logPath =
|
|
4736
|
-
const pre =
|
|
5079
|
+
const dir = join6(projectRoot, ".massu", "rule-candidates");
|
|
5080
|
+
if (!existsSync12(dir)) mkdirSync7(dir, { recursive: true });
|
|
5081
|
+
const logPath = join6(dir, ".cr53-increment-failures.jsonl");
|
|
5082
|
+
const pre = existsSync12(logPath) ? readFileSync9(logPath, "utf-8") : "";
|
|
4737
5083
|
const sep = pre && !pre.endsWith("\n") ? "\n" : "";
|
|
4738
|
-
|
|
5084
|
+
writeFileSync3(logPath, pre + sep + JSON.stringify({
|
|
4739
5085
|
session_id,
|
|
4740
5086
|
scanner_output_excerpt: (tool_response ?? "").slice(0, 200),
|
|
4741
5087
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -4838,13 +5184,13 @@ var _conventionDefaults = {
|
|
|
4838
5184
|
function readConventions(cwd) {
|
|
4839
5185
|
try {
|
|
4840
5186
|
const projectRoot = cwd ?? process.cwd();
|
|
4841
|
-
const configPath =
|
|
4842
|
-
if (!
|
|
5187
|
+
const configPath = join6(projectRoot, "massu.config.yaml");
|
|
5188
|
+
if (!existsSync12(configPath)) return _conventionDefaults;
|
|
4843
5189
|
const mtimeMs = statSync3(configPath).mtimeMs;
|
|
4844
5190
|
if (_cachedConventions !== null && _cachedConventionsPath === configPath && _cachedConventionsMtimeMs === mtimeMs) {
|
|
4845
5191
|
return _cachedConventions;
|
|
4846
5192
|
}
|
|
4847
|
-
const content =
|
|
5193
|
+
const content = readFileSync9(configPath, "utf-8");
|
|
4848
5194
|
const parsed = parseYaml3(content);
|
|
4849
5195
|
if (!parsed || typeof parsed !== "object") return _conventionDefaults;
|
|
4850
5196
|
const conventions = parsed.conventions;
|
|
@@ -4876,11 +5222,11 @@ function isKnowledgeSourceFile(filePath) {
|
|
|
4876
5222
|
function checkMemoryFileIntegrity(filePath) {
|
|
4877
5223
|
const issues = [];
|
|
4878
5224
|
try {
|
|
4879
|
-
if (!
|
|
5225
|
+
if (!existsSync12(filePath)) {
|
|
4880
5226
|
issues.push("MEMORY.md file does not exist after write");
|
|
4881
5227
|
return issues;
|
|
4882
5228
|
}
|
|
4883
|
-
const content =
|
|
5229
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
4884
5230
|
const lines = content.split("\n");
|
|
4885
5231
|
const MAX_LINES = 200;
|
|
4886
5232
|
if (lines.length > MAX_LINES) {
|