@massu/core 2.5.6 → 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.
@@ -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.6",
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",