@massu/core 2.5.5 → 2.5.7

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.
@@ -2205,6 +2205,8 @@ __export(memory_db_exports, {
2205
2205
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2206
2206
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2207
2207
  MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
2208
+ PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
2209
+ PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
2208
2210
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2209
2211
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2210
2212
  addConversationTurn: () => addConversationTurn,
@@ -2213,6 +2215,7 @@ __export(memory_db_exports, {
2213
2215
  addSummary: () => addSummary,
2214
2216
  addToolCallDetail: () => addToolCallDetail,
2215
2217
  addUserPrompt: () => addUserPrompt,
2218
+ allPendingSync: () => allPendingSync,
2216
2219
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2217
2220
  armUsageCounter: () => armUsageCounter,
2218
2221
  assignImportance: () => assignImportance,
@@ -2220,6 +2223,7 @@ __export(memory_db_exports, {
2220
2223
  createSession: () => createSession,
2221
2224
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2222
2225
  dequeuePendingSync: () => dequeuePendingSync,
2226
+ discardPendingSync: () => discardPendingSync,
2223
2227
  embedMissingObservations: () => embedMissingObservations,
2224
2228
  endSession: () => endSession,
2225
2229
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3202,6 +3206,35 @@ function initMemorySchema(db) {
3202
3206
  }
3203
3207
  function enqueueSyncPayload(db, payload) {
3204
3208
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
3209
+ const overflow = db.prepare(
3210
+ `SELECT id, retry_count, last_error, payload FROM pending_sync
3211
+ WHERE created_at < datetime('now', ?)
3212
+ OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
3213
+ LIMIT 10000`
3214
+ ).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
3215
+ if (overflow.length > 0) {
3216
+ discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
3217
+ }
3218
+ }
3219
+ function discardPendingSync(db, rows, why) {
3220
+ if (rows.length === 0) return 0;
3221
+ const rescued = rescueLearningFromStalePayloads(db, rows);
3222
+ for (let i = 0; i < rows.length; i += 500) {
3223
+ const ids = rows.slice(i, i + 500).map((r) => r.id);
3224
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
3225
+ }
3226
+ if (rescued > 0) {
3227
+ process.stderr.write(
3228
+ `[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
3229
+ `
3230
+ );
3231
+ }
3232
+ process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
3233
+ `);
3234
+ return rows.length;
3235
+ }
3236
+ function allPendingSync(db) {
3237
+ return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
3205
3238
  }
3206
3239
  function getMemoryMeta(db, key) {
3207
3240
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3404,7 +3437,9 @@ function dequeuePendingSync(db, limit = 10) {
3404
3437
  }
3405
3438
  }
3406
3439
  return db.prepare(
3407
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
3440
+ // Least-retried first: oldest-first retried the same 10 items until they were discarded
3441
+ // while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
3442
+ "SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
3408
3443
  ).all(limit);
3409
3444
  }
3410
3445
  function removePendingSync(db, id) {
@@ -4077,7 +4112,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
4077
4112
  }
4078
4113
  return bestMatch;
4079
4114
  }
4080
- var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
4115
+ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, PENDING_SYNC_MAX_ITEMS, PENDING_SYNC_MAX_AGE_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
4081
4116
  var init_memory_db = __esm({
4082
4117
  "src/memory-db.ts"() {
4083
4118
  "use strict";
@@ -4092,6 +4127,8 @@ var init_memory_db = __esm({
4092
4127
  init_memory_embed_sweep();
4093
4128
  MEMORY_SCHEMA_VERSION = 2;
4094
4129
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4130
+ PENDING_SYNC_MAX_ITEMS = 500;
4131
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
4095
4132
  MAX_DRAFT_TEXT_LEN = 16384;
4096
4133
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
4097
4134
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4219,6 +4256,13 @@ function assertContainedIn(rootDir, candidate, opts = {}) {
4219
4256
  }
4220
4257
  return abs;
4221
4258
  }
4259
+ function isSameRealPath(a, b) {
4260
+ try {
4261
+ return realpathSync(a) === realpathSync(b);
4262
+ } catch {
4263
+ return false;
4264
+ }
4265
+ }
4222
4266
  function isContainedIn(rootDir, candidate, opts = {}) {
4223
4267
  if (candidate.includes("\0")) return false;
4224
4268
  const root = resolve6(rootDir);
@@ -13984,7 +14028,7 @@ init_config();
13984
14028
  init_corrections_md();
13985
14029
  init_db_driver();
13986
14030
  import { readFileSync as readFileSync19, existsSync as existsSync24 } from "fs";
13987
- import { resolve as resolve9 } from "path";
14031
+ import { dirname as dirname13, join as join18, resolve as resolve9 } from "path";
13988
14032
  import { parse as parseYaml4 } from "yaml";
13989
14033
 
13990
14034
  // src/capability-advisor.ts
@@ -14276,18 +14320,69 @@ function checkWindows(pid) {
14276
14320
  init_hook_failure_signal();
14277
14321
 
14278
14322
  // src/hooks/lib/is-direct-invocation.ts
14279
- import { pathToFileURL } from "url";
14323
+ init_safe_write();
14324
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
14280
14325
  function isDirectInvocation(moduleUrl) {
14281
14326
  const entry = process.argv[1];
14282
14327
  if (!entry) return false;
14283
14328
  try {
14284
- return moduleUrl === pathToFileURL(entry).href;
14329
+ if (moduleUrl === pathToFileURL(entry).href) return true;
14285
14330
  } catch {
14286
14331
  return false;
14287
14332
  }
14333
+ try {
14334
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
14335
+ } catch {
14336
+ return false;
14337
+ }
14338
+ }
14339
+
14340
+ // src/cloud-sync.ts
14341
+ init_config();
14342
+ init_memory_db();
14343
+ init_rule_delivery();
14344
+
14345
+ // src/observation-extractor.ts
14346
+ init_memory_db();
14347
+ init_config();
14348
+
14349
+ // src/cloud-sync.ts
14350
+ var CLOUD_SYNC_REFUSAL_KEY = "cloud_sync_refused";
14351
+ var CLOUD_SYNC_RECHECK_MS = 24 * 60 * 60 * 1e3;
14352
+ function readCloudSyncRefusal(db) {
14353
+ try {
14354
+ const raw = getMemoryMeta(db, CLOUD_SYNC_REFUSAL_KEY);
14355
+ if (!raw) return null;
14356
+ const v = JSON.parse(raw);
14357
+ return v && typeof v.status === "number" ? v : null;
14358
+ } catch {
14359
+ return null;
14360
+ }
14361
+ }
14362
+ function cloudSyncRefusalBanner(db) {
14363
+ const r = readCloudSyncRefusal(db);
14364
+ if (!r) return null;
14365
+ return `=== MASSU CLOUD SYNC PAUSED ===
14366
+ The massu cloud refused uploads from this project (HTTP ${r.status}: ${r.reason || "no reason given"}) on ${r.at}. Nothing is being uploaded or queued; local memory is unaffected. It re-checks once a day and resumes on its own when the account allows it.
14367
+ === END ===
14368
+
14369
+ `;
14288
14370
  }
14289
14371
 
14290
14372
  // src/hooks/session-start.ts
14373
+ import { spawn } from "child_process";
14374
+ import { fileURLToPath as fileURLToPath3 } from "url";
14375
+ function startRecallWarmUp() {
14376
+ if (process.env.MASSU_RECALL_WARMUP === "0") return false;
14377
+ try {
14378
+ const recall = join18(dirname13(fileURLToPath3(import.meta.url)), "memory-recall.js");
14379
+ if (!existsSync24(recall)) return false;
14380
+ spawn(process.execPath, [recall, "--warm-cache"], { detached: true, stdio: "ignore" }).unref();
14381
+ return true;
14382
+ } catch {
14383
+ return false;
14384
+ }
14385
+ }
14291
14386
  async function main() {
14292
14387
  try {
14293
14388
  const input = await readStdin();
@@ -14347,6 +14442,12 @@ Session memory, code intelligence, and governance are now active.
14347
14442
  const watcherBanner = buildWatcherBanner();
14348
14443
  if (watcherBanner) process.stdout.write(watcherBanner);
14349
14444
  }
14445
+ try {
14446
+ const syncBanner = cloudSyncRefusalBanner(db);
14447
+ if (syncBanner) process.stdout.write(syncBanner);
14448
+ } catch {
14449
+ }
14450
+ startRecallWarmUp();
14350
14451
  try {
14351
14452
  const consolidationCfg = resolveConsolidationConfig();
14352
14453
  const advisorBlock = await runAdvisors([localModelAdvisor, crossRepoShareAdvisor], {
@@ -14499,11 +14600,11 @@ Index has ${stats.doc_count} documents, last indexed ${ageHours}h ago. Consider
14499
14600
  }
14500
14601
  sections.sort((a, b) => b.importance - a.importance);
14501
14602
  let usedTokens = 0;
14502
- const headerTokens = estimateTokens("=== Massu Memory: Previous Session Context ===\n\n=== END Massu Memory ===\n");
14603
+ const headerTokens = estimateTokens2("=== Massu Memory: Previous Session Context ===\n\n=== END Massu Memory ===\n");
14503
14604
  usedTokens += headerTokens;
14504
14605
  const includedSections = [];
14505
14606
  for (const section of sections) {
14506
- const sectionTokens = estimateTokens(section.text);
14607
+ const sectionTokens = estimateTokens2(section.text);
14507
14608
  if (usedTokens + sectionTokens <= tokenBudget) {
14508
14609
  includedSections.push(section.text);
14509
14610
  usedTokens += sectionTokens;
@@ -14516,7 +14617,7 @@ ${includedSections.join("\n")}
14516
14617
  === END Massu Memory ===
14517
14618
  `;
14518
14619
  }
14519
- function estimateTokens(text) {
14620
+ function estimateTokens2(text) {
14520
14621
  return Math.ceil(text.length / 4);
14521
14622
  }
14522
14623
  async function getGitBranch() {
@@ -14649,6 +14750,9 @@ function formatAge(ms) {
14649
14750
  if (isDirectInvocation(import.meta.url)) {
14650
14751
  main();
14651
14752
  }
14753
+ export {
14754
+ startRecallWarmUp
14755
+ };
14652
14756
  /*! Bundled license information:
14653
14757
 
14654
14758
  is-extglob/index.js:
@@ -2182,6 +2182,8 @@ __export(memory_db_exports, {
2182
2182
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2183
2183
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2184
2184
  MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
2185
+ PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
2186
+ PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
2185
2187
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2186
2188
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2187
2189
  addConversationTurn: () => addConversationTurn,
@@ -2190,6 +2192,7 @@ __export(memory_db_exports, {
2190
2192
  addSummary: () => addSummary,
2191
2193
  addToolCallDetail: () => addToolCallDetail,
2192
2194
  addUserPrompt: () => addUserPrompt,
2195
+ allPendingSync: () => allPendingSync,
2193
2196
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2194
2197
  armUsageCounter: () => armUsageCounter,
2195
2198
  assignImportance: () => assignImportance,
@@ -2197,6 +2200,7 @@ __export(memory_db_exports, {
2197
2200
  createSession: () => createSession,
2198
2201
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2199
2202
  dequeuePendingSync: () => dequeuePendingSync,
2203
+ discardPendingSync: () => discardPendingSync,
2200
2204
  embedMissingObservations: () => embedMissingObservations,
2201
2205
  endSession: () => endSession,
2202
2206
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3179,6 +3183,35 @@ function initMemorySchema(db) {
3179
3183
  }
3180
3184
  function enqueueSyncPayload(db, payload) {
3181
3185
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
3186
+ const overflow = db.prepare(
3187
+ `SELECT id, retry_count, last_error, payload FROM pending_sync
3188
+ WHERE created_at < datetime('now', ?)
3189
+ OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
3190
+ LIMIT 10000`
3191
+ ).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
3192
+ if (overflow.length > 0) {
3193
+ discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
3194
+ }
3195
+ }
3196
+ function discardPendingSync(db, rows, why) {
3197
+ if (rows.length === 0) return 0;
3198
+ const rescued = rescueLearningFromStalePayloads(db, rows);
3199
+ for (let i = 0; i < rows.length; i += 500) {
3200
+ const ids = rows.slice(i, i + 500).map((r) => r.id);
3201
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
3202
+ }
3203
+ if (rescued > 0) {
3204
+ process.stderr.write(
3205
+ `[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
3206
+ `
3207
+ );
3208
+ }
3209
+ process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
3210
+ `);
3211
+ return rows.length;
3212
+ }
3213
+ function allPendingSync(db) {
3214
+ return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
3182
3215
  }
3183
3216
  function getMemoryMeta(db, key) {
3184
3217
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3381,7 +3414,9 @@ function dequeuePendingSync(db, limit = 10) {
3381
3414
  }
3382
3415
  }
3383
3416
  return db.prepare(
3384
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
3417
+ // Least-retried first: oldest-first retried the same 10 items until they were discarded
3418
+ // while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
3419
+ "SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
3385
3420
  ).all(limit);
3386
3421
  }
3387
3422
  function removePendingSync(db, id) {
@@ -4054,7 +4089,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
4054
4089
  }
4055
4090
  return bestMatch;
4056
4091
  }
4057
- var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
4092
+ var MEMORY_SCHEMA_VERSION, TOOL_COST_EVENTS_RETENTION_DAYS, PENDING_SYNC_MAX_ITEMS, PENDING_SYNC_MAX_AGE_DAYS, MAX_DRAFT_TEXT_LEN, FUNNEL_EVENT_OUTBOX_CAP, _temporalColCache, CONSOLIDATION_LESSON_EVIDENCE, MEMORY_FILE_TITLE_PREFIX, MEMORY_FILE_TITLE_LIKE, USAGE_COUNTER_ARMED_KEY;
4058
4093
  var init_memory_db = __esm({
4059
4094
  "src/memory-db.ts"() {
4060
4095
  "use strict";
@@ -4069,6 +4104,8 @@ var init_memory_db = __esm({
4069
4104
  init_memory_embed_sweep();
4070
4105
  MEMORY_SCHEMA_VERSION = 2;
4071
4106
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4107
+ PENDING_SYNC_MAX_ITEMS = 500;
4108
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
4072
4109
  MAX_DRAFT_TEXT_LEN = 16384;
4073
4110
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
4074
4111
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4083,7 +4120,7 @@ var init_memory_db = __esm({
4083
4120
  init_memory_db();
4084
4121
  init_db_driver();
4085
4122
  init_config();
4086
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync4, readFileSync as readFileSync7, readdirSync as readdirSync3, openSync as openSync2, fstatSync, readSync, closeSync as closeSync2 } from "fs";
4123
+ import { existsSync as existsSync11, mkdirSync as mkdirSync8, writeFileSync as writeFileSync5, readFileSync as readFileSync7, readdirSync as readdirSync3, openSync as openSync3, fstatSync, readSync, closeSync as closeSync3 } from "fs";
4087
4124
  import { join as join7 } from "path";
4088
4125
 
4089
4126
  // src/rule-candidate-detector.ts
@@ -4395,13 +4432,41 @@ function entitledForTeamSharedPromotion(tier) {
4395
4432
  init_rule_candidate_store();
4396
4433
  init_hook_failure_signal();
4397
4434
 
4435
+ // src/lib/safe-write.ts
4436
+ import {
4437
+ writeFileSync as writeFileSync4,
4438
+ renameSync,
4439
+ openSync as openSync2,
4440
+ fsyncSync as fsyncSync2,
4441
+ closeSync as closeSync2,
4442
+ existsSync as existsSync10,
4443
+ realpathSync,
4444
+ unlinkSync as unlinkSync2,
4445
+ accessSync as accessSync2,
4446
+ statSync as statSync2,
4447
+ chmodSync as chmodSync3,
4448
+ constants
4449
+ } from "fs";
4450
+ function isSameRealPath(a, b) {
4451
+ try {
4452
+ return realpathSync(a) === realpathSync(b);
4453
+ } catch {
4454
+ return false;
4455
+ }
4456
+ }
4457
+
4398
4458
  // src/hooks/lib/is-direct-invocation.ts
4399
- import { pathToFileURL } from "url";
4459
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4400
4460
  function isDirectInvocation(moduleUrl) {
4401
4461
  const entry = process.argv[1];
4402
4462
  if (!entry) return false;
4403
4463
  try {
4404
- return moduleUrl === pathToFileURL(entry).href;
4464
+ if (moduleUrl === pathToFileURL(entry).href) return true;
4465
+ } catch {
4466
+ return false;
4467
+ }
4468
+ try {
4469
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
4405
4470
  } catch {
4406
4471
  return false;
4407
4472
  }
@@ -4439,7 +4504,7 @@ async function main() {
4439
4504
  const fileRefs = extractFileReferences(prompt);
4440
4505
  if (fileRefs.length > 0) {
4441
4506
  const knowledgeDbPath = getResolvedPaths().knowledgeDbPath;
4442
- if (knowledgeDbPath && existsSync10(knowledgeDbPath)) {
4507
+ if (knowledgeDbPath && existsSync11(knowledgeDbPath)) {
4443
4508
  const kdb = openDatabase2(knowledgeDbPath, { readonly: true, selfHeal: false });
4444
4509
  try {
4445
4510
  const placeholders = fileRefs.map(() => "?").join(",");
@@ -4499,7 +4564,7 @@ async function main() {
4499
4564
  mkdirSync8(candidateDir, { recursive: true });
4500
4565
  const promptHash = hashPrompt(prompt);
4501
4566
  const candidatePath = join7(candidateDir, `${promptHash}.json`);
4502
- if (!existsSync10(candidatePath)) {
4567
+ if (!existsSync11(candidatePath)) {
4503
4568
  upsertCandidate(db, hookInput.cwd, {
4504
4569
  prompt,
4505
4570
  prompt_hash: promptHash,
@@ -4524,7 +4589,7 @@ async function main() {
4524
4589
  }
4525
4590
  } else {
4526
4591
  const nudgePath = join7(candidateDir, ".last-tier-nudge");
4527
- if (!existsSync10(nudgePath)) {
4592
+ if (!existsSync11(nudgePath)) {
4528
4593
  mkdirSync8(candidateDir, { recursive: true });
4529
4594
  process.stderr.write(
4530
4595
  `
@@ -4532,18 +4597,18 @@ async function main() {
4532
4597
 
4533
4598
  `
4534
4599
  );
4535
- writeFileSync4(nudgePath, (/* @__PURE__ */ new Date()).toISOString());
4600
+ writeFileSync5(nudgePath, (/* @__PURE__ */ new Date()).toISOString());
4536
4601
  }
4537
4602
  }
4538
4603
  }
4539
- if (existsSync10(candidateDir)) {
4604
+ if (existsSync11(candidateDir)) {
4540
4605
  const candidates = readdirSync3(candidateDir).filter(
4541
4606
  (f) => f.endsWith(".json") && !f.startsWith(".")
4542
4607
  );
4543
4608
  const candidateCount = candidates.length;
4544
4609
  const surfacedPath = join7(candidateDir, ".last-surfaced");
4545
4610
  let lastSurfaced = 0;
4546
- if (existsSync10(surfacedPath)) {
4611
+ if (existsSync11(surfacedPath)) {
4547
4612
  const raw = readFileSync7(surfacedPath, "utf-8").trim();
4548
4613
  const parsed = parseInt(raw, 10);
4549
4614
  if (!Number.isNaN(parsed)) lastSurfaced = parsed;
@@ -4555,17 +4620,17 @@ async function main() {
4555
4620
 
4556
4621
  `
4557
4622
  );
4558
- writeFileSync4(surfacedPath, String(candidateCount));
4623
+ writeFileSync5(surfacedPath, String(candidateCount));
4559
4624
  }
4560
4625
  }
4561
4626
  } catch (candidateErr) {
4562
4627
  try {
4563
4628
  const dir = join7(hookInput.cwd, ".massu", "rule-candidates");
4564
- if (!existsSync10(dir)) mkdirSync8(dir, { recursive: true });
4629
+ if (!existsSync11(dir)) mkdirSync8(dir, { recursive: true });
4565
4630
  const logPath = join7(dir, ".detector-failures.jsonl");
4566
- const pre = existsSync10(logPath) ? readFileSync7(logPath, "utf-8") : "";
4631
+ const pre = existsSync11(logPath) ? readFileSync7(logPath, "utf-8") : "";
4567
4632
  const sep = pre && !pre.endsWith("\n") ? "\n" : "";
4568
- writeFileSync4(logPath, pre + sep + JSON.stringify({
4633
+ writeFileSync5(logPath, pre + sep + JSON.stringify({
4569
4634
  session_id,
4570
4635
  // Security review (plan-2026-05-27): log the prompt HASH, never a
4571
4636
  // raw prompt excerpt — the failure log must not persist PII/secrets.
@@ -4586,13 +4651,13 @@ async function main() {
4586
4651
  }
4587
4652
  function detectPriorAssistantTurn(transcriptPath) {
4588
4653
  try {
4589
- if (!transcriptPath || !existsSync10(transcriptPath)) {
4654
+ if (!transcriptPath || !existsSync11(transcriptPath)) {
4590
4655
  return { hadEditOrWrite: false, files: [] };
4591
4656
  }
4592
4657
  if (!transcriptPath.includes("/.claude/projects/")) {
4593
4658
  return { hadEditOrWrite: false, files: [] };
4594
4659
  }
4595
- const fd = openSync2(transcriptPath, "r");
4660
+ const fd = openSync3(transcriptPath, "r");
4596
4661
  let buf;
4597
4662
  try {
4598
4663
  const stats = fstatSync(fd);
@@ -4601,7 +4666,7 @@ function detectPriorAssistantTurn(transcriptPath) {
4601
4666
  buf = Buffer.alloc(readLen);
4602
4667
  readSync(fd, buf, 0, readLen, offset);
4603
4668
  } finally {
4604
- closeSync2(fd);
4669
+ closeSync3(fd);
4605
4670
  }
4606
4671
  const lines = buf.toString("utf-8").split("\n").filter(Boolean);
4607
4672
  const files = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massu/core",
3
- "version": "2.5.5",
3
+ "version": "2.5.7",
4
4
  "type": "module",
5
5
  "description": "AI Engineering Governance MCP Server - Session memory, knowledge system, feature registry, code intelligence, rule enforcement, tiered tooling (12 free / 73 total), 56 workflow commands, 11 agents, 20+ patterns",
6
6
  "main": "./dist/cli.js",