@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.
@@ -2157,6 +2157,8 @@ __export(memory_db_exports, {
2157
2157
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2158
2158
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2159
2159
  MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
2160
+ PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
2161
+ PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
2160
2162
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2161
2163
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2162
2164
  addConversationTurn: () => addConversationTurn,
@@ -2165,6 +2167,7 @@ __export(memory_db_exports, {
2165
2167
  addSummary: () => addSummary,
2166
2168
  addToolCallDetail: () => addToolCallDetail,
2167
2169
  addUserPrompt: () => addUserPrompt,
2170
+ allPendingSync: () => allPendingSync,
2168
2171
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2169
2172
  armUsageCounter: () => armUsageCounter,
2170
2173
  assignImportance: () => assignImportance,
@@ -2172,6 +2175,7 @@ __export(memory_db_exports, {
2172
2175
  createSession: () => createSession,
2173
2176
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2174
2177
  dequeuePendingSync: () => dequeuePendingSync,
2178
+ discardPendingSync: () => discardPendingSync,
2175
2179
  embedMissingObservations: () => embedMissingObservations,
2176
2180
  endSession: () => endSession,
2177
2181
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3154,6 +3158,35 @@ function initMemorySchema(db) {
3154
3158
  }
3155
3159
  function enqueueSyncPayload(db, payload) {
3156
3160
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
3161
+ const overflow = db.prepare(
3162
+ `SELECT id, retry_count, last_error, payload FROM pending_sync
3163
+ WHERE created_at < datetime('now', ?)
3164
+ OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
3165
+ LIMIT 10000`
3166
+ ).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
3167
+ if (overflow.length > 0) {
3168
+ discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
3169
+ }
3170
+ }
3171
+ function discardPendingSync(db, rows, why) {
3172
+ if (rows.length === 0) return 0;
3173
+ const rescued = rescueLearningFromStalePayloads(db, rows);
3174
+ for (let i = 0; i < rows.length; i += 500) {
3175
+ const ids = rows.slice(i, i + 500).map((r) => r.id);
3176
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
3177
+ }
3178
+ if (rescued > 0) {
3179
+ process.stderr.write(
3180
+ `[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
3181
+ `
3182
+ );
3183
+ }
3184
+ process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
3185
+ `);
3186
+ return rows.length;
3187
+ }
3188
+ function allPendingSync(db) {
3189
+ return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
3157
3190
  }
3158
3191
  function getMemoryMeta(db, key) {
3159
3192
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3356,7 +3389,9 @@ function dequeuePendingSync(db, limit = 10) {
3356
3389
  }
3357
3390
  }
3358
3391
  return db.prepare(
3359
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
3392
+ // Least-retried first: oldest-first retried the same 10 items until they were discarded
3393
+ // while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
3394
+ "SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
3360
3395
  ).all(limit);
3361
3396
  }
3362
3397
  function removePendingSync(db, id) {
@@ -4029,7 +4064,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
4029
4064
  }
4030
4065
  return bestMatch;
4031
4066
  }
4032
- 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;
4067
+ 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;
4033
4068
  var init_memory_db = __esm({
4034
4069
  "src/memory-db.ts"() {
4035
4070
  "use strict";
@@ -4044,6 +4079,8 @@ var init_memory_db = __esm({
4044
4079
  init_memory_embed_sweep();
4045
4080
  MEMORY_SCHEMA_VERSION = 2;
4046
4081
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4082
+ PENDING_SYNC_MAX_ITEMS = 500;
4083
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
4047
4084
  MAX_DRAFT_TEXT_LEN = 16384;
4048
4085
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
4049
4086
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4156,13 +4193,41 @@ function toolResponseText(raw) {
4156
4193
  init_hook_failure_signal();
4157
4194
  init_timestamps();
4158
4195
 
4196
+ // src/lib/safe-write.ts
4197
+ import {
4198
+ writeFileSync as writeFileSync3,
4199
+ renameSync,
4200
+ openSync as openSync2,
4201
+ fsyncSync as fsyncSync2,
4202
+ closeSync as closeSync2,
4203
+ existsSync as existsSync9,
4204
+ realpathSync,
4205
+ unlinkSync as unlinkSync2,
4206
+ accessSync as accessSync2,
4207
+ statSync as statSync2,
4208
+ chmodSync as chmodSync3,
4209
+ constants
4210
+ } from "fs";
4211
+ function isSameRealPath(a, b) {
4212
+ try {
4213
+ return realpathSync(a) === realpathSync(b);
4214
+ } catch {
4215
+ return false;
4216
+ }
4217
+ }
4218
+
4159
4219
  // src/hooks/lib/is-direct-invocation.ts
4160
- import { pathToFileURL } from "url";
4220
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4161
4221
  function isDirectInvocation(moduleUrl) {
4162
4222
  const entry = process.argv[1];
4163
4223
  if (!entry) return false;
4164
4224
  try {
4165
- return moduleUrl === pathToFileURL(entry).href;
4225
+ if (moduleUrl === pathToFileURL(entry).href) return true;
4226
+ } catch {
4227
+ return false;
4228
+ }
4229
+ try {
4230
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
4166
4231
  } catch {
4167
4232
  return false;
4168
4233
  }
@@ -2092,6 +2092,8 @@ __export(memory_db_exports, {
2092
2092
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2093
2093
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2094
2094
  MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
2095
+ PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
2096
+ PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
2095
2097
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2096
2098
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2097
2099
  addConversationTurn: () => addConversationTurn,
@@ -2100,6 +2102,7 @@ __export(memory_db_exports, {
2100
2102
  addSummary: () => addSummary,
2101
2103
  addToolCallDetail: () => addToolCallDetail,
2102
2104
  addUserPrompt: () => addUserPrompt,
2105
+ allPendingSync: () => allPendingSync,
2103
2106
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2104
2107
  armUsageCounter: () => armUsageCounter,
2105
2108
  assignImportance: () => assignImportance,
@@ -2107,6 +2110,7 @@ __export(memory_db_exports, {
2107
2110
  createSession: () => createSession,
2108
2111
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2109
2112
  dequeuePendingSync: () => dequeuePendingSync,
2113
+ discardPendingSync: () => discardPendingSync,
2110
2114
  embedMissingObservations: () => embedMissingObservations,
2111
2115
  endSession: () => endSession,
2112
2116
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3089,6 +3093,35 @@ function initMemorySchema(db) {
3089
3093
  }
3090
3094
  function enqueueSyncPayload(db, payload) {
3091
3095
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
3096
+ const overflow = db.prepare(
3097
+ `SELECT id, retry_count, last_error, payload FROM pending_sync
3098
+ WHERE created_at < datetime('now', ?)
3099
+ OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
3100
+ LIMIT 10000`
3101
+ ).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
3102
+ if (overflow.length > 0) {
3103
+ discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
3104
+ }
3105
+ }
3106
+ function discardPendingSync(db, rows, why) {
3107
+ if (rows.length === 0) return 0;
3108
+ const rescued = rescueLearningFromStalePayloads(db, rows);
3109
+ for (let i = 0; i < rows.length; i += 500) {
3110
+ const ids = rows.slice(i, i + 500).map((r) => r.id);
3111
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
3112
+ }
3113
+ if (rescued > 0) {
3114
+ process.stderr.write(
3115
+ `[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
3116
+ `
3117
+ );
3118
+ }
3119
+ process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
3120
+ `);
3121
+ return rows.length;
3122
+ }
3123
+ function allPendingSync(db) {
3124
+ return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
3092
3125
  }
3093
3126
  function getMemoryMeta(db, key) {
3094
3127
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3291,7 +3324,9 @@ function dequeuePendingSync(db, limit = 10) {
3291
3324
  }
3292
3325
  }
3293
3326
  return db.prepare(
3294
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
3327
+ // Least-retried first: oldest-first retried the same 10 items until they were discarded
3328
+ // while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
3329
+ "SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
3295
3330
  ).all(limit);
3296
3331
  }
3297
3332
  function removePendingSync(db, id) {
@@ -3964,7 +3999,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
3964
3999
  }
3965
4000
  return bestMatch;
3966
4001
  }
3967
- 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;
4002
+ 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;
3968
4003
  var init_memory_db = __esm({
3969
4004
  "src/memory-db.ts"() {
3970
4005
  "use strict";
@@ -3979,6 +4014,8 @@ var init_memory_db = __esm({
3979
4014
  init_memory_embed_sweep();
3980
4015
  MEMORY_SCHEMA_VERSION = 2;
3981
4016
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4017
+ PENDING_SYNC_MAX_ITEMS = 500;
4018
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
3982
4019
  MAX_DRAFT_TEXT_LEN = 16384;
3983
4020
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
3984
4021
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4063,7 +4100,7 @@ var init_hook_failure_signal = __esm({
4063
4100
  // src/hooks/fix-detector.ts
4064
4101
  init_config();
4065
4102
  import { execFileSync } from "child_process";
4066
- import { existsSync as existsSync9, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync6 } from "fs";
4103
+ import { existsSync as existsSync10, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync6 } from "fs";
4067
4104
  import { tmpdir as tmpdir2 } from "os";
4068
4105
  import { join as join6 } from "path";
4069
4106
 
@@ -4441,20 +4478,47 @@ function writeHookContext(hookEventName, message) {
4441
4478
  // src/hooks/fix-detector.ts
4442
4479
  init_hook_failure_signal();
4443
4480
 
4481
+ // src/lib/safe-write.ts
4482
+ import {
4483
+ writeFileSync as writeFileSync3,
4484
+ renameSync,
4485
+ openSync as openSync2,
4486
+ fsyncSync as fsyncSync2,
4487
+ closeSync as closeSync2,
4488
+ existsSync as existsSync9,
4489
+ realpathSync,
4490
+ unlinkSync as unlinkSync2,
4491
+ accessSync as accessSync2,
4492
+ statSync as statSync2,
4493
+ chmodSync as chmodSync3,
4494
+ constants
4495
+ } from "fs";
4496
+ function isSameRealPath(a, b) {
4497
+ try {
4498
+ return realpathSync(a) === realpathSync(b);
4499
+ } catch {
4500
+ return false;
4501
+ }
4502
+ }
4503
+
4444
4504
  // src/hooks/lib/is-direct-invocation.ts
4445
- import { pathToFileURL } from "url";
4505
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4446
4506
  function isDirectInvocation(moduleUrl) {
4447
4507
  const entry = process.argv[1];
4448
4508
  if (!entry) return false;
4449
4509
  try {
4450
- return moduleUrl === pathToFileURL(entry).href;
4510
+ if (moduleUrl === pathToFileURL(entry).href) return true;
4511
+ } catch {
4512
+ return false;
4513
+ }
4514
+ try {
4515
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
4451
4516
  } catch {
4452
4517
  return false;
4453
4518
  }
4454
4519
  }
4455
4520
 
4456
- // src/hooks/fix-detector.ts
4457
- var HOOK_EVENT = "PostToolUse";
4521
+ // src/hooks/lib/test-or-fixture-path.ts
4458
4522
  var TEST_OR_FIXTURE_PATH_PATTERNS = [
4459
4523
  /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4460
4524
  /\.(test|spec)\.[cm]?[jt]sx?$/,
@@ -4463,6 +4527,9 @@ var TEST_OR_FIXTURE_PATH_PATTERNS = [
4463
4527
  function isTestOrFixturePath(filePath) {
4464
4528
  return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4465
4529
  }
4530
+
4531
+ // src/hooks/fix-detector.ts
4532
+ var HOOK_EVENT = "PostToolUse";
4466
4533
  var FIX_HEURISTICS = [
4467
4534
  {
4468
4535
  name: "removed_broken_code",
@@ -4580,14 +4647,14 @@ var FIX_HEURISTICS = [
4580
4647
  ];
4581
4648
  function getSessionFlagPath(sessionId) {
4582
4649
  const dir = join6(tmpdir2(), "massu-auto-learning");
4583
- if (!existsSync9(dir)) {
4650
+ if (!existsSync10(dir)) {
4584
4651
  mkdirSync7(dir, { recursive: true });
4585
4652
  }
4586
4653
  return join6(dir, `fixes-${sessionId.slice(0, 12)}.jsonl`);
4587
4654
  }
4588
4655
  function _stateDir() {
4589
4656
  const dir = join6(tmpdir2(), "massu-fix-detector-state");
4590
- if (!existsSync9(dir)) {
4657
+ if (!existsSync10(dir)) {
4591
4658
  mkdirSync7(dir, { recursive: true });
4592
4659
  }
4593
4660
  return dir;
@@ -4602,7 +4669,7 @@ function _cwdHash(cwd) {
4602
4669
  function isGitWorkTreeFast(cwd) {
4603
4670
  try {
4604
4671
  const cachePath = join6(_stateDir(), `worktree-${_cwdHash(cwd)}.json`);
4605
- if (existsSync9(cachePath)) {
4672
+ if (existsSync10(cachePath)) {
4606
4673
  const cached = JSON.parse(readFileSync6(cachePath, "utf-8"));
4607
4674
  const ageMs = Date.now() - cached.ts;
4608
4675
  if (ageMs < 36e5) return cached.isWorkTree;
@@ -4630,7 +4697,7 @@ function _disabledFlagPath(sessionId) {
4630
4697
  }
4631
4698
  function isSessionAutoDisabled(sessionId) {
4632
4699
  try {
4633
- return existsSync9(_disabledFlagPath(sessionId));
4700
+ return existsSync10(_disabledFlagPath(sessionId));
4634
4701
  } catch {
4635
4702
  return false;
4636
4703
  }
@@ -4648,7 +4715,7 @@ async function main() {
4648
4715
  const input = await readStdin();
4649
4716
  const hookInput = JSON.parse(input);
4650
4717
  const filePath = hookInput.tool_input?.file_path;
4651
- if (!filePath || !existsSync9(filePath)) {
4718
+ if (!filePath || !existsSync10(filePath)) {
4652
4719
  process.exit(0);
4653
4720
  return;
4654
4721
  }
@@ -2157,6 +2157,8 @@ __export(memory_db_exports, {
2157
2157
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2158
2158
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2159
2159
  MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
2160
+ PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
2161
+ PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
2160
2162
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2161
2163
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2162
2164
  addConversationTurn: () => addConversationTurn,
@@ -2165,6 +2167,7 @@ __export(memory_db_exports, {
2165
2167
  addSummary: () => addSummary,
2166
2168
  addToolCallDetail: () => addToolCallDetail,
2167
2169
  addUserPrompt: () => addUserPrompt,
2170
+ allPendingSync: () => allPendingSync,
2168
2171
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2169
2172
  armUsageCounter: () => armUsageCounter,
2170
2173
  assignImportance: () => assignImportance,
@@ -2172,6 +2175,7 @@ __export(memory_db_exports, {
2172
2175
  createSession: () => createSession,
2173
2176
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2174
2177
  dequeuePendingSync: () => dequeuePendingSync,
2178
+ discardPendingSync: () => discardPendingSync,
2175
2179
  embedMissingObservations: () => embedMissingObservations,
2176
2180
  endSession: () => endSession,
2177
2181
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3154,6 +3158,35 @@ function initMemorySchema(db) {
3154
3158
  }
3155
3159
  function enqueueSyncPayload(db, payload) {
3156
3160
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
3161
+ const overflow = db.prepare(
3162
+ `SELECT id, retry_count, last_error, payload FROM pending_sync
3163
+ WHERE created_at < datetime('now', ?)
3164
+ OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
3165
+ LIMIT 10000`
3166
+ ).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
3167
+ if (overflow.length > 0) {
3168
+ discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
3169
+ }
3170
+ }
3171
+ function discardPendingSync(db, rows, why) {
3172
+ if (rows.length === 0) return 0;
3173
+ const rescued = rescueLearningFromStalePayloads(db, rows);
3174
+ for (let i = 0; i < rows.length; i += 500) {
3175
+ const ids = rows.slice(i, i + 500).map((r) => r.id);
3176
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
3177
+ }
3178
+ if (rescued > 0) {
3179
+ process.stderr.write(
3180
+ `[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
3181
+ `
3182
+ );
3183
+ }
3184
+ process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
3185
+ `);
3186
+ return rows.length;
3187
+ }
3188
+ function allPendingSync(db) {
3189
+ return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
3157
3190
  }
3158
3191
  function getMemoryMeta(db, key) {
3159
3192
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3356,7 +3389,9 @@ function dequeuePendingSync(db, limit = 10) {
3356
3389
  }
3357
3390
  }
3358
3391
  return db.prepare(
3359
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
3392
+ // Least-retried first: oldest-first retried the same 10 items until they were discarded
3393
+ // while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
3394
+ "SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
3360
3395
  ).all(limit);
3361
3396
  }
3362
3397
  function removePendingSync(db, id) {
@@ -4029,7 +4064,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
4029
4064
  }
4030
4065
  return bestMatch;
4031
4066
  }
4032
- 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;
4067
+ 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;
4033
4068
  var init_memory_db = __esm({
4034
4069
  "src/memory-db.ts"() {
4035
4070
  "use strict";
@@ -4044,6 +4079,8 @@ var init_memory_db = __esm({
4044
4079
  init_memory_embed_sweep();
4045
4080
  MEMORY_SCHEMA_VERSION = 2;
4046
4081
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4082
+ PENDING_SYNC_MAX_ITEMS = 500;
4083
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
4047
4084
  MAX_DRAFT_TEXT_LEN = 16384;
4048
4085
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
4049
4086
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4057,7 +4094,7 @@ var init_memory_db = __esm({
4057
4094
  // src/hooks/incident-pipeline.ts
4058
4095
  init_config();
4059
4096
  init_memory_db();
4060
- import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
4097
+ import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync2 } from "fs";
4061
4098
  import { basename as basename3, resolve as resolve5 } from "path";
4062
4099
 
4063
4100
  // src/hooks/lib/write-hook-message.ts
@@ -4098,13 +4135,41 @@ function writeHookContext(hookEventName, message) {
4098
4135
  // src/hooks/incident-pipeline.ts
4099
4136
  init_hook_failure_signal();
4100
4137
 
4138
+ // src/lib/safe-write.ts
4139
+ import {
4140
+ writeFileSync as writeFileSync3,
4141
+ renameSync,
4142
+ openSync as openSync2,
4143
+ fsyncSync as fsyncSync2,
4144
+ closeSync as closeSync2,
4145
+ existsSync as existsSync9,
4146
+ realpathSync,
4147
+ unlinkSync as unlinkSync2,
4148
+ accessSync as accessSync2,
4149
+ statSync as statSync2,
4150
+ chmodSync as chmodSync3,
4151
+ constants
4152
+ } from "fs";
4153
+ function isSameRealPath(a, b) {
4154
+ try {
4155
+ return realpathSync(a) === realpathSync(b);
4156
+ } catch {
4157
+ return false;
4158
+ }
4159
+ }
4160
+
4101
4161
  // src/hooks/lib/is-direct-invocation.ts
4102
- import { pathToFileURL } from "url";
4162
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4103
4163
  function isDirectInvocation(moduleUrl) {
4104
4164
  const entry = process.argv[1];
4105
4165
  if (!entry) return false;
4106
4166
  try {
4107
- return moduleUrl === pathToFileURL(entry).href;
4167
+ if (moduleUrl === pathToFileURL(entry).href) return true;
4168
+ } catch {
4169
+ return false;
4170
+ }
4171
+ try {
4172
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
4108
4173
  } catch {
4109
4174
  return false;
4110
4175
  }
@@ -4137,7 +4202,7 @@ async function main() {
4137
4202
  process.exit(0);
4138
4203
  return;
4139
4204
  }
4140
- if (!existsSync9(filePath)) {
4205
+ if (!existsSync10(filePath)) {
4141
4206
  process.exit(0);
4142
4207
  return;
4143
4208
  }
@@ -4147,7 +4212,7 @@ async function main() {
4147
4212
  const slug = basename3(filePath, ".md").replace(/^\d{4}-\d{2}-\d{2}-?/, "").toLowerCase();
4148
4213
  const memoryDirAbs = resolve5(root, memoryDir);
4149
4214
  let hasExistingRule = false;
4150
- if (existsSync9(memoryDirAbs)) {
4215
+ if (existsSync10(memoryDirAbs)) {
4151
4216
  const ruleFiles = readdirSync2(memoryDirAbs).filter((f) => f.startsWith("feedback_"));
4152
4217
  for (const ruleFile of ruleFiles) {
4153
4218
  if (ruleFile.toLowerCase().includes(slug.slice(0, 20))) {
@@ -2086,6 +2086,8 @@ __export(memory_db_exports, {
2086
2086
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2087
2087
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2088
2088
  MEMORY_SCHEMA_VERSION: () => MEMORY_SCHEMA_VERSION,
2089
+ PENDING_SYNC_MAX_AGE_DAYS: () => PENDING_SYNC_MAX_AGE_DAYS,
2090
+ PENDING_SYNC_MAX_ITEMS: () => PENDING_SYNC_MAX_ITEMS,
2089
2091
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2090
2092
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2091
2093
  addConversationTurn: () => addConversationTurn,
@@ -2094,6 +2096,7 @@ __export(memory_db_exports, {
2094
2096
  addSummary: () => addSummary,
2095
2097
  addToolCallDetail: () => addToolCallDetail,
2096
2098
  addUserPrompt: () => addUserPrompt,
2099
+ allPendingSync: () => allPendingSync,
2097
2100
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2098
2101
  armUsageCounter: () => armUsageCounter,
2099
2102
  assignImportance: () => assignImportance,
@@ -2101,6 +2104,7 @@ __export(memory_db_exports, {
2101
2104
  createSession: () => createSession,
2102
2105
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2103
2106
  dequeuePendingSync: () => dequeuePendingSync,
2107
+ discardPendingSync: () => discardPendingSync,
2104
2108
  embedMissingObservations: () => embedMissingObservations,
2105
2109
  endSession: () => endSession,
2106
2110
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3083,6 +3087,35 @@ function initMemorySchema(db) {
3083
3087
  }
3084
3088
  function enqueueSyncPayload(db, payload) {
3085
3089
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
3090
+ const overflow = db.prepare(
3091
+ `SELECT id, retry_count, last_error, payload FROM pending_sync
3092
+ WHERE created_at < datetime('now', ?)
3093
+ OR id NOT IN (SELECT id FROM pending_sync ORDER BY created_at DESC, id DESC LIMIT ?)
3094
+ LIMIT 10000`
3095
+ ).all(`-${PENDING_SYNC_MAX_AGE_DAYS} days`, PENDING_SYNC_MAX_ITEMS);
3096
+ if (overflow.length > 0) {
3097
+ discardPendingSync(db, overflow, `over the queue limit (${PENDING_SYNC_MAX_ITEMS} items / ${PENDING_SYNC_MAX_AGE_DAYS} days)`);
3098
+ }
3099
+ }
3100
+ function discardPendingSync(db, rows, why) {
3101
+ if (rows.length === 0) return 0;
3102
+ const rescued = rescueLearningFromStalePayloads(db, rows);
3103
+ for (let i = 0; i < rows.length; i += 500) {
3104
+ const ids = rows.slice(i, i + 500).map((r) => r.id);
3105
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
3106
+ }
3107
+ if (rescued > 0) {
3108
+ process.stderr.write(
3109
+ `[massu] RESCUED ${rescued} learned rule(s) from ${rows.length} discarded payload(s) \u2014 they were re-queued for delivery, NOT discarded.
3110
+ `
3111
+ );
3112
+ }
3113
+ process.stderr.write(`[massu] cloud sync: discarded ${rows.length} queued upload(s): ${why}.
3114
+ `);
3115
+ return rows.length;
3116
+ }
3117
+ function allPendingSync(db) {
3118
+ return db.prepare("SELECT id, retry_count, last_error, payload FROM pending_sync").all();
3086
3119
  }
3087
3120
  function getMemoryMeta(db, key) {
3088
3121
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3285,7 +3318,9 @@ function dequeuePendingSync(db, limit = 10) {
3285
3318
  }
3286
3319
  }
3287
3320
  return db.prepare(
3288
- "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
3321
+ // Least-retried first: oldest-first retried the same 10 items until they were discarded
3322
+ // while every newer item waited at retry 0 (2026-11-09: 43,492 never attempted).
3323
+ "SELECT id, payload, retry_count FROM pending_sync ORDER BY retry_count ASC, created_at ASC LIMIT ?"
3289
3324
  ).all(limit);
3290
3325
  }
3291
3326
  function removePendingSync(db, id) {
@@ -3958,7 +3993,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
3958
3993
  }
3959
3994
  return bestMatch;
3960
3995
  }
3961
- 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;
3996
+ 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;
3962
3997
  var init_memory_db = __esm({
3963
3998
  "src/memory-db.ts"() {
3964
3999
  "use strict";
@@ -3973,6 +4008,8 @@ var init_memory_db = __esm({
3973
4008
  init_memory_embed_sweep();
3974
4009
  MEMORY_SCHEMA_VERSION = 2;
3975
4010
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4011
+ PENDING_SYNC_MAX_ITEMS = 500;
4012
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
3976
4013
  MAX_DRAFT_TEXT_LEN = 16384;
3977
4014
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
3978
4015
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4092,13 +4129,41 @@ function writeHookContext(hookEventName, message) {
4092
4129
  // src/hooks/intent-suggester.ts
4093
4130
  init_hook_failure_signal();
4094
4131
 
4132
+ // src/lib/safe-write.ts
4133
+ import {
4134
+ writeFileSync as writeFileSync3,
4135
+ renameSync,
4136
+ openSync as openSync2,
4137
+ fsyncSync as fsyncSync2,
4138
+ closeSync as closeSync2,
4139
+ existsSync as existsSync9,
4140
+ realpathSync,
4141
+ unlinkSync as unlinkSync2,
4142
+ accessSync as accessSync2,
4143
+ statSync as statSync2,
4144
+ chmodSync as chmodSync3,
4145
+ constants
4146
+ } from "fs";
4147
+ function isSameRealPath(a, b) {
4148
+ try {
4149
+ return realpathSync(a) === realpathSync(b);
4150
+ } catch {
4151
+ return false;
4152
+ }
4153
+ }
4154
+
4095
4155
  // src/hooks/lib/is-direct-invocation.ts
4096
- import { pathToFileURL } from "url";
4156
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4097
4157
  function isDirectInvocation(moduleUrl) {
4098
4158
  const entry = process.argv[1];
4099
4159
  if (!entry) return false;
4100
4160
  try {
4101
- return moduleUrl === pathToFileURL(entry).href;
4161
+ if (moduleUrl === pathToFileURL(entry).href) return true;
4162
+ } catch {
4163
+ return false;
4164
+ }
4165
+ try {
4166
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
4102
4167
  } catch {
4103
4168
  return false;
4104
4169
  }