@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
|
@@ -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,345 @@ 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/lib/memory-path.ts
|
|
450
|
+
function encodeMemoryDirName(projectRoot) {
|
|
451
|
+
return projectRoot.replace(/\//g, "-");
|
|
452
|
+
}
|
|
453
|
+
var init_memory_path = __esm({
|
|
454
|
+
"src/lib/memory-path.ts"() {
|
|
455
|
+
"use strict";
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
|
|
130
459
|
// src/config-memory-schema.ts
|
|
131
460
|
import { z } from "zod";
|
|
132
461
|
var MemoryConfigSchema;
|
|
@@ -266,8 +595,8 @@ var init_config_memory_schema = __esm({
|
|
|
266
595
|
});
|
|
267
596
|
|
|
268
597
|
// src/config.ts
|
|
269
|
-
import { resolve as resolve2, dirname } from "path";
|
|
270
|
-
import { existsSync as
|
|
598
|
+
import { resolve as resolve2, dirname as dirname3 } from "path";
|
|
599
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
271
600
|
import { homedir as homedir2 } from "os";
|
|
272
601
|
import { parse as parseYaml } from "yaml";
|
|
273
602
|
import { z as z2 } from "zod";
|
|
@@ -275,22 +604,22 @@ function findProjectRoot() {
|
|
|
275
604
|
const cwd = process.cwd();
|
|
276
605
|
let dir = cwd;
|
|
277
606
|
while (true) {
|
|
278
|
-
if (
|
|
607
|
+
if (existsSync3(resolve2(dir, "massu.config.yaml"))) {
|
|
279
608
|
return dir;
|
|
280
609
|
}
|
|
281
|
-
const parent =
|
|
610
|
+
const parent = dirname3(dir);
|
|
282
611
|
if (parent === dir) break;
|
|
283
612
|
dir = parent;
|
|
284
613
|
}
|
|
285
614
|
dir = cwd;
|
|
286
615
|
while (true) {
|
|
287
|
-
if (
|
|
616
|
+
if (existsSync3(resolve2(dir, "package.json"))) {
|
|
288
617
|
return dir;
|
|
289
618
|
}
|
|
290
|
-
if (
|
|
619
|
+
if (existsSync3(resolve2(dir, ".git"))) {
|
|
291
620
|
return dir;
|
|
292
621
|
}
|
|
293
|
-
const parent =
|
|
622
|
+
const parent = dirname3(dir);
|
|
294
623
|
if (parent === dir) break;
|
|
295
624
|
dir = parent;
|
|
296
625
|
}
|
|
@@ -307,8 +636,8 @@ function getConfig() {
|
|
|
307
636
|
const root = getProjectRoot();
|
|
308
637
|
const configPath = resolve2(root, "massu.config.yaml");
|
|
309
638
|
let rawYaml = {};
|
|
310
|
-
if (
|
|
311
|
-
const content =
|
|
639
|
+
if (existsSync3(configPath)) {
|
|
640
|
+
const content = readFileSync3(configPath, "utf-8");
|
|
312
641
|
rawYaml = parseYaml(content) ?? {};
|
|
313
642
|
}
|
|
314
643
|
const result = RawConfigSchema.safeParse(rawYaml);
|
|
@@ -389,9 +718,13 @@ Hint: run \`massu config refresh\` to regenerate a valid config or fix the liste
|
|
|
389
718
|
const resolvedEndpoint = resolveEndpoint({ configEndpoint: _config.cloud?.endpoint });
|
|
390
719
|
if (resolvedKey.apiKey) {
|
|
391
720
|
_config.cloud = {
|
|
392
|
-
enabled: true,
|
|
393
721
|
sync: { memory: true, analytics: true, audit: true },
|
|
394
722
|
..._config.cloud,
|
|
723
|
+
// Spread FIRST, then decide `enabled`, so a workspace that declares a `cloud:`
|
|
724
|
+
// block only to tune `requestTimeoutMs` does not disable its own sync. An
|
|
725
|
+
// EXPLICIT `enabled: false` is still honoured; `undefined` (not stated) means
|
|
726
|
+
// "a key resolved, so turn it on". (plan-2026-07-20-cloud-sync-timeout)
|
|
727
|
+
enabled: _config.cloud?.enabled ?? true,
|
|
395
728
|
apiKey: resolvedKey.apiKey,
|
|
396
729
|
endpoint: resolvedEndpoint
|
|
397
730
|
};
|
|
@@ -611,9 +944,22 @@ var init_config = __esm({
|
|
|
611
944
|
})).default([])
|
|
612
945
|
}).optional();
|
|
613
946
|
CloudConfigSchema = z2.object({
|
|
614
|
-
|
|
947
|
+
// OPTIONAL, not `.default(false)`. A zod default is indistinguishable from an
|
|
948
|
+
// explicit value once parsed, so `.default(false)` meant that merely declaring a
|
|
949
|
+
// `cloud:` block (to set any OTHER key) silently produced `enabled: false`, which
|
|
950
|
+
// then overrode the `enabled: true` auto-enable below via object spread — turning
|
|
951
|
+
// cloud sync OFF as a side effect of tuning it. `undefined` now means "not stated",
|
|
952
|
+
// and the auto-enable preserves it. (plan-2026-07-20-cloud-sync-timeout)
|
|
953
|
+
enabled: z2.boolean().optional(),
|
|
615
954
|
apiKey: z2.string().optional(),
|
|
616
955
|
endpoint: z2.string().optional(),
|
|
956
|
+
// Per-request POST budget for the `/sync` ingest. cloud-sync.ts reads this
|
|
957
|
+
// (`(cloud as { requestTimeoutMs?: number }).requestTimeoutMs`) but it was ABSENT
|
|
958
|
+
// from this schema, and zod strips unknown keys — so the knob was unreachable from
|
|
959
|
+
// massu.config.yaml and the default could never be tuned. Measured 2026-07-20: a
|
|
960
|
+
// 423-observation payload takes ~9.2s against the live ingest. Capped at
|
|
961
|
+
// SYNC_DEADLINE_MS (20s) since the overall deadline clamps each attempt anyway.
|
|
962
|
+
requestTimeoutMs: z2.number().int().positive().max(2e4).optional(),
|
|
617
963
|
sync: z2.object({
|
|
618
964
|
memory: z2.boolean().default(true),
|
|
619
965
|
analytics: z2.boolean().default(true),
|
|
@@ -850,27 +1196,26 @@ var init_memory_vector = __esm({
|
|
|
850
1196
|
});
|
|
851
1197
|
|
|
852
1198
|
// src/db-backup.ts
|
|
853
|
-
import
|
|
854
|
-
import {
|
|
855
|
-
import { resolve as resolve3, join, basename, dirname as dirname2 } from "path";
|
|
1199
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync3 } from "fs";
|
|
1200
|
+
import { resolve as resolve3, join as join2, basename, dirname as dirname4 } from "path";
|
|
856
1201
|
import { homedir as homedir3 } from "os";
|
|
857
1202
|
function dbBackupsRoot(home = homedir3()) {
|
|
858
1203
|
return resolve3(home, ".massu", "db-backups");
|
|
859
1204
|
}
|
|
860
1205
|
function projectBackupDir(projectRoot, home = homedir3()) {
|
|
861
1206
|
const slug = basename(projectRoot).replace(/[^A-Za-z0-9._-]/g, "_") || "project";
|
|
862
|
-
return
|
|
1207
|
+
return join2(dbBackupsRoot(home), slug);
|
|
863
1208
|
}
|
|
864
1209
|
function backupStamp(nowMs) {
|
|
865
1210
|
return new Date(nowMs).toISOString().replace(/[:.]/g, "-");
|
|
866
1211
|
}
|
|
867
1212
|
function listDbBackups(projectRoot, home = homedir3()) {
|
|
868
1213
|
const dir = projectBackupDir(projectRoot, home);
|
|
869
|
-
if (!
|
|
1214
|
+
if (!existsSync4(dir)) return [];
|
|
870
1215
|
const out = [];
|
|
871
1216
|
for (const f of readdirSync(dir)) {
|
|
872
1217
|
if (!f.endsWith(".db")) continue;
|
|
873
|
-
const p =
|
|
1218
|
+
const p = join2(dir, f);
|
|
874
1219
|
const st = statSync(p);
|
|
875
1220
|
const db = f.replace(/-\d{4}-\d{2}-\d{2}T.*$/, "");
|
|
876
1221
|
out.push({ db, path: p, bytes: st.size, mtimeMs: st.mtimeMs });
|
|
@@ -883,13 +1228,13 @@ function hasFreshDbBackup(projectRoot, dbPath, nowMs = Date.now(), home = homedi
|
|
|
883
1228
|
if (backups.length === 0) return false;
|
|
884
1229
|
const newest = backups[0];
|
|
885
1230
|
if (nowMs - newest.mtimeMs > FRESH_WINDOW_MS) return false;
|
|
886
|
-
if (
|
|
1231
|
+
if (existsSync4(dbPath) && statSync(dbPath).mtimeMs > newest.mtimeMs) return false;
|
|
887
1232
|
return true;
|
|
888
1233
|
}
|
|
889
1234
|
function integrityOk(dbPath) {
|
|
890
1235
|
let db = null;
|
|
891
1236
|
try {
|
|
892
|
-
db =
|
|
1237
|
+
db = openDatabase(dbPath, { readonly: true });
|
|
893
1238
|
const rows = db.pragma("integrity_check");
|
|
894
1239
|
return rows.length === 1 && rows[0].integrity_check === "ok";
|
|
895
1240
|
} catch {
|
|
@@ -902,16 +1247,16 @@ function integrityOk(dbPath) {
|
|
|
902
1247
|
}
|
|
903
1248
|
}
|
|
904
1249
|
function backupDb(projectRoot, dbPath, nowMs = Date.now(), home = homedir3()) {
|
|
905
|
-
if (!
|
|
1250
|
+
if (!existsSync4(dbPath)) {
|
|
906
1251
|
throw new DbBackupError(`cannot back up a database that does not exist: ${dbPath}`);
|
|
907
1252
|
}
|
|
908
1253
|
const dir = projectBackupDir(projectRoot, home);
|
|
909
|
-
|
|
1254
|
+
mkdirSync4(dir, { recursive: true });
|
|
910
1255
|
const name = basename(dbPath, ".db");
|
|
911
|
-
const dest =
|
|
1256
|
+
const dest = join2(dir, `${name}-${backupStamp(nowMs)}.db`);
|
|
912
1257
|
let src = null;
|
|
913
1258
|
try {
|
|
914
|
-
src =
|
|
1259
|
+
src = openDatabase(dbPath, { readonly: true });
|
|
915
1260
|
src.exec(`VACUUM INTO '${dest.replace(/'/g, "''")}'`);
|
|
916
1261
|
} catch (err) {
|
|
917
1262
|
throw new DbBackupError(
|
|
@@ -950,7 +1295,7 @@ function pruneDbBackups(projectRoot, dbName, keep = DEFAULT_RETENTION, home = ho
|
|
|
950
1295
|
}
|
|
951
1296
|
function backupBeforeSchemaChange(projectRoot, dbPath, onError, nowMs = Date.now(), home = homedir3()) {
|
|
952
1297
|
try {
|
|
953
|
-
if (!
|
|
1298
|
+
if (!existsSync4(dbPath)) return null;
|
|
954
1299
|
if (hasFreshDbBackup(projectRoot, dbPath, nowMs, home)) return null;
|
|
955
1300
|
return backupDb(projectRoot, dbPath, nowMs, home);
|
|
956
1301
|
} catch (err) {
|
|
@@ -962,6 +1307,7 @@ var DEFAULT_RETENTION, FRESH_WINDOW_MS, DbBackupError;
|
|
|
962
1307
|
var init_db_backup = __esm({
|
|
963
1308
|
"src/db-backup.ts"() {
|
|
964
1309
|
"use strict";
|
|
1310
|
+
init_sqlite_loader();
|
|
965
1311
|
DEFAULT_RETENTION = 5;
|
|
966
1312
|
FRESH_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
967
1313
|
DbBackupError = class extends Error {
|
|
@@ -1074,21 +1420,21 @@ var init_rule_candidate_store = __esm({
|
|
|
1074
1420
|
});
|
|
1075
1421
|
|
|
1076
1422
|
// src/hooks/lib/hook-failure-signal.ts
|
|
1077
|
-
import { appendFileSync, mkdirSync as
|
|
1078
|
-
import { join as
|
|
1423
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
|
|
1424
|
+
import { join as join3, dirname as dirname5 } from "path";
|
|
1079
1425
|
function resolveFailureLogPath() {
|
|
1080
1426
|
const explicit = process.env.MASSU_HOOK_FAILURE_LOG;
|
|
1081
1427
|
if (explicit) return explicit;
|
|
1082
1428
|
let dir = process.cwd();
|
|
1083
1429
|
for (let i = 0; i < 12; i++) {
|
|
1084
|
-
if (
|
|
1085
|
-
return
|
|
1430
|
+
if (existsSync5(join3(dir, ".massu")) || existsSync5(join3(dir, "massu.config.yaml"))) {
|
|
1431
|
+
return join3(dir, ".massu", "hook-failures.jsonl");
|
|
1086
1432
|
}
|
|
1087
|
-
const parent =
|
|
1433
|
+
const parent = dirname5(dir);
|
|
1088
1434
|
if (parent === dir) break;
|
|
1089
1435
|
dir = parent;
|
|
1090
1436
|
}
|
|
1091
|
-
return
|
|
1437
|
+
return join3(process.cwd(), ".massu", "hook-failures.jsonl");
|
|
1092
1438
|
}
|
|
1093
1439
|
function recordHookFailure(hook, error, context) {
|
|
1094
1440
|
const record = {
|
|
@@ -1101,8 +1447,8 @@ function recordHookFailure(hook, error, context) {
|
|
|
1101
1447
|
let wroteSomething = false;
|
|
1102
1448
|
try {
|
|
1103
1449
|
const path = resolveFailureLogPath();
|
|
1104
|
-
|
|
1105
|
-
|
|
1450
|
+
mkdirSync5(dirname5(path), { recursive: true });
|
|
1451
|
+
appendFileSync2(path, JSON.stringify(record) + "\n", "utf-8");
|
|
1106
1452
|
wroteSomething = true;
|
|
1107
1453
|
} catch {
|
|
1108
1454
|
}
|
|
@@ -1145,9 +1491,9 @@ var init_hook_failure_signal = __esm({
|
|
|
1145
1491
|
});
|
|
1146
1492
|
|
|
1147
1493
|
// src/memory-embedder-tokenizer.ts
|
|
1148
|
-
import { readFileSync as
|
|
1494
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1149
1495
|
function loadVocab(vocabPath) {
|
|
1150
|
-
const lines =
|
|
1496
|
+
const lines = readFileSync4(vocabPath, "utf-8").split("\n");
|
|
1151
1497
|
const vocab = /* @__PURE__ */ new Map();
|
|
1152
1498
|
for (let i = 0; i < lines.length; i++) {
|
|
1153
1499
|
const tok = lines[i].replace(/\r$/, "");
|
|
@@ -1267,9 +1613,9 @@ var init_memory_embedder_tokenizer = __esm({
|
|
|
1267
1613
|
|
|
1268
1614
|
// src/memory-embedder.ts
|
|
1269
1615
|
import { fileURLToPath } from "url";
|
|
1270
|
-
import { dirname as
|
|
1271
|
-
import { existsSync as
|
|
1272
|
-
import { createRequire } from "module";
|
|
1616
|
+
import { dirname as dirname6, join as join4 } from "path";
|
|
1617
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1618
|
+
import { createRequire as createRequire2 } from "module";
|
|
1273
1619
|
function getActiveEmbedModel() {
|
|
1274
1620
|
return _activeModel;
|
|
1275
1621
|
}
|
|
@@ -1290,21 +1636,21 @@ function embeddingsDisabled() {
|
|
|
1290
1636
|
}
|
|
1291
1637
|
function resolveModelDir() {
|
|
1292
1638
|
try {
|
|
1293
|
-
let dir =
|
|
1639
|
+
let dir = dirname6(fileURLToPath(import.meta.url));
|
|
1294
1640
|
let root = null;
|
|
1295
1641
|
for (let i = 0; i < 8; i++) {
|
|
1296
|
-
if (
|
|
1642
|
+
if (existsSync6(join4(dir, "package.json"))) {
|
|
1297
1643
|
root = dir;
|
|
1298
1644
|
break;
|
|
1299
1645
|
}
|
|
1300
|
-
const parent =
|
|
1646
|
+
const parent = dirname6(dir);
|
|
1301
1647
|
if (parent === dir) break;
|
|
1302
1648
|
dir = parent;
|
|
1303
1649
|
}
|
|
1304
1650
|
if (!root) return null;
|
|
1305
1651
|
for (const rel of ["dist/embedder", "assets/embedder"]) {
|
|
1306
|
-
const candidate =
|
|
1307
|
-
if (
|
|
1652
|
+
const candidate = join4(root, rel);
|
|
1653
|
+
if (existsSync6(join4(candidate, "model_quantized.onnx"))) return candidate;
|
|
1308
1654
|
}
|
|
1309
1655
|
return null;
|
|
1310
1656
|
} catch {
|
|
@@ -1313,9 +1659,9 @@ function resolveModelDir() {
|
|
|
1313
1659
|
}
|
|
1314
1660
|
function resolveWasmDir() {
|
|
1315
1661
|
try {
|
|
1316
|
-
const
|
|
1317
|
-
const main2 =
|
|
1318
|
-
return
|
|
1662
|
+
const req2 = createRequire2(import.meta.url);
|
|
1663
|
+
const main2 = req2.resolve(ORT_PKG);
|
|
1664
|
+
return dirname6(main2);
|
|
1319
1665
|
} catch {
|
|
1320
1666
|
return null;
|
|
1321
1667
|
}
|
|
@@ -1335,8 +1681,8 @@ async function getSession() {
|
|
|
1335
1681
|
ort.env.wasm.proxy = false;
|
|
1336
1682
|
ort.env.wasm.wasmPaths = wasmDir.endsWith("/") ? wasmDir : wasmDir + "/";
|
|
1337
1683
|
_ortTensor = ort.Tensor;
|
|
1338
|
-
const modelPath =
|
|
1339
|
-
const vocabPath =
|
|
1684
|
+
const modelPath = join4(modelDir, "model_quantized.onnx");
|
|
1685
|
+
const vocabPath = join4(modelDir, "vocab.txt");
|
|
1340
1686
|
_vocab = loadVocab(vocabPath);
|
|
1341
1687
|
const session = await ort.InferenceSession.create(modelPath, {
|
|
1342
1688
|
executionProviders: ["wasm"],
|
|
@@ -1673,9 +2019,8 @@ __export(memory_db_exports, {
|
|
|
1673
2019
|
upsertObservationEmbedding: () => upsertObservationEmbedding,
|
|
1674
2020
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
1675
2021
|
});
|
|
1676
|
-
import
|
|
1677
|
-
import {
|
|
1678
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2022
|
+
import { dirname as dirname7, basename as basename2 } from "path";
|
|
2023
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
|
|
1679
2024
|
function sanitizeFts5Query(raw) {
|
|
1680
2025
|
const trimmed = raw.trim();
|
|
1681
2026
|
if (!trimmed) return '""';
|
|
@@ -1691,12 +2036,12 @@ function sanitizeFts5QueryOr(raw) {
|
|
|
1691
2036
|
}
|
|
1692
2037
|
function getMemoryDb() {
|
|
1693
2038
|
const dbPath = getResolvedPaths().memoryDbPath;
|
|
1694
|
-
const dir =
|
|
1695
|
-
if (!
|
|
1696
|
-
|
|
2039
|
+
const dir = dirname7(dbPath);
|
|
2040
|
+
if (!existsSync7(dir)) {
|
|
2041
|
+
mkdirSync6(dir, { recursive: true });
|
|
1697
2042
|
}
|
|
1698
|
-
const preExisting =
|
|
1699
|
-
const db =
|
|
2043
|
+
const preExisting = existsSync7(dbPath);
|
|
2044
|
+
const db = openDatabase(dbPath);
|
|
1700
2045
|
db.pragma("journal_mode = WAL");
|
|
1701
2046
|
db.pragma("foreign_keys = ON");
|
|
1702
2047
|
const onDisk = db.pragma("user_version", { simple: true });
|
|
@@ -3402,6 +3747,7 @@ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN,
|
|
|
3402
3747
|
var init_memory_db = __esm({
|
|
3403
3748
|
"src/memory-db.ts"() {
|
|
3404
3749
|
"use strict";
|
|
3750
|
+
init_sqlite_loader();
|
|
3405
3751
|
init_config();
|
|
3406
3752
|
init_memory_vector();
|
|
3407
3753
|
init_db_backup();
|
|
@@ -3423,20 +3769,20 @@ var init_memory_db = __esm({
|
|
|
3423
3769
|
|
|
3424
3770
|
// src/lib/safe-write.ts
|
|
3425
3771
|
import {
|
|
3426
|
-
writeFileSync as
|
|
3772
|
+
writeFileSync as writeFileSync3,
|
|
3427
3773
|
renameSync,
|
|
3428
3774
|
openSync,
|
|
3429
3775
|
fsyncSync,
|
|
3430
3776
|
closeSync,
|
|
3431
|
-
existsSync as
|
|
3777
|
+
existsSync as existsSync8,
|
|
3432
3778
|
realpathSync,
|
|
3433
3779
|
unlinkSync as unlinkSync2,
|
|
3434
|
-
accessSync,
|
|
3780
|
+
accessSync as accessSync2,
|
|
3435
3781
|
statSync as statSync2,
|
|
3436
|
-
chmodSync as
|
|
3782
|
+
chmodSync as chmodSync3,
|
|
3437
3783
|
constants
|
|
3438
3784
|
} from "fs";
|
|
3439
|
-
import { dirname as
|
|
3785
|
+
import { dirname as dirname8, resolve as resolve5, relative, isAbsolute, basename as basename3, sep } from "path";
|
|
3440
3786
|
import { createHash as createHash2 } from "crypto";
|
|
3441
3787
|
function assertContainedIn(rootDir, candidate, opts = {}) {
|
|
3442
3788
|
if (candidate.includes("\0")) {
|
|
@@ -3468,8 +3814,8 @@ function isContainedIn(rootDir, candidate, opts = {}) {
|
|
|
3468
3814
|
if (rel === "" && !opts.allowRoot) return false;
|
|
3469
3815
|
if (rel.startsWith("..") || isAbsolute(rel)) return false;
|
|
3470
3816
|
let probe = abs;
|
|
3471
|
-
while (!
|
|
3472
|
-
const parent =
|
|
3817
|
+
while (!existsSync8(probe) && probe.length > 1) {
|
|
3818
|
+
const parent = dirname8(probe);
|
|
3473
3819
|
if (parent === probe) break;
|
|
3474
3820
|
probe = parent;
|
|
3475
3821
|
}
|
|
@@ -3486,17 +3832,17 @@ function isContainedIn(rootDir, candidate, opts = {}) {
|
|
|
3486
3832
|
return !realRel.startsWith("..") && !isAbsolute(realRel);
|
|
3487
3833
|
}
|
|
3488
3834
|
function atomicWriteFileSync(destPath, contents) {
|
|
3489
|
-
const dir =
|
|
3835
|
+
const dir = dirname8(destPath);
|
|
3490
3836
|
const tmp = resolve5(dir, `.${basename3(destPath)}.massu-tmp-${process.pid}`);
|
|
3491
3837
|
let destMode;
|
|
3492
|
-
if (
|
|
3493
|
-
|
|
3838
|
+
if (existsSync8(destPath)) {
|
|
3839
|
+
accessSync2(destPath, constants.W_OK);
|
|
3494
3840
|
destMode = statSync2(destPath).mode & 511;
|
|
3495
3841
|
}
|
|
3496
3842
|
let fd;
|
|
3497
3843
|
try {
|
|
3498
|
-
|
|
3499
|
-
if (destMode !== void 0)
|
|
3844
|
+
writeFileSync3(tmp, contents, "utf-8");
|
|
3845
|
+
if (destMode !== void 0) chmodSync3(tmp, destMode);
|
|
3500
3846
|
fd = openSync(tmp, "r+");
|
|
3501
3847
|
fsyncSync(fd);
|
|
3502
3848
|
closeSync(fd);
|
|
@@ -3510,7 +3856,7 @@ function atomicWriteFileSync(destPath, contents) {
|
|
|
3510
3856
|
}
|
|
3511
3857
|
}
|
|
3512
3858
|
try {
|
|
3513
|
-
if (
|
|
3859
|
+
if (existsSync8(tmp)) unlinkSync2(tmp);
|
|
3514
3860
|
} catch {
|
|
3515
3861
|
}
|
|
3516
3862
|
throw err;
|
|
@@ -3589,7 +3935,7 @@ var init_safe_write = __esm({
|
|
|
3589
3935
|
|
|
3590
3936
|
// src/memory-authorship.ts
|
|
3591
3937
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
3592
|
-
import { readFileSync as
|
|
3938
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync9, mkdirSync as mkdirSync7, chmodSync as chmodSync4, statSync as statSync3 } from "fs";
|
|
3593
3939
|
import { homedir as homedir4 } from "os";
|
|
3594
3940
|
import { resolve as resolve6 } from "path";
|
|
3595
3941
|
function renderKeyPath(home = homedir4()) {
|
|
@@ -3598,8 +3944,8 @@ function renderKeyPath(home = homedir4()) {
|
|
|
3598
3944
|
function readRenderKey(home = homedir4()) {
|
|
3599
3945
|
const p = renderKeyPath(home);
|
|
3600
3946
|
try {
|
|
3601
|
-
if (!
|
|
3602
|
-
const raw =
|
|
3947
|
+
if (!existsSync9(p)) return void 0;
|
|
3948
|
+
const raw = readFileSync5(p);
|
|
3603
3949
|
if (raw.length !== 32) return void 0;
|
|
3604
3950
|
return raw;
|
|
3605
3951
|
} catch {
|
|
@@ -3612,15 +3958,15 @@ function ensureRenderKey(home = homedir4()) {
|
|
|
3612
3958
|
const p = renderKeyPath(home);
|
|
3613
3959
|
try {
|
|
3614
3960
|
const dir = resolve6(home, ".massu");
|
|
3615
|
-
|
|
3961
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
3616
3962
|
try {
|
|
3617
|
-
|
|
3963
|
+
chmodSync4(dir, 448);
|
|
3618
3964
|
} catch {
|
|
3619
3965
|
}
|
|
3620
3966
|
const key = randomBytes(32);
|
|
3621
|
-
|
|
3967
|
+
writeFileSync4(p, key, { mode: 384 });
|
|
3622
3968
|
try {
|
|
3623
|
-
|
|
3969
|
+
chmodSync4(p, 384);
|
|
3624
3970
|
} catch {
|
|
3625
3971
|
}
|
|
3626
3972
|
return key;
|
|
@@ -3665,105 +4011,9 @@ var init_memory_authorship = __esm({
|
|
|
3665
4011
|
}
|
|
3666
4012
|
});
|
|
3667
4013
|
|
|
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
4014
|
// src/memory-index-region.ts
|
|
3765
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3766
|
-
import { dirname as
|
|
4015
|
+
import { readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
|
|
4016
|
+
import { dirname as dirname9, join as join5 } from "path";
|
|
3767
4017
|
function parseRegion(content) {
|
|
3768
4018
|
const begins = countOccurrences(content, BEGIN_SENTINEL);
|
|
3769
4019
|
const ends = countOccurrences(content, END_SENTINEL);
|
|
@@ -3849,7 +4099,7 @@ function assertOutsideRegionUnchanged(pre, post) {
|
|
|
3849
4099
|
}
|
|
3850
4100
|
}
|
|
3851
4101
|
function memoryIndexLockPath(memoryDir) {
|
|
3852
|
-
return
|
|
4102
|
+
return join5(dirname9(memoryDir), ".massu-memory-index.lock");
|
|
3853
4103
|
}
|
|
3854
4104
|
function withMemoryIndexLock(memoryDir, fn) {
|
|
3855
4105
|
return withFileLockSync(memoryIndexLockPath(memoryDir), fn, {
|
|
@@ -3861,7 +4111,7 @@ function withMemoryIndexLock(memoryDir, fn) {
|
|
|
3861
4111
|
});
|
|
3862
4112
|
}
|
|
3863
4113
|
function readMemoryIndex(indexPath) {
|
|
3864
|
-
if (!
|
|
4114
|
+
if (!existsSync10(indexPath)) return void 0;
|
|
3865
4115
|
try {
|
|
3866
4116
|
return readFileSync6(indexPath, "utf8");
|
|
3867
4117
|
} catch {
|
|
@@ -3900,11 +4150,11 @@ var init_memory_index_region = __esm({
|
|
|
3900
4150
|
});
|
|
3901
4151
|
|
|
3902
4152
|
// src/rule-candidate-snapshot.ts
|
|
3903
|
-
import { existsSync as
|
|
4153
|
+
import { existsSync as existsSync11, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
3904
4154
|
function takeSnapshots(paths) {
|
|
3905
4155
|
const out = /* @__PURE__ */ new Map();
|
|
3906
4156
|
for (const p of paths) {
|
|
3907
|
-
if (
|
|
4157
|
+
if (existsSync11(p)) out.set(p, readFileSync7(p, "utf-8"));
|
|
3908
4158
|
else out.set(p, null);
|
|
3909
4159
|
}
|
|
3910
4160
|
return out;
|
|
@@ -3914,7 +4164,7 @@ function restoreSnapshots(snapshot) {
|
|
|
3914
4164
|
for (const [path, content] of snapshot) {
|
|
3915
4165
|
try {
|
|
3916
4166
|
if (content === null) {
|
|
3917
|
-
if (
|
|
4167
|
+
if (existsSync11(path)) unlinkSync3(path);
|
|
3918
4168
|
} else {
|
|
3919
4169
|
writeFileSync5(path, content, "utf-8");
|
|
3920
4170
|
}
|
|
@@ -3931,8 +4181,8 @@ var init_rule_candidate_snapshot = __esm({
|
|
|
3931
4181
|
});
|
|
3932
4182
|
|
|
3933
4183
|
// src/memory-file-ingest.ts
|
|
3934
|
-
import { readFileSync as readFileSync9, existsSync as
|
|
3935
|
-
import { join as
|
|
4184
|
+
import { readFileSync as readFileSync9, existsSync as existsSync13, readdirSync as readdirSync2 } from "fs";
|
|
4185
|
+
import { join as join7 } from "path";
|
|
3936
4186
|
import { parse as parseYaml2 } from "yaml";
|
|
3937
4187
|
import { basename as pathBasename } from "path";
|
|
3938
4188
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -4038,7 +4288,7 @@ function upsertMemoryFileMirror(db, f) {
|
|
|
4038
4288
|
return "inserted";
|
|
4039
4289
|
}
|
|
4040
4290
|
function ingestMemoryFile(db, sessionId, filePath) {
|
|
4041
|
-
if (!
|
|
4291
|
+
if (!existsSync13(filePath)) return "skipped";
|
|
4042
4292
|
const content = readFileSync9(filePath, "utf-8");
|
|
4043
4293
|
if (Buffer.byteLength(content, "utf-8") > MAX_MEMORY_FILE_BYTES) {
|
|
4044
4294
|
process.stderr.write(
|
|
@@ -4109,21 +4359,21 @@ ${body}` : body;
|
|
|
4109
4359
|
}
|
|
4110
4360
|
function backfillMemoryFiles(db, memoryDir, sessionId) {
|
|
4111
4361
|
const stats = { inserted: 0, updated: 0, skipped: 0, total: 0 };
|
|
4112
|
-
if (!
|
|
4362
|
+
if (!existsSync13(memoryDir)) return stats;
|
|
4113
4363
|
const files = readdirSync2(memoryDir).filter(
|
|
4114
4364
|
(f) => f.endsWith(".md") && f !== "MEMORY.md"
|
|
4115
4365
|
);
|
|
4116
4366
|
stats.total = files.length;
|
|
4117
4367
|
const sid = sessionId ?? `backfill-${Date.now()}`;
|
|
4118
4368
|
for (const file of files) {
|
|
4119
|
-
const result = ingestMemoryFile(db, sid,
|
|
4369
|
+
const result = ingestMemoryFile(db, sid, join7(memoryDir, file));
|
|
4120
4370
|
stats[result]++;
|
|
4121
4371
|
}
|
|
4122
4372
|
return stats;
|
|
4123
4373
|
}
|
|
4124
4374
|
function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
|
|
4125
4375
|
try {
|
|
4126
|
-
if (!
|
|
4376
|
+
if (!existsSync13(memoryDir)) return 0;
|
|
4127
4377
|
let entries;
|
|
4128
4378
|
try {
|
|
4129
4379
|
entries = readdirSync2(memoryDir);
|
|
@@ -4135,7 +4385,7 @@ function reconcileMemoryFileObservations(db, memoryDir, sessionId) {
|
|
|
4135
4385
|
for (const file of files) {
|
|
4136
4386
|
liveNames.add(stripMdExtension(file));
|
|
4137
4387
|
try {
|
|
4138
|
-
const content = readFileSync9(
|
|
4388
|
+
const content = readFileSync9(join7(memoryDir, file), "utf-8");
|
|
4139
4389
|
const fm = readMemoryFileFrontmatter(content);
|
|
4140
4390
|
if (fm && typeof fm.name === "string" && fm.name) liveNames.add(fm.name);
|
|
4141
4391
|
} catch {
|
|
@@ -4289,15 +4539,15 @@ var init_memory_render_path = __esm({
|
|
|
4289
4539
|
});
|
|
4290
4540
|
|
|
4291
4541
|
// src/memory-tombstones.ts
|
|
4292
|
-
import { readFileSync as readFileSync11, appendFileSync as
|
|
4293
|
-
import { join as
|
|
4542
|
+
import { readFileSync as readFileSync11, appendFileSync as appendFileSync3, existsSync as existsSync15 } from "fs";
|
|
4543
|
+
import { join as join9 } from "path";
|
|
4294
4544
|
function tombstoneLedgerPath(memoryDir) {
|
|
4295
|
-
return
|
|
4545
|
+
return join9(memoryDir, TOMBSTONE_LEDGER);
|
|
4296
4546
|
}
|
|
4297
4547
|
function readTombstones(memoryDir) {
|
|
4298
4548
|
const out = /* @__PURE__ */ new Map();
|
|
4299
4549
|
const p = tombstoneLedgerPath(memoryDir);
|
|
4300
|
-
if (!
|
|
4550
|
+
if (!existsSync15(p)) return out;
|
|
4301
4551
|
let raw;
|
|
4302
4552
|
try {
|
|
4303
4553
|
raw = readFileSync11(p, "utf8");
|
|
@@ -4368,15 +4618,15 @@ var init_memory_llm = __esm({
|
|
|
4368
4618
|
// src/memory-backup.ts
|
|
4369
4619
|
import {
|
|
4370
4620
|
readdirSync as readdirSync3,
|
|
4371
|
-
mkdirSync as
|
|
4621
|
+
mkdirSync as mkdirSync10,
|
|
4372
4622
|
copyFileSync as copyFileSync2,
|
|
4373
4623
|
statSync as statSync4,
|
|
4374
|
-
existsSync as
|
|
4624
|
+
existsSync as existsSync16,
|
|
4375
4625
|
rmSync as rmSync4,
|
|
4376
4626
|
readFileSync as readFileSync12
|
|
4377
4627
|
} from "fs";
|
|
4378
4628
|
import { homedir as homedir6 } from "os";
|
|
4379
|
-
import { join as
|
|
4629
|
+
import { join as join10, resolve as resolve7 } from "path";
|
|
4380
4630
|
function backupsRoot(home = homedir6()) {
|
|
4381
4631
|
return resolve7(home, ".massu", "memory-backups");
|
|
4382
4632
|
}
|
|
@@ -4384,10 +4634,10 @@ function backupStamp2(nowMs) {
|
|
|
4384
4634
|
return new Date(nowMs).toISOString().replace(/:/g, "-");
|
|
4385
4635
|
}
|
|
4386
4636
|
function newestMtimeMs(dir) {
|
|
4387
|
-
if (!
|
|
4637
|
+
if (!existsSync16(dir)) return 0;
|
|
4388
4638
|
let newest = 0;
|
|
4389
4639
|
for (const entry of readdirSync3(dir)) {
|
|
4390
|
-
const p =
|
|
4640
|
+
const p = join10(dir, entry);
|
|
4391
4641
|
try {
|
|
4392
4642
|
const st = statSync4(p);
|
|
4393
4643
|
if (st.isFile() && st.mtimeMs > newest) newest = st.mtimeMs;
|
|
@@ -4399,10 +4649,10 @@ function newestMtimeMs(dir) {
|
|
|
4399
4649
|
}
|
|
4400
4650
|
function listBackups(home = homedir6()) {
|
|
4401
4651
|
const root = backupsRoot(home);
|
|
4402
|
-
if (!
|
|
4652
|
+
if (!existsSync16(root)) return [];
|
|
4403
4653
|
const out = [];
|
|
4404
4654
|
for (const stamp of readdirSync3(root)) {
|
|
4405
|
-
const dir =
|
|
4655
|
+
const dir = join10(root, stamp);
|
|
4406
4656
|
try {
|
|
4407
4657
|
const st = statSync4(dir);
|
|
4408
4658
|
if (!st.isDirectory()) continue;
|
|
@@ -4420,21 +4670,21 @@ function hasFreshBackup(memoryDir, home = homedir6()) {
|
|
|
4420
4670
|
return backups[0].createdMs >= newestChange;
|
|
4421
4671
|
}
|
|
4422
4672
|
function takeBackup(memoryDir, nowMs, home = homedir6(), retention = DEFAULT_RETENTION2) {
|
|
4423
|
-
if (!
|
|
4673
|
+
if (!existsSync16(memoryDir)) {
|
|
4424
4674
|
throw new BackupError(`memory directory does not exist: ${memoryDir}`);
|
|
4425
4675
|
}
|
|
4426
4676
|
const stamp = backupStamp2(nowMs);
|
|
4427
|
-
const dir =
|
|
4677
|
+
const dir = join10(backupsRoot(home), stamp);
|
|
4428
4678
|
try {
|
|
4429
|
-
|
|
4679
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
4430
4680
|
for (const entry of readdirSync3(memoryDir)) {
|
|
4431
|
-
const srcPath =
|
|
4681
|
+
const srcPath = join10(memoryDir, entry);
|
|
4432
4682
|
try {
|
|
4433
4683
|
if (!statSync4(srcPath).isFile()) continue;
|
|
4434
4684
|
} catch {
|
|
4435
4685
|
continue;
|
|
4436
4686
|
}
|
|
4437
|
-
copyFileSync2(srcPath,
|
|
4687
|
+
copyFileSync2(srcPath, join10(dir, entry));
|
|
4438
4688
|
}
|
|
4439
4689
|
} catch (err) {
|
|
4440
4690
|
throw new BackupError(`backup failed: ${err.message}`);
|
|
@@ -4499,8 +4749,8 @@ __export(memory_renderer_exports, {
|
|
|
4499
4749
|
renderMemoryFiles: () => renderMemoryFiles,
|
|
4500
4750
|
stripFrontmatter: () => stripFrontmatter
|
|
4501
4751
|
});
|
|
4502
|
-
import { existsSync as
|
|
4503
|
-
import { join as
|
|
4752
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
4753
|
+
import { join as join11 } from "path";
|
|
4504
4754
|
import { homedir as homedir7 } from "os";
|
|
4505
4755
|
import { createHash as createHash5 } from "crypto";
|
|
4506
4756
|
function renderMemoryFiles(db, candidates, opts) {
|
|
@@ -4514,7 +4764,7 @@ function renderMemoryFiles(db, candidates, opts) {
|
|
|
4514
4764
|
const audit = opts.audit ?? (() => {
|
|
4515
4765
|
});
|
|
4516
4766
|
const { memoryDir } = opts;
|
|
4517
|
-
if (!
|
|
4767
|
+
if (!existsSync17(memoryDir)) {
|
|
4518
4768
|
return EMPTY("no_memory_dir");
|
|
4519
4769
|
}
|
|
4520
4770
|
try {
|
|
@@ -4582,7 +4832,7 @@ function renderLocked(db, candidates, o) {
|
|
|
4582
4832
|
refusals.push({ name: c.name, reason: "tombstoned" });
|
|
4583
4833
|
continue;
|
|
4584
4834
|
}
|
|
4585
|
-
if (
|
|
4835
|
+
if (existsSync17(absPath)) {
|
|
4586
4836
|
const existing = readFileSync13(absPath, "utf8");
|
|
4587
4837
|
const fm = readMemoryFileFrontmatter(existing);
|
|
4588
4838
|
const storeRow = db.prepare(
|
|
@@ -4632,7 +4882,7 @@ function renderLocked(db, candidates, o) {
|
|
|
4632
4882
|
return EMPTY("backup_failed");
|
|
4633
4883
|
}
|
|
4634
4884
|
}
|
|
4635
|
-
const indexPath = o.indexPath ??
|
|
4885
|
+
const indexPath = o.indexPath ?? join11(memoryDir, "MEMORY.md");
|
|
4636
4886
|
const targets = [...planned.map((p) => p.absPath), indexPath];
|
|
4637
4887
|
const snapshot = takeSnapshots(targets);
|
|
4638
4888
|
let bytesWritten = 0;
|
|
@@ -4996,12 +5246,12 @@ var init_manifest_registry = __esm({
|
|
|
4996
5246
|
});
|
|
4997
5247
|
|
|
4998
5248
|
// src/detect/package-detector.ts
|
|
4999
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
5000
|
-
import { join as
|
|
5249
|
+
import { readFileSync as readFileSync14, existsSync as existsSync18, statSync as statSync5, lstatSync, readdirSync as readdirSync4 } from "fs";
|
|
5250
|
+
import { join as join12, relative as relative2 } from "path";
|
|
5001
5251
|
import { parse as parseToml } from "smol-toml";
|
|
5002
5252
|
function safeRead(path) {
|
|
5003
5253
|
try {
|
|
5004
|
-
if (!
|
|
5254
|
+
if (!existsSync18(path)) return null;
|
|
5005
5255
|
const ls = lstatSync(path);
|
|
5006
5256
|
if (ls.isSymbolicLink()) return null;
|
|
5007
5257
|
const st = statSync5(path);
|
|
@@ -5037,7 +5287,7 @@ function parsePackageJson(path, directory, root, warnings) {
|
|
|
5037
5287
|
const peer = Object.keys(
|
|
5038
5288
|
pkg.peerDependencies ?? {}
|
|
5039
5289
|
);
|
|
5040
|
-
const hasTs = deps.includes("typescript") || devDeps.includes("typescript") ||
|
|
5290
|
+
const hasTs = deps.includes("typescript") || devDeps.includes("typescript") || existsSync18(join12(directory, "tsconfig.json"));
|
|
5041
5291
|
const language = hasTs ? "typescript" : "javascript";
|
|
5042
5292
|
const scripts = Object.keys(
|
|
5043
5293
|
pkg.scripts ?? {}
|
|
@@ -5463,8 +5713,8 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
5463
5713
|
let dirEntries = null;
|
|
5464
5714
|
for (const entry of getManifestRegistry2()) {
|
|
5465
5715
|
if (!entry.pattern.startsWith("*")) {
|
|
5466
|
-
const path =
|
|
5467
|
-
if (!
|
|
5716
|
+
const path = join12(dir, entry.pattern);
|
|
5717
|
+
if (!existsSync18(path)) continue;
|
|
5468
5718
|
const m = entry.parse(path, dir, root, warnings);
|
|
5469
5719
|
if (m !== null) out.push(m);
|
|
5470
5720
|
} else {
|
|
@@ -5477,8 +5727,8 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
5477
5727
|
}
|
|
5478
5728
|
for (const fname of dirEntries) {
|
|
5479
5729
|
if (!matchManifestPattern2(fname, entry.pattern)) continue;
|
|
5480
|
-
const path =
|
|
5481
|
-
if (!
|
|
5730
|
+
const path = join12(dir, fname);
|
|
5731
|
+
if (!existsSync18(path)) continue;
|
|
5482
5732
|
const m = entry.parse(path, dir, root, warnings);
|
|
5483
5733
|
if (m !== null) out.push(m);
|
|
5484
5734
|
}
|
|
@@ -5488,7 +5738,7 @@ function detectManifestsInDir(dir, root, warnings) {
|
|
|
5488
5738
|
}
|
|
5489
5739
|
function listSubdirs(dir) {
|
|
5490
5740
|
try {
|
|
5491
|
-
return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) =>
|
|
5741
|
+
return readdirSync4(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS.has(e.name)).map((e) => join12(dir, e.name));
|
|
5492
5742
|
} catch {
|
|
5493
5743
|
return [];
|
|
5494
5744
|
}
|
|
@@ -5498,8 +5748,8 @@ function detectPackageManifests(projectRoot) {
|
|
|
5498
5748
|
const manifests = [];
|
|
5499
5749
|
manifests.push(...detectManifestsInDir(projectRoot, projectRoot, warnings));
|
|
5500
5750
|
for (const ws of WORKSPACE_DIRS) {
|
|
5501
|
-
const wsRoot =
|
|
5502
|
-
if (!
|
|
5751
|
+
const wsRoot = join12(projectRoot, ws);
|
|
5752
|
+
if (!existsSync18(wsRoot)) continue;
|
|
5503
5753
|
for (const sub of listSubdirs(wsRoot)) {
|
|
5504
5754
|
manifests.push(...detectManifestsInDir(sub, projectRoot, warnings));
|
|
5505
5755
|
for (const sub2 of listSubdirs(sub)) {
|
|
@@ -11678,13 +11928,13 @@ var init_source_dir_detector = __esm({
|
|
|
11678
11928
|
});
|
|
11679
11929
|
|
|
11680
11930
|
// src/detect/monorepo-detector.ts
|
|
11681
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
11682
|
-
import { join as
|
|
11931
|
+
import { readFileSync as readFileSync15, existsSync as existsSync19, statSync as statSync6, lstatSync as lstatSync2, readdirSync as readdirSync5 } from "fs";
|
|
11932
|
+
import { join as join13, relative as relative3 } from "path";
|
|
11683
11933
|
import { parse as parseYaml3 } from "yaml";
|
|
11684
11934
|
import { parse as parseToml2 } from "smol-toml";
|
|
11685
11935
|
function safeReadText(path) {
|
|
11686
11936
|
try {
|
|
11687
|
-
if (!
|
|
11937
|
+
if (!existsSync19(path)) return null;
|
|
11688
11938
|
const ls = lstatSync2(path);
|
|
11689
11939
|
if (ls.isSymbolicLink()) return null;
|
|
11690
11940
|
const st = statSync6(path);
|
|
@@ -11696,20 +11946,20 @@ function safeReadText(path) {
|
|
|
11696
11946
|
}
|
|
11697
11947
|
function firstManifestIn(dir) {
|
|
11698
11948
|
for (const m of MANIFEST_PRIORITY) {
|
|
11699
|
-
if (
|
|
11949
|
+
if (existsSync19(join13(dir, m))) return m;
|
|
11700
11950
|
}
|
|
11701
11951
|
return null;
|
|
11702
11952
|
}
|
|
11703
11953
|
function manifestName(dir, manifest) {
|
|
11704
11954
|
try {
|
|
11705
11955
|
if (manifest === "package.json") {
|
|
11706
|
-
const raw = safeReadText(
|
|
11956
|
+
const raw = safeReadText(join13(dir, "package.json"));
|
|
11707
11957
|
if (!raw) return null;
|
|
11708
11958
|
const pkg = JSON.parse(raw);
|
|
11709
11959
|
return typeof pkg.name === "string" ? pkg.name : null;
|
|
11710
11960
|
}
|
|
11711
11961
|
if (manifest === "pyproject.toml") {
|
|
11712
|
-
const raw = safeReadText(
|
|
11962
|
+
const raw = safeReadText(join13(dir, "pyproject.toml"));
|
|
11713
11963
|
if (!raw) return null;
|
|
11714
11964
|
const toml = parseToml2(raw);
|
|
11715
11965
|
const project = toml.project;
|
|
@@ -11720,7 +11970,7 @@ function manifestName(dir, manifest) {
|
|
|
11720
11970
|
return null;
|
|
11721
11971
|
}
|
|
11722
11972
|
if (manifest === "Cargo.toml") {
|
|
11723
|
-
const raw = safeReadText(
|
|
11973
|
+
const raw = safeReadText(join13(dir, "Cargo.toml"));
|
|
11724
11974
|
if (!raw) return null;
|
|
11725
11975
|
const toml = parseToml2(raw);
|
|
11726
11976
|
const pkg = toml.package;
|
|
@@ -11728,7 +11978,7 @@ function manifestName(dir, manifest) {
|
|
|
11728
11978
|
return null;
|
|
11729
11979
|
}
|
|
11730
11980
|
if (manifest === "go.mod") {
|
|
11731
|
-
const raw = safeReadText(
|
|
11981
|
+
const raw = safeReadText(join13(dir, "go.mod"));
|
|
11732
11982
|
if (!raw) return null;
|
|
11733
11983
|
for (const line of raw.split(/\r?\n/)) {
|
|
11734
11984
|
const trimmed = line.trim();
|
|
@@ -11752,7 +12002,7 @@ function pkgFromDir(root, dir) {
|
|
|
11752
12002
|
}
|
|
11753
12003
|
function listSubdirs2(dir) {
|
|
11754
12004
|
try {
|
|
11755
|
-
return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) =>
|
|
12005
|
+
return readdirSync5(dir, { withFileTypes: true }).filter((e) => e.isDirectory() && !IGNORED_DIRS2.has(e.name)).map((e) => join13(dir, e.name));
|
|
11756
12006
|
} catch {
|
|
11757
12007
|
return [];
|
|
11758
12008
|
}
|
|
@@ -11760,8 +12010,8 @@ function listSubdirs2(dir) {
|
|
|
11760
12010
|
function genericWorkspaces(root) {
|
|
11761
12011
|
const out = [];
|
|
11762
12012
|
for (const parent of CONVENTIONAL_WORKSPACE_PARENTS) {
|
|
11763
|
-
const p =
|
|
11764
|
-
if (!
|
|
12013
|
+
const p = join13(root, parent);
|
|
12014
|
+
if (!existsSync19(p)) continue;
|
|
11765
12015
|
for (const sub of listSubdirs2(p)) {
|
|
11766
12016
|
const pkg = pkgFromDir(root, sub);
|
|
11767
12017
|
if (pkg) out.push(pkg);
|
|
@@ -11770,7 +12020,7 @@ function genericWorkspaces(root) {
|
|
|
11770
12020
|
return out;
|
|
11771
12021
|
}
|
|
11772
12022
|
function detectYarnWorkspaces(root) {
|
|
11773
|
-
const raw = safeReadText(
|
|
12023
|
+
const raw = safeReadText(join13(root, "package.json"));
|
|
11774
12024
|
if (!raw) return null;
|
|
11775
12025
|
let pkg;
|
|
11776
12026
|
try {
|
|
@@ -11785,7 +12035,7 @@ function detectYarnWorkspaces(root) {
|
|
|
11785
12035
|
return expandWorkspaceGlobs(root, globs);
|
|
11786
12036
|
}
|
|
11787
12037
|
function detectPnpmWorkspaces(root) {
|
|
11788
|
-
const raw = safeReadText(
|
|
12038
|
+
const raw = safeReadText(join13(root, "pnpm-workspace.yaml"));
|
|
11789
12039
|
if (!raw) return null;
|
|
11790
12040
|
try {
|
|
11791
12041
|
const parsed = parseYaml3(raw);
|
|
@@ -11801,8 +12051,8 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
11801
12051
|
for (const pattern of globs) {
|
|
11802
12052
|
const parts = pattern.split("/");
|
|
11803
12053
|
if (parts.length === 2 && (parts[1] === "*" || parts[1] === "**")) {
|
|
11804
|
-
const parent =
|
|
11805
|
-
if (!
|
|
12054
|
+
const parent = join13(root, parts[0]);
|
|
12055
|
+
if (!existsSync19(parent)) continue;
|
|
11806
12056
|
for (const sub of listSubdirs2(parent)) {
|
|
11807
12057
|
const pkg = pkgFromDir(root, sub);
|
|
11808
12058
|
if (pkg && !seen.has(pkg.path)) {
|
|
@@ -11812,8 +12062,8 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
11812
12062
|
}
|
|
11813
12063
|
continue;
|
|
11814
12064
|
}
|
|
11815
|
-
const direct =
|
|
11816
|
-
if (
|
|
12065
|
+
const direct = join13(root, pattern);
|
|
12066
|
+
if (existsSync19(direct)) {
|
|
11817
12067
|
const pkg = pkgFromDir(root, direct);
|
|
11818
12068
|
if (pkg && !seen.has(pkg.path)) {
|
|
11819
12069
|
seen.add(pkg.path);
|
|
@@ -11824,16 +12074,16 @@ function expandWorkspaceGlobs(root, globs) {
|
|
|
11824
12074
|
return out;
|
|
11825
12075
|
}
|
|
11826
12076
|
function hasTurbo(root) {
|
|
11827
|
-
return
|
|
12077
|
+
return existsSync19(join13(root, "turbo.json"));
|
|
11828
12078
|
}
|
|
11829
12079
|
function hasNx(root) {
|
|
11830
|
-
return
|
|
12080
|
+
return existsSync19(join13(root, "nx.json"));
|
|
11831
12081
|
}
|
|
11832
12082
|
function hasLerna(root) {
|
|
11833
|
-
return
|
|
12083
|
+
return existsSync19(join13(root, "lerna.json"));
|
|
11834
12084
|
}
|
|
11835
12085
|
function hasBazel(root) {
|
|
11836
|
-
return
|
|
12086
|
+
return existsSync19(join13(root, "WORKSPACE")) || existsSync19(join13(root, "WORKSPACE.bazel")) || existsSync19(join13(root, "MODULE.bazel"));
|
|
11837
12087
|
}
|
|
11838
12088
|
function detectMonorepo(projectRoot) {
|
|
11839
12089
|
const nested = [];
|
|
@@ -12024,8 +12274,8 @@ var init_vr_command_map = __esm({
|
|
|
12024
12274
|
});
|
|
12025
12275
|
|
|
12026
12276
|
// src/detect/domain-inferrer.ts
|
|
12027
|
-
import { existsSync as
|
|
12028
|
-
import { join as
|
|
12277
|
+
import { existsSync as existsSync20, readdirSync as readdirSync6 } from "fs";
|
|
12278
|
+
import { join as join14 } from "path";
|
|
12029
12279
|
function titleCase(s) {
|
|
12030
12280
|
if (!s) return s;
|
|
12031
12281
|
return s.split(/[-_\s]+/).filter(Boolean).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
|
|
@@ -12045,8 +12295,8 @@ function topLevelSrcSubdirs(root, sourceDirs) {
|
|
|
12045
12295
|
const effective = sourceDirs.length > 0 ? sourceDirs : ["src"];
|
|
12046
12296
|
const seen = /* @__PURE__ */ new Set();
|
|
12047
12297
|
for (const rel of effective) {
|
|
12048
|
-
const abs =
|
|
12049
|
-
if (!
|
|
12298
|
+
const abs = join14(root, rel);
|
|
12299
|
+
if (!existsSync20(abs)) continue;
|
|
12050
12300
|
try {
|
|
12051
12301
|
for (const e of readdirSync6(abs, { withFileTypes: true })) {
|
|
12052
12302
|
if (!e.isDirectory()) continue;
|
|
@@ -12133,8 +12383,8 @@ var init_domain_inferrer = __esm({
|
|
|
12133
12383
|
});
|
|
12134
12384
|
|
|
12135
12385
|
// src/detect/regex-fallback.ts
|
|
12136
|
-
import { existsSync as
|
|
12137
|
-
import { resolve as resolve8, join as
|
|
12386
|
+
import { existsSync as existsSync21, readdirSync as readdirSync7, readFileSync as readFileSync16, statSync as statSync7 } from "fs";
|
|
12387
|
+
import { resolve as resolve8, join as join15, basename as basename4 } from "path";
|
|
12138
12388
|
function introspectPython(detection, projectRoot) {
|
|
12139
12389
|
const sourceDir = resolveSourceDir(detection, "python", projectRoot);
|
|
12140
12390
|
if (!sourceDir) return null;
|
|
@@ -12301,9 +12551,9 @@ function resolveSourceDir(detection, lang, projectRoot) {
|
|
|
12301
12551
|
if (list.length > 0) {
|
|
12302
12552
|
const first = list[0];
|
|
12303
12553
|
const abs = resolve8(projectRoot, first);
|
|
12304
|
-
return
|
|
12554
|
+
return existsSync21(abs) ? abs : null;
|
|
12305
12555
|
}
|
|
12306
|
-
return
|
|
12556
|
+
return existsSync21(projectRoot) ? projectRoot : null;
|
|
12307
12557
|
}
|
|
12308
12558
|
function sampleFiles(dir, nameRegex, pathFilter) {
|
|
12309
12559
|
const out = [];
|
|
@@ -12323,7 +12573,7 @@ function sampleFiles(dir, nameRegex, pathFilter) {
|
|
|
12323
12573
|
if (entry === "__pycache__") continue;
|
|
12324
12574
|
if (entry === "venv" || entry === ".venv") continue;
|
|
12325
12575
|
if (entry === "dist" || entry === "build") continue;
|
|
12326
|
-
const child =
|
|
12576
|
+
const child = join15(path, entry);
|
|
12327
12577
|
let st;
|
|
12328
12578
|
try {
|
|
12329
12579
|
st = statSync7(child);
|
|
@@ -12727,8 +12977,8 @@ init_memory_db();
|
|
|
12727
12977
|
|
|
12728
12978
|
// src/team-rule-sync.ts
|
|
12729
12979
|
init_config();
|
|
12730
|
-
import { existsSync as
|
|
12731
|
-
import { join as
|
|
12980
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync8 } from "fs";
|
|
12981
|
+
import { join as join6, dirname as dirname10 } from "path";
|
|
12732
12982
|
|
|
12733
12983
|
// src/license.ts
|
|
12734
12984
|
init_config();
|
|
@@ -12819,7 +13069,7 @@ function verifyLicenseResponse(payload) {
|
|
|
12819
13069
|
);
|
|
12820
13070
|
}
|
|
12821
13071
|
function isLicenseSignatureRequired() {
|
|
12822
|
-
return process.env.MASSU_REQUIRE_SIGNED_LICENSE
|
|
13072
|
+
return process.env.MASSU_REQUIRE_SIGNED_LICENSE !== "false";
|
|
12823
13073
|
}
|
|
12824
13074
|
|
|
12825
13075
|
// src/license.ts
|
|
@@ -13125,7 +13375,7 @@ async function pullTeamPromotions(db, opts = {}) {
|
|
|
13125
13375
|
result.revoked_handled += 1;
|
|
13126
13376
|
continue;
|
|
13127
13377
|
}
|
|
13128
|
-
if (
|
|
13378
|
+
if (existsSync12(candidatePath) || alreadyApplied(db, p.prompt_hash)) {
|
|
13129
13379
|
result.skipped += 1;
|
|
13130
13380
|
continue;
|
|
13131
13381
|
}
|
|
@@ -13161,7 +13411,7 @@ function isValidWirePromotion(p) {
|
|
|
13161
13411
|
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
13412
|
}
|
|
13163
13413
|
function sidecarPath(projectRoot, promptHash) {
|
|
13164
|
-
return
|
|
13414
|
+
return join6(projectRoot, ".massu", "rule-candidates", `${promptHash}.json`);
|
|
13165
13415
|
}
|
|
13166
13416
|
function alreadyApplied(db, promptHash) {
|
|
13167
13417
|
try {
|
|
@@ -13175,7 +13425,7 @@ function alreadyApplied(db, promptHash) {
|
|
|
13175
13425
|
}
|
|
13176
13426
|
}
|
|
13177
13427
|
function handleRevocation(db, projectRoot, candidatePath, promptHash) {
|
|
13178
|
-
if (
|
|
13428
|
+
if (existsSync12(candidatePath)) {
|
|
13179
13429
|
try {
|
|
13180
13430
|
unlinkSync4(candidatePath);
|
|
13181
13431
|
} catch {
|
|
@@ -13223,8 +13473,8 @@ function materializeCandidate(db, projectRoot, candidatePath, p, orgId) {
|
|
|
13223
13473
|
draft_text: p.draft_text,
|
|
13224
13474
|
...p.review_attestation !== void 0 ? { publisher_review_attestation: p.review_attestation } : {}
|
|
13225
13475
|
};
|
|
13226
|
-
const dir =
|
|
13227
|
-
if (!
|
|
13476
|
+
const dir = dirname10(candidatePath);
|
|
13477
|
+
if (!existsSync12(dir)) mkdirSync8(dir, { recursive: true });
|
|
13228
13478
|
writeFileSync6(candidatePath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
13229
13479
|
try {
|
|
13230
13480
|
shareObservation(db, p.promoted_by, getProjectName(), "rule_promotion", promptText, {
|
|
@@ -13279,22 +13529,23 @@ function failSync(db, reason) {
|
|
|
13279
13529
|
// src/hooks/session-start.ts
|
|
13280
13530
|
init_memory_file_ingest();
|
|
13281
13531
|
init_config();
|
|
13282
|
-
|
|
13283
|
-
import {
|
|
13532
|
+
init_sqlite_loader();
|
|
13533
|
+
import { readFileSync as readFileSync17, existsSync as existsSync22 } from "fs";
|
|
13534
|
+
import { join as join16, resolve as resolve9 } from "path";
|
|
13284
13535
|
import { parse as parseYaml4 } from "yaml";
|
|
13285
13536
|
|
|
13286
13537
|
// src/capability-advisor.ts
|
|
13287
13538
|
import { createHash as createHash4 } from "crypto";
|
|
13288
|
-
import { existsSync as
|
|
13539
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
|
|
13289
13540
|
import { homedir as homedir5 } from "os";
|
|
13290
|
-
import { dirname as
|
|
13541
|
+
import { dirname as dirname11, join as join8 } from "path";
|
|
13291
13542
|
function advisorStatePath(home = homedir5()) {
|
|
13292
|
-
return
|
|
13543
|
+
return join8(home, ".massu", "advisor-state.json");
|
|
13293
13544
|
}
|
|
13294
13545
|
function readAdvisorState(home) {
|
|
13295
13546
|
try {
|
|
13296
13547
|
const p = advisorStatePath(home);
|
|
13297
|
-
if (!
|
|
13548
|
+
if (!existsSync14(p)) return {};
|
|
13298
13549
|
return JSON.parse(readFileSync10(p, "utf-8"));
|
|
13299
13550
|
} catch {
|
|
13300
13551
|
return {};
|
|
@@ -13303,7 +13554,7 @@ function readAdvisorState(home) {
|
|
|
13303
13554
|
function writeAdvisorState(state, home) {
|
|
13304
13555
|
try {
|
|
13305
13556
|
const p = advisorStatePath(home);
|
|
13306
|
-
|
|
13557
|
+
mkdirSync9(dirname11(p), { recursive: true });
|
|
13307
13558
|
writeFileSync7(p, JSON.stringify(state, null, 2), { mode: 384 });
|
|
13308
13559
|
} catch {
|
|
13309
13560
|
}
|
|
@@ -13457,7 +13708,7 @@ var localModelAdvisor = {
|
|
|
13457
13708
|
init_consolidation_config();
|
|
13458
13709
|
|
|
13459
13710
|
// src/lib/pidLiveness.ts
|
|
13460
|
-
import { spawnSync } from "child_process";
|
|
13711
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
13461
13712
|
function isPidAlive(pid, opts = {}) {
|
|
13462
13713
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
13463
13714
|
const platform = opts.platformOverride ?? process.platform;
|
|
@@ -13482,7 +13733,7 @@ function checkPosix(pid, killOverride) {
|
|
|
13482
13733
|
}
|
|
13483
13734
|
function checkWindows(pid) {
|
|
13484
13735
|
try {
|
|
13485
|
-
const res =
|
|
13736
|
+
const res = spawnSync2("tasklist", ["/FI", `PID eq ${pid}`, "/NH"], {
|
|
13486
13737
|
encoding: "utf-8",
|
|
13487
13738
|
windowsHide: true
|
|
13488
13739
|
});
|
|
@@ -13665,9 +13916,8 @@ async function buildContext(db, sessionId, source, tokenBudget, taskId) {
|
|
|
13665
13916
|
}
|
|
13666
13917
|
try {
|
|
13667
13918
|
const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
|
|
13668
|
-
if (
|
|
13669
|
-
const
|
|
13670
|
-
const kdb = new Database3(knowledgeDbPath, { readonly: true });
|
|
13919
|
+
if (existsSync22(knowledgeDbPath)) {
|
|
13920
|
+
const kdb = openDatabase(knowledgeDbPath, { readonly: true, selfHeal: false });
|
|
13671
13921
|
try {
|
|
13672
13922
|
const stats = kdb.prepare(
|
|
13673
13923
|
"SELECT COUNT(*) as doc_count, MAX(indexed_at) as last_indexed FROM knowledge_documents"
|
|
@@ -13729,8 +13979,8 @@ function estimateTokens(text) {
|
|
|
13729
13979
|
}
|
|
13730
13980
|
async function getGitBranch() {
|
|
13731
13981
|
try {
|
|
13732
|
-
const { spawnSync:
|
|
13733
|
-
const result =
|
|
13982
|
+
const { spawnSync: spawnSync3 } = await import("child_process");
|
|
13983
|
+
const result = spawnSync3("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
13734
13984
|
encoding: "utf-8",
|
|
13735
13985
|
timeout: 5e3
|
|
13736
13986
|
});
|
|
@@ -13756,7 +14006,7 @@ async function buildDriftBanner() {
|
|
|
13756
14006
|
if (process.env.MASSU_DRIFT_QUIET === "1") return "";
|
|
13757
14007
|
if (watcherIsLiveAndFresh()) return "";
|
|
13758
14008
|
const configPath = resolve9(process.cwd(), "massu.config.yaml");
|
|
13759
|
-
if (!
|
|
14009
|
+
if (!existsSync22(configPath)) return "";
|
|
13760
14010
|
const content = readFileSync17(configPath, "utf-8");
|
|
13761
14011
|
const parsed = parseYaml4(content);
|
|
13762
14012
|
if (!parsed || typeof parsed !== "object") return "";
|
|
@@ -13800,8 +14050,8 @@ function getMemoryDir() {
|
|
|
13800
14050
|
}
|
|
13801
14051
|
function loadCorrectionsPreventionRules() {
|
|
13802
14052
|
try {
|
|
13803
|
-
const correctionsPath =
|
|
13804
|
-
if (!
|
|
14053
|
+
const correctionsPath = join16(getResolvedPaths().memoryDir, "corrections.md");
|
|
14054
|
+
if (!existsSync22(correctionsPath)) return [];
|
|
13805
14055
|
return parseCorrectionRules(readFileSync17(correctionsPath, "utf-8"));
|
|
13806
14056
|
} catch (_e) {
|
|
13807
14057
|
return [];
|
|
@@ -13810,7 +14060,7 @@ function loadCorrectionsPreventionRules() {
|
|
|
13810
14060
|
function readWatchStateRaw(cwd) {
|
|
13811
14061
|
try {
|
|
13812
14062
|
const path = resolve9(cwd, ".massu", "watch-state.json");
|
|
13813
|
-
if (!
|
|
14063
|
+
if (!existsSync22(path)) return null;
|
|
13814
14064
|
const obj = JSON.parse(readFileSync17(path, "utf-8"));
|
|
13815
14065
|
if (!obj || typeof obj !== "object") return null;
|
|
13816
14066
|
return obj;
|