@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.
@@ -4,12 +4,6 @@ var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
- }) : x)(function(x) {
10
- if (typeof require !== "undefined") return require.apply(this, arguments);
11
- throw Error('Dynamic require of "' + x + '" is not supported');
12
- });
13
7
  var __esm = (fn, res, err) => function __init() {
14
8
  if (err) throw err[0];
15
9
  try {
@@ -1866,8 +1860,8 @@ function resolveModelDir() {
1866
1860
  function resolveWasmDir() {
1867
1861
  try {
1868
1862
  const req3 = createRequire3(import.meta.url);
1869
- const main3 = req3.resolve(ORT_PKG);
1870
- return dirname6(main3);
1863
+ const main2 = req3.resolve(ORT_PKG);
1864
+ return dirname6(main2);
1871
1865
  } catch {
1872
1866
  return null;
1873
1867
  }
@@ -2163,6 +2157,8 @@ __export(memory_db_exports, {
2163
2157
  MEMORY_FILE_TITLE_LIKE: () => MEMORY_FILE_TITLE_LIKE,
2164
2158
  MEMORY_FILE_TITLE_PREFIX: () => MEMORY_FILE_TITLE_PREFIX,
2165
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,
2166
2162
  TOOL_COST_EVENTS_RETENTION_DAYS: () => TOOL_COST_EVENTS_RETENTION_DAYS,
2167
2163
  USAGE_COUNTER_ARMED_KEY: () => USAGE_COUNTER_ARMED_KEY,
2168
2164
  addConversationTurn: () => addConversationTurn,
@@ -2171,6 +2167,7 @@ __export(memory_db_exports, {
2171
2167
  addSummary: () => addSummary,
2172
2168
  addToolCallDetail: () => addToolCallDetail,
2173
2169
  addUserPrompt: () => addUserPrompt,
2170
+ allPendingSync: () => allPendingSync,
2174
2171
  appendIncidentToFailureClass: () => appendIncidentToFailureClass,
2175
2172
  armUsageCounter: () => armUsageCounter,
2176
2173
  assignImportance: () => assignImportance,
@@ -2178,6 +2175,7 @@ __export(memory_db_exports, {
2178
2175
  createSession: () => createSession,
2179
2176
  deduplicateFailedAttempt: () => deduplicateFailedAttempt,
2180
2177
  dequeuePendingSync: () => dequeuePendingSync,
2178
+ discardPendingSync: () => discardPendingSync,
2181
2179
  embedMissingObservations: () => embedMissingObservations,
2182
2180
  endSession: () => endSession,
2183
2181
  enqueueRulePromotionEvent: () => enqueueRulePromotionEvent,
@@ -3160,6 +3158,35 @@ function initMemorySchema(db) {
3160
3158
  }
3161
3159
  function enqueueSyncPayload(db, payload) {
3162
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();
3163
3190
  }
3164
3191
  function getMemoryMeta(db, key) {
3165
3192
  const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get(key);
@@ -3362,7 +3389,9 @@ function dequeuePendingSync(db, limit = 10) {
3362
3389
  }
3363
3390
  }
3364
3391
  return db.prepare(
3365
- "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 ?"
3366
3395
  ).all(limit);
3367
3396
  }
3368
3397
  function removePendingSync(db, id) {
@@ -4035,7 +4064,7 @@ function scoreFailureClasses(db, matchText, filePath, promptContext, weights) {
4035
4064
  }
4036
4065
  return bestMatch;
4037
4066
  }
4038
- 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;
4039
4068
  var init_memory_db = __esm({
4040
4069
  "src/memory-db.ts"() {
4041
4070
  "use strict";
@@ -4050,6 +4079,8 @@ var init_memory_db = __esm({
4050
4079
  init_memory_embed_sweep();
4051
4080
  MEMORY_SCHEMA_VERSION = 2;
4052
4081
  TOOL_COST_EVENTS_RETENTION_DAYS = 90;
4082
+ PENDING_SYNC_MAX_ITEMS = 500;
4083
+ PENDING_SYNC_MAX_AGE_DAYS = 30;
4053
4084
  MAX_DRAFT_TEXT_LEN = 16384;
4054
4085
  FUNNEL_EVENT_OUTBOX_CAP = 2e4;
4055
4086
  _temporalColCache = /* @__PURE__ */ new WeakMap();
@@ -4063,351 +4094,18 @@ var init_memory_db = __esm({
4063
4094
  // src/hooks/classify-failure.ts
4064
4095
  init_config();
4065
4096
  init_memory_db();
4066
- import { existsSync as existsSync10, readFileSync as readFileSync7, readdirSync as readdirSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
4067
- import { tmpdir as tmpdir3 } from "os";
4068
- import { join as join7, basename as basename3 } from "path";
4069
-
4070
- // src/hooks/fix-detector.ts
4071
- init_config();
4072
- import { execFileSync } from "child_process";
4073
- import { existsSync as existsSync9, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7, readFileSync as readFileSync6 } from "fs";
4097
+ import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "fs";
4074
4098
  import { tmpdir as tmpdir2 } from "os";
4075
- import { join as join6 } from "path";
4099
+ import { join as join6, basename as basename3 } from "path";
4076
4100
 
4077
- // src/hooks/lib/code-spans.ts
4078
- import { extname } from "path";
4079
- var DQ = { open: '"', close: '"', escapes: true, multiline: false };
4080
- var SQ = { open: "'", close: "'", escapes: true, multiline: false };
4081
- var C_COMMENTS = { lineComments: ["//"], blockComments: [["/*", "*/"]] };
4082
- var JS = {
4083
- id: "js",
4084
- ...C_COMMENTS,
4085
- strings: [DQ, SQ, { open: "`", close: "`", escapes: true, multiline: true, interpolation: true }],
4086
- regexLiterals: true
4087
- };
4088
- var C = { id: "c", ...C_COMMENTS, strings: [DQ, SQ] };
4089
- var GO = {
4090
- id: "go",
4091
- ...C_COMMENTS,
4092
- strings: [DQ, SQ, { open: "`", close: "`", escapes: false, multiline: true }]
4093
- };
4094
- var JVM = {
4095
- id: "jvm",
4096
- ...C_COMMENTS,
4097
- strings: [{ open: '"""', close: '"""', escapes: true, multiline: true }, DQ, SQ]
4098
- };
4099
- var SWIFT = {
4100
- id: "swift",
4101
- ...C_COMMENTS,
4102
- strings: [{ open: '"""', close: '"""', escapes: true, multiline: true }, DQ]
4103
- };
4104
- var RUST = {
4105
- id: "rust",
4106
- ...C_COMMENTS,
4107
- strings: [{ open: '"', close: '"', escapes: true, multiline: true }],
4108
- rustChars: true
4109
- };
4110
- var PYTHON = {
4111
- id: "python",
4112
- lineComments: ["#"],
4113
- blockComments: [],
4114
- strings: [
4115
- { open: '"""', close: '"""', escapes: true, multiline: true },
4116
- { open: "'''", close: "'''", escapes: true, multiline: true },
4117
- DQ,
4118
- SQ
4119
- ]
4120
- };
4121
- var SHELL = {
4122
- id: "shell",
4123
- lineComments: ["#"],
4124
- blockComments: [],
4125
- strings: [
4126
- { open: '"', close: '"', escapes: true, multiline: true },
4127
- { open: "'", close: "'", escapes: false, multiline: true }
4128
- ],
4129
- hashNeedsBoundary: true
4130
- };
4131
- var RUBY = {
4132
- id: "ruby",
4133
- lineComments: ["#"],
4134
- blockComments: [],
4135
- strings: [
4136
- { open: '"', close: '"', escapes: true, multiline: true },
4137
- { open: "'", close: "'", escapes: true, multiline: true }
4138
- ]
4139
- };
4140
- var LEX_SPECS = Object.freeze({
4141
- ".ts": JS,
4142
- ".tsx": JS,
4143
- ".mts": JS,
4144
- ".cts": JS,
4145
- ".js": JS,
4146
- ".jsx": JS,
4147
- ".mjs": JS,
4148
- ".cjs": JS,
4149
- ".c": C,
4150
- ".h": C,
4151
- ".cc": C,
4152
- ".cpp": C,
4153
- ".hpp": C,
4154
- ".cs": C,
4155
- ".dart": C,
4156
- ".m": C,
4157
- ".mm": C,
4158
- ".go": GO,
4159
- ".java": JVM,
4160
- ".kt": JVM,
4161
- ".kts": JVM,
4162
- ".scala": JVM,
4163
- ".groovy": JVM,
4164
- ".swift": SWIFT,
4165
- ".rs": RUST,
4166
- ".py": PYTHON,
4167
- ".pyi": PYTHON,
4168
- ".sh": SHELL,
4169
- ".bash": SHELL,
4170
- ".zsh": SHELL,
4171
- ".rb": RUBY
4172
- });
4173
- function lexSpecFor(path) {
4174
- const ext = extname(path).toLowerCase();
4175
- return Object.prototype.hasOwnProperty.call(LEX_SPECS, ext) ? LEX_SPECS[ext] : null;
4176
- }
4177
- var RUST_CHAR_RE = /^'(?:\\(?:u\{[0-9a-fA-F]{1,6}\}|x[0-9a-fA-F]{2}|.)|[^\\'\n])'/;
4178
- var IDENT_CHAR_RE = /[A-Za-z0-9_$]/;
4179
- var SHELL_WORD_BREAK = /* @__PURE__ */ new Set([" ", " ", ";", "|", "&", "(", ")"]);
4180
- var REGEX_AFTER_KEYWORD = /* @__PURE__ */ new Set([
4181
- "return",
4182
- "typeof",
4183
- "case",
4184
- "in",
4185
- "of",
4186
- "delete",
4187
- "void",
4188
- "throw",
4189
- "new",
4190
- "yield",
4191
- "await",
4192
- "else",
4193
- "do"
4194
- ]);
4195
- function regexLiteralLength(line, i, lastSig) {
4196
- if (lastSig === ")" || lastSig === "]") return 0;
4197
- if (lastSig !== "" && IDENT_CHAR_RE.test(lastSig)) {
4198
- const m = /([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(line.slice(0, i));
4199
- if (!m || !REGEX_AFTER_KEYWORD.has(m[1])) return 0;
4200
- }
4201
- let inClass = false;
4202
- for (let j = i + 1; j < line.length; j++) {
4203
- const ch = line[j];
4204
- if (ch === "\\") {
4205
- j++;
4206
- continue;
4207
- }
4208
- if (inClass) {
4209
- if (ch === "]") inClass = false;
4210
- continue;
4211
- }
4212
- if (ch === "[") {
4213
- inClass = true;
4214
- continue;
4215
- }
4216
- if (ch === "/") return j - i + 1;
4217
- }
4218
- return 0;
4219
- }
4220
- function projectLine(line, spec, st) {
4221
- const out = line.split("");
4222
- const blank = (from, len) => {
4223
- for (let k = from; k < from + len && k < out.length; k++) out[k] = " ";
4224
- };
4225
- let i = 0;
4226
- while (i < line.length) {
4227
- const mode = st.mode;
4228
- if (mode.kind === "block") {
4229
- if (line.startsWith(mode.close, i)) {
4230
- blank(i, mode.close.length);
4231
- i += mode.close.length;
4232
- st.mode = { kind: "code" };
4233
- } else {
4234
- blank(i, 1);
4235
- i++;
4236
- }
4237
- continue;
4238
- }
4239
- if (mode.kind === "string") {
4240
- const r = mode.rule;
4241
- if (r.escapes && line[i] === "\\") {
4242
- blank(i, 2);
4243
- i += 2;
4244
- continue;
4245
- }
4246
- if (r.interpolation && line.startsWith("${", i)) {
4247
- blank(i, 2);
4248
- i += 2;
4249
- st.holes.push(0);
4250
- st.mode = { kind: "code" };
4251
- st.lastSig = "";
4252
- continue;
4253
- }
4254
- if (line.startsWith(r.close, i)) {
4255
- blank(i, r.close.length);
4256
- i += r.close.length;
4257
- st.mode = { kind: "code" };
4258
- st.lastSig = '"';
4259
- continue;
4260
- }
4261
- blank(i, 1);
4262
- i++;
4263
- continue;
4264
- }
4265
- const ch = line[i];
4266
- if (st.holes.length > 0) {
4267
- const top = st.holes.length - 1;
4268
- if (ch === "{") st.holes[top]++;
4269
- else if (ch === "}") {
4270
- if (st.holes[top] === 0) {
4271
- st.holes.pop();
4272
- blank(i, 1);
4273
- i++;
4274
- st.mode = { kind: "string", rule: spec.strings.find((s) => s.interpolation) };
4275
- continue;
4276
- }
4277
- st.holes[top]--;
4278
- }
4279
- }
4280
- const lc = spec.lineComments.find((m) => line.startsWith(m, i));
4281
- if (lc && (!spec.hashNeedsBoundary || i === 0 || SHELL_WORD_BREAK.has(line[i - 1]))) {
4282
- blank(i, line.length - i);
4283
- break;
4284
- }
4285
- const bc = spec.blockComments.find(([open]) => line.startsWith(open, i));
4286
- if (bc) {
4287
- blank(i, bc[0].length);
4288
- i += bc[0].length;
4289
- st.mode = { kind: "block", close: bc[1] };
4290
- continue;
4291
- }
4292
- if (spec.rustChars && ch === "'") {
4293
- const m = RUST_CHAR_RE.exec(line.slice(i));
4294
- if (m) {
4295
- blank(i, m[0].length);
4296
- i += m[0].length;
4297
- st.lastSig = "'";
4298
- continue;
4299
- }
4300
- i++;
4301
- continue;
4302
- }
4303
- const sr = spec.strings.find((s) => line.startsWith(s.open, i));
4304
- if (sr) {
4305
- blank(i, sr.open.length);
4306
- i += sr.open.length;
4307
- st.mode = { kind: "string", rule: sr };
4308
- continue;
4309
- }
4310
- if (spec.regexLiterals && ch === "/") {
4311
- const len = regexLiteralLength(line, i, st.lastSig);
4312
- if (len > 0) {
4313
- blank(i, len);
4314
- i += len;
4315
- st.lastSig = "/";
4316
- continue;
4317
- }
4318
- }
4319
- if (ch !== " " && ch !== " ") st.lastSig = ch;
4320
- i++;
4321
- }
4322
- if (st.mode.kind === "string" && !st.mode.rule.multiline) st.mode = { kind: "code" };
4323
- return out.join("");
4324
- }
4325
- function projectCode(lines, spec) {
4326
- const st = { mode: { kind: "code" }, holes: [], lastSig: "" };
4327
- return lines.map((l) => projectLine(l, spec, st));
4328
- }
4329
- function unquoteGitPath(p) {
4330
- const s = p.startsWith('"') && p.endsWith('"') ? p.slice(1, -1) : p;
4331
- return s.startsWith("b/") ? s.slice(2) : s;
4332
- }
4333
- function diffCodeView(diff, opts = {}) {
4334
- const files = [];
4335
- let cur = null;
4336
- let hunk = null;
4337
- let newLine = 0;
4338
- let inHeader = false;
4339
- const startFile = (path) => {
4340
- const f = { path, hunks: [], removed: [] };
4341
- files.push(f);
4342
- return f;
4343
- };
4344
- for (const line of diff.split("\n")) {
4345
- if (line.startsWith("diff --git ")) {
4346
- cur = startFile("");
4347
- hunk = null;
4348
- inHeader = true;
4349
- continue;
4350
- }
4351
- if (inHeader && line.startsWith("+++ ")) {
4352
- const p = line.slice(4).trim();
4353
- if (cur) cur.path = p === "/dev/null" ? "" : unquoteGitPath(p);
4354
- continue;
4355
- }
4356
- if (inHeader && (line.startsWith("--- ") || !line.startsWith("@@"))) continue;
4357
- const hh = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
4358
- if (hh) {
4359
- inHeader = false;
4360
- if (!cur) cur = startFile("");
4361
- hunk = { newSide: [], addedAt: [] };
4362
- cur.hunks.push(hunk);
4363
- newLine = parseInt(hh[1], 10);
4364
- continue;
4365
- }
4366
- if (!cur) cur = startFile("");
4367
- if (!hunk) {
4368
- hunk = { newSide: [], addedAt: [] };
4369
- cur.hunks.push(hunk);
4370
- newLine = 1;
4371
- }
4372
- if (line.startsWith("+")) {
4373
- hunk.addedAt.push({ idx: hunk.newSide.length, lineNo: newLine });
4374
- hunk.newSide.push(line.slice(1));
4375
- newLine++;
4376
- } else if (line.startsWith("-")) {
4377
- cur.removed.push(line.slice(1));
4378
- } else if (line.startsWith(" ")) {
4379
- hunk.newSide.push(line.slice(1));
4380
- newLine++;
4381
- }
4382
- }
4383
- return files.map((f) => {
4384
- const spec = f.path ? lexSpecFor(f.path) : null;
4385
- const rawAdded = f.hunks.flatMap(
4386
- (h) => h.addedAt.map((a) => ({ lineNo: a.lineNo, raw: h.newSide[a.idx] }))
4387
- );
4388
- if (!spec) {
4389
- return { path: f.path, mode: "raw", added: rawAdded.map((a) => ({ ...a, code: a.raw })), removed: f.removed };
4390
- }
4391
- const text = opts.postImage ? opts.postImage(f.path) : null;
4392
- if (text !== null && text !== void 0) {
4393
- const post = text.split(/\r\n|\n|\r/);
4394
- if (rawAdded.every((a) => post[a.lineNo - 1] === a.raw)) {
4395
- const proj = projectCode(post, spec);
4396
- return {
4397
- path: f.path,
4398
- mode: "post-image",
4399
- added: rawAdded.map((a) => ({ ...a, code: proj[a.lineNo - 1] })),
4400
- removed: f.removed
4401
- };
4402
- }
4403
- }
4404
- const added = [];
4405
- for (const h of f.hunks) {
4406
- const proj = projectCode(h.newSide, spec);
4407
- for (const a of h.addedAt) added.push({ lineNo: a.lineNo, raw: h.newSide[a.idx], code: proj[a.idx] });
4408
- }
4409
- return { path: f.path, mode: "hunk", added, removed: f.removed };
4410
- });
4101
+ // src/hooks/lib/test-or-fixture-path.ts
4102
+ var TEST_OR_FIXTURE_PATH_PATTERNS = [
4103
+ /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4104
+ /\.(test|spec)\.[cm]?[jt]sx?$/,
4105
+ /(^|\/)(test_[^/]+|[^/]+_test)\.py$/
4106
+ ];
4107
+ function isTestOrFixturePath(filePath) {
4108
+ return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4411
4109
  }
4412
4110
 
4413
4111
  // src/hooks/lib/write-hook-message.ts
@@ -4445,311 +4143,51 @@ function writeHookContext(hookEventName, message) {
4445
4143
  );
4446
4144
  }
4447
4145
 
4448
- // src/hooks/fix-detector.ts
4146
+ // src/hooks/classify-failure.ts
4449
4147
  init_hook_failure_signal();
4450
4148
 
4451
- // src/hooks/lib/is-direct-invocation.ts
4452
- import { pathToFileURL } from "url";
4453
- function isDirectInvocation(moduleUrl) {
4454
- const entry = process.argv[1];
4455
- if (!entry) return false;
4149
+ // src/lib/safe-write.ts
4150
+ import {
4151
+ writeFileSync as writeFileSync3,
4152
+ renameSync,
4153
+ openSync as openSync2,
4154
+ fsyncSync as fsyncSync2,
4155
+ closeSync as closeSync2,
4156
+ existsSync as existsSync9,
4157
+ realpathSync,
4158
+ unlinkSync as unlinkSync2,
4159
+ accessSync as accessSync2,
4160
+ statSync as statSync2,
4161
+ chmodSync as chmodSync3,
4162
+ constants
4163
+ } from "fs";
4164
+ function isSameRealPath(a, b) {
4456
4165
  try {
4457
- return moduleUrl === pathToFileURL(entry).href;
4166
+ return realpathSync(a) === realpathSync(b);
4458
4167
  } catch {
4459
4168
  return false;
4460
4169
  }
4461
4170
  }
4462
4171
 
4463
- // src/hooks/fix-detector.ts
4464
- var HOOK_EVENT = "PostToolUse";
4465
- var TEST_OR_FIXTURE_PATH_PATTERNS = [
4466
- /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4467
- /\.(test|spec)\.[cm]?[jt]sx?$/,
4468
- /(^|\/)(test_[^/]+|[^/]+_test)\.py$/
4469
- ];
4470
- function isTestOrFixturePath(filePath) {
4471
- return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4472
- }
4473
- var FIX_HEURISTICS = [
4474
- {
4475
- name: "removed_broken_code",
4476
- test: (diff) => /^-.*\b(bug|broken|wrong|incorrect|typo|crash|error|fail|miss|stale)\b/m.test(diff)
4477
- },
4478
- {
4479
- name: "added_error_handling",
4480
- // `if.*nil` / `if.*None` were DROPPED here, and that is deduplication rather than a silent
4481
- // narrowing: `nil_handling_fix` below already matches exactly that shape
4482
- // (`= nil|= None|is None|!= nil|is not None`), so a nil check scored twice under one banner.
4483
- // Scored on each added line's CODE projection (`lib/code-spans.ts`), the same lexer the
4484
- // Stop hook uses: a keyword in a string literal or a comment is not error handling. The
4485
- // per-edit diff carries its `diff --git` header, so the file's language is known here
4486
- // without threading a path through this table. A language with no lexer is judged raw.
4487
- test: (diff) => {
4488
- const re = /^\+.*\b(try|except|catch|guard|validate|assert|raise|throw)\b/;
4489
- let added = 0;
4490
- for (const view of diffCodeView(diff)) {
4491
- for (const a of view.added) if (re.test(`+${a.code}`)) added++;
4492
- }
4493
- return added > 2;
4494
- }
4495
- },
4496
- // ⛔ `method_name_correction` WAS RETIRED HERE 2026-08-20, and the measurement is why.
4497
- //
4498
- // test: (diff) => {
4499
- // const removed = diff.match(/^-.*\.([a-z_]+)\(/m); // FIRST removed method call
4500
- // const added = diff.match(/^\+.*\.([a-z_]+)\(/m); // FIRST added method call
4501
- // return !!(removed && added && removed[1] !== added[1]);
4502
- // }
4503
- //
4504
- // It compared the FIRST removed call against the FIRST added call, anywhere in the diff.
4505
- // The two need no relationship to each other, so it fired whenever a diff contained any
4506
- // two differently-named method calls — which is nearly every diff that touches code.
4507
- //
4508
- // Measured over the last 250 commits repo-wide:
4509
- // shipped predicate 29 firings (12%)
4510
- // one requiring the two lines to be identical but for the name 0 firings
4511
- // Eleven firings read by hand over packages/core/src: `.push()`->`.includes()`,
4512
- // `.call()`->`.system()`, `.trim()`->`.stringify()`, `.all()`->`.cwd()`, `.now()`->`.push()`,
4513
- // `.on()`->`.sort()`, `.test()`->`.split()` … NOT ONE was a rename. Precision 0 of 29.
4514
- //
4515
- // RETIRED RATHER THAN REPAIRED, on the same numbers: a correct implementation fires ZERO
4516
- // times in 250 commits, so repairing it buys a branch that never runs — dead weight with a
4517
- // maintenance cost (CR-71 in spirit: a detector that cannot fire is not a detector). It was
4518
- // not free while it lived: signals feed `sessionFixes`, which drives the ACTION REQUIRED
4519
- // banner demanding incidents, so this put noise directly on the retro pipeline — the one
4520
- // banner that must not cry wolf (CR-83).
4521
- //
4522
- // Kept as a comment, not deleted outright, so the next author who reaches for
4523
- // "detect a method rename from a diff" finds the measurement instead of rebuilding it.
4524
- //
4525
- // ⛔ THE FULL WRITE-UP IS DELIBERATELY NOT LINKED FROM HERE. This file SYNCS TO THE PUBLIC
4526
- // MIRROR and the incident corpus does not, so a path citation would point at something
4527
- // that does not exist there — and the leak guard refuses it, correctly. It caught exactly
4528
- // this line. The measurement above is self-contained for that reason; search the internal
4529
- // incident corpus for "a summary counted signals and listed files" if you need the rest.
4530
- // (Memory: a-retro-citation-is-publishable-content.)
4531
- {
4532
- name: "auth_fix",
4533
- // `authn`/`authz` are spelled out because `\bauth\b` no longer reaches them, and the
4534
- // trailing `s?` keeps plurals (`tokens`, `credential`+`s`) that the boundary would drop.
4535
- //
4536
- // ⚠️ Backticks, not double quotes, and the plural is split — deliberately. CR-59's
4537
- // drift-guard forbids a quoted `credentials` segment literal outside credentials.ts via
4538
- // /['"]credentials['"]/, which cannot tell a string literal in CODE from quotation marks
4539
- // in PROSE. Writing the word normally here turns that guard red. That is the SAME defect
4540
- // class this file's own boundary fix addresses, in a second detector — recorded in the
4541
- // incident rather than fixed here, because widening a security-adjacent guard is its own
4542
- // change with its own blast radius.
4543
- test: (diff) => /^\+.*\b(token|auth|authn|authz|header|X-Service|Bearer|credential)s?\b/im.test(diff)
4544
- },
4545
- {
4546
- name: "nil_handling_fix",
4547
- test: (diff) => /^\+.*(= nil|= None|\.isNil|is None|!= nil|is not None|guard let|if let|optional)/m.test(diff) && /^-/m.test(diff)
4548
- },
4549
- {
4550
- name: "concurrency_fix",
4551
- // `rate[-_. ]limit` — the original `rate.limit` used an UNESCAPED dot, so `.` matched any
4552
- // character and the alternative fired on "corporate_limits". The class is now explicit
4553
- // about which separators are intended.
4554
- test: (diff) => /^\+.*\b(timeout|semaphore|lock|mutex|throttle|rate[-_. ]limit|max_conn)\b/im.test(diff)
4555
- }
4556
- // ⛔ `async_pattern_fix` AND `added_missing_import` WERE RETIRED HERE 2026-08-20.
4557
- //
4558
- // async_pattern_fix:
4559
- // test: (diff) => /^\+.*(@MainActor|async with|asyncio\.timeout|\.await)/.test(diff)
4560
- // && /^-/m.test(diff),
4561
- // added_missing_import:
4562
- // test: (diff) => /^\+.*(import|from.*import|require)/.test(diff)
4563
- // && !/^-.*(import|from.*import|require)/m.test(diff),
4564
- //
4565
- // Both fire ZERO times across the last 250 commits — measured on the same run that produced
4566
- // the boundary numbers above, over the 87 commits touching non-test source:
4567
- //
4568
- // async_pattern_fix 0 / 87
4569
- // added_missing_import 0 / 87
4570
- //
4571
- // This is the identical finding that retired `method_name_correction` below, and it is
4572
- // retired for the identical reason: a detector that never fires is not a detector, and it
4573
- // still costs review attention and maintenance on every pass over this table (CR-71 in
4574
- // spirit — a zero-caller chokepoint is a dead feature).
4575
- //
4576
- // WHY THEY CANNOT FIRE HERE, so the next author does not "fix" them back in: their tokens are
4577
- // largely foreign to this codebase. `@MainActor` is Swift; `async with` is Python. Neither
4578
- // appears in a TypeScript monorepo. `added_missing_import` requires added imports with NO
4579
- // removed import anywhere in the same diff, which any edit touching an import block violates.
4580
- // If this table is ever pointed at a Swift or Python corpus, re-measure before reinstating —
4581
- // do not reinstate on the assumption that the language mix changed.
4582
- //
4583
- // Kept as comments rather than deleted so the measurement travels with the code, and so a
4584
- // future author reaching for "detect an async-pattern fix" finds the number first.
4585
- // (Same reasoning, and same publication constraint, as the block below: this file syncs to
4586
- // the public mirror, so no internal path citations.)
4587
- ];
4588
- function getSessionFlagPath(sessionId) {
4589
- const dir = join6(tmpdir2(), "massu-auto-learning");
4590
- if (!existsSync9(dir)) {
4591
- mkdirSync7(dir, { recursive: true });
4592
- }
4593
- return join6(dir, `fixes-${sessionId.slice(0, 12)}.jsonl`);
4594
- }
4595
- function _stateDir() {
4596
- const dir = join6(tmpdir2(), "massu-fix-detector-state");
4597
- if (!existsSync9(dir)) {
4598
- mkdirSync7(dir, { recursive: true });
4599
- }
4600
- return dir;
4601
- }
4602
- function _cwdHash(cwd) {
4603
- let h = 5381;
4604
- for (let i = 0; i < cwd.length; i++) {
4605
- h = (h << 5) + h + cwd.charCodeAt(i) | 0;
4606
- }
4607
- return Math.abs(h).toString(36);
4608
- }
4609
- function isGitWorkTreeFast(cwd) {
4172
+ // src/hooks/lib/is-direct-invocation.ts
4173
+ import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
4174
+ function isDirectInvocation(moduleUrl) {
4175
+ const entry = process.argv[1];
4176
+ if (!entry) return false;
4610
4177
  try {
4611
- const cachePath = join6(_stateDir(), `worktree-${_cwdHash(cwd)}.json`);
4612
- if (existsSync9(cachePath)) {
4613
- const cached = JSON.parse(readFileSync6(cachePath, "utf-8"));
4614
- const ageMs = Date.now() - cached.ts;
4615
- if (ageMs < 36e5) return cached.isWorkTree;
4616
- }
4617
- let isWorkTree = false;
4618
- try {
4619
- execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, timeout: 500, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
4620
- isWorkTree = true;
4621
- } catch {
4622
- isWorkTree = false;
4623
- }
4624
- try {
4625
- appendFileSync3(cachePath, "");
4626
- const fs = __require("fs");
4627
- fs.writeFileSync(cachePath, JSON.stringify({ isWorkTree, ts: Date.now() }));
4628
- } catch {
4629
- }
4630
- return isWorkTree;
4178
+ if (moduleUrl === pathToFileURL(entry).href) return true;
4631
4179
  } catch {
4632
4180
  return false;
4633
4181
  }
4634
- }
4635
- function _disabledFlagPath(sessionId) {
4636
- return join6(_stateDir(), `disabled-${sessionId.slice(0, 12)}.flag`);
4637
- }
4638
- function isSessionAutoDisabled(sessionId) {
4639
4182
  try {
4640
- return existsSync9(_disabledFlagPath(sessionId));
4183
+ return isSameRealPath(fileURLToPath2(moduleUrl), entry);
4641
4184
  } catch {
4642
4185
  return false;
4643
4186
  }
4644
4187
  }
4645
- function markSessionAutoDisabled(sessionId, elapsedMs) {
4646
- try {
4647
- const fs = __require("fs");
4648
- fs.writeFileSync(_disabledFlagPath(sessionId), `disabled: previous git diff took ${elapsedMs}ms
4649
- `);
4650
- } catch {
4651
- }
4652
- }
4653
- async function main() {
4654
- try {
4655
- const input = await readStdin();
4656
- const hookInput = JSON.parse(input);
4657
- const filePath = hookInput.tool_input?.file_path;
4658
- if (!filePath || !existsSync9(filePath)) {
4659
- process.exit(0);
4660
- return;
4661
- }
4662
- if (!/\.(py|swift|ts|tsx|js|jsx|rs|go|rb|sh)$/.test(filePath)) {
4663
- process.exit(0);
4664
- return;
4665
- }
4666
- const config = getConfig();
4667
- const incidentDir = config.autoLearning?.incidentDir ?? "docs/incidents";
4668
- const memoryDir = config.autoLearning?.memoryDir ?? "memory";
4669
- if (filePath.includes(incidentDir) || filePath.includes(memoryDir) || filePath.includes("MEMORY.md")) {
4670
- process.exit(0);
4671
- return;
4672
- }
4673
- if (isTestOrFixturePath(filePath)) {
4674
- process.exit(0);
4675
- return;
4676
- }
4677
- if (config.autoLearning?.enabled === false || config.autoLearning?.fixDetection?.enabled === false) {
4678
- process.exit(0);
4679
- return;
4680
- }
4681
- const root = getProjectRoot();
4682
- if (!isGitWorkTreeFast(root) || isSessionAutoDisabled(hookInput.session_id)) {
4683
- process.exit(0);
4684
- return;
4685
- }
4686
- let diff = "";
4687
- try {
4688
- const startMs = Date.now();
4689
- diff = execFileSync("git", ["diff", "--", filePath], { cwd: root, timeout: 3e3, encoding: "utf-8" });
4690
- if (!diff) {
4691
- diff = execFileSync("git", ["diff", "HEAD", "--", filePath], { cwd: root, timeout: 3e3, encoding: "utf-8" });
4692
- }
4693
- const elapsedMs = Date.now() - startMs;
4694
- if (elapsedMs > 2e3) {
4695
- markSessionAutoDisabled(hookInput.session_id, elapsedMs);
4696
- }
4697
- } catch {
4698
- process.exit(0);
4699
- return;
4700
- }
4701
- if (!diff) {
4702
- process.exit(0);
4703
- return;
4704
- }
4705
- const enabledSignals = new Set(config.autoLearning?.fixDetection?.signals ?? FIX_HEURISTICS.map((h) => h.name));
4706
- const detected = [];
4707
- for (const heuristic of FIX_HEURISTICS) {
4708
- if (enabledSignals.has(heuristic.name) && heuristic.test(diff)) {
4709
- detected.push(heuristic.name);
4710
- }
4711
- }
4712
- if (detected.length === 0) {
4713
- process.exit(0);
4714
- return;
4715
- }
4716
- const signal = {
4717
- file: filePath,
4718
- signals: detected,
4719
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4720
- };
4721
- const flagPath = getSessionFlagPath(hookInput.session_id);
4722
- appendFileSync3(flagPath, JSON.stringify(signal) + "\n");
4723
- const lines = readFileSync6(flagPath, "utf-8").split("\n").filter(Boolean);
4724
- if (lines.length === 1) {
4725
- writeHookContext(
4726
- HOOK_EVENT,
4727
- `[Massu Auto-Learning] Bug fix detected in ${filePath} (signals: ${detected.join(", ")}). The auto-learning pipeline will prompt you at session end to create an incident report, derive a prevention rule, and add enforcement.`
4728
- );
4729
- }
4730
- } catch (err) {
4731
- recordHookFailure("fix-detector", err);
4732
- }
4733
- process.exit(0);
4734
- }
4735
- function readStdin() {
4736
- return new Promise((resolve5) => {
4737
- let data = "";
4738
- process.stdin.setEncoding("utf-8");
4739
- process.stdin.on("data", (chunk) => {
4740
- data += chunk;
4741
- });
4742
- process.stdin.on("end", () => resolve5(data));
4743
- setTimeout(() => resolve5(data), 3e3);
4744
- });
4745
- }
4746
- if (isDirectInvocation(import.meta.url)) {
4747
- main();
4748
- }
4749
4188
 
4750
4189
  // src/hooks/classify-failure.ts
4751
- init_hook_failure_signal();
4752
- var HOOK_EVENT2 = "PostToolUse";
4190
+ var HOOK_EVENT = "PostToolUse";
4753
4191
  var BUG_FIX_INDICATORS = [
4754
4192
  /\b(catch|error|throw|fail|fix|bug|broken|missing|crash|wrong|typo|incorrect)\b/i
4755
4193
  ];
@@ -4757,7 +4195,7 @@ var CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|swift|rs|go|rb|sh)$/;
4757
4195
  function getDedupeMarkerPath(sessionId, filePath) {
4758
4196
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4759
4197
  const hash = simpleHash(filePath);
4760
- return join7(tmpdir3(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
4198
+ return join6(tmpdir2(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
4761
4199
  }
4762
4200
  function simpleHash(str) {
4763
4201
  let h = 0;
@@ -4767,13 +4205,13 @@ function simpleHash(str) {
4767
4205
  return Math.abs(h).toString(36);
4768
4206
  }
4769
4207
  function readFailureContextFiles() {
4770
- const dir = tmpdir3();
4208
+ const dir = tmpdir2();
4771
4209
  let context = "";
4772
4210
  try {
4773
4211
  const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
4774
4212
  for (const file of files) {
4775
4213
  try {
4776
- context += " " + readFileSync7(join7(dir, file), "utf-8");
4214
+ context += " " + readFileSync6(join6(dir, file), "utf-8");
4777
4215
  } catch {
4778
4216
  }
4779
4217
  }
@@ -4782,21 +4220,21 @@ function readFailureContextFiles() {
4782
4220
  return context.trim();
4783
4221
  }
4784
4222
  function cleanupFailureContextFiles() {
4785
- const dir = tmpdir3();
4223
+ const dir = tmpdir2();
4786
4224
  try {
4787
4225
  const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
4788
4226
  for (const file of files) {
4789
4227
  try {
4790
- unlinkSync2(join7(dir, file));
4228
+ unlinkSync3(join6(dir, file));
4791
4229
  } catch {
4792
4230
  }
4793
4231
  }
4794
4232
  } catch {
4795
4233
  }
4796
4234
  }
4797
- async function main2() {
4235
+ async function main() {
4798
4236
  try {
4799
- const input = await readStdin2();
4237
+ const input = await readStdin();
4800
4238
  const hookInput = JSON.parse(input);
4801
4239
  const filePath = hookInput.tool_input?.file_path;
4802
4240
  if (!filePath) {
@@ -4858,7 +4296,7 @@ async function main2() {
4858
4296
  return;
4859
4297
  }
4860
4298
  try {
4861
- writeFileSync3(dedupeMarker, "1");
4299
+ writeFileSync4(dedupeMarker, "1");
4862
4300
  } catch {
4863
4301
  }
4864
4302
  const db = getMemoryDb();
@@ -4895,7 +4333,7 @@ function outputKnownPattern(match) {
4895
4333
  lines.push(` Covered by: ${match.rules.join(", ")}`);
4896
4334
  }
4897
4335
  lines.push(" No new rules needed. Reference existing incident if logging.");
4898
- writeHookContext(HOOK_EVENT2, lines.join("\n"));
4336
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4899
4337
  }
4900
4338
  function outputSimilarPattern(match) {
4901
4339
  const lines = [];
@@ -4905,7 +4343,7 @@ function outputSimilarPattern(match) {
4905
4343
  lines.push(` Check if existing rules cover this case: ${match.rules.join(", ")}`);
4906
4344
  }
4907
4345
  lines.push(" If genuinely new: create incident + prevention rule + enforcement.");
4908
- writeHookContext(HOOK_EVENT2, lines.join("\n"));
4346
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4909
4347
  }
4910
4348
  function outputNewPattern(fileName, match) {
4911
4349
  const lines = [];
@@ -4919,9 +4357,9 @@ function outputNewPattern(fileName, match) {
4919
4357
  lines.push(" 1. INCIDENT REPORT");
4920
4358
  lines.push(" 2. PREVENTION RULE (if new failure pattern)");
4921
4359
  lines.push(" 3. ENFORCEMENT (hook or static check)");
4922
- writeHookContext(HOOK_EVENT2, lines.join("\n"));
4360
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4923
4361
  }
4924
- function readStdin2() {
4362
+ function readStdin() {
4925
4363
  return new Promise((resolve5) => {
4926
4364
  let data = "";
4927
4365
  process.stdin.setEncoding("utf-8");
@@ -4933,5 +4371,5 @@ function readStdin2() {
4933
4371
  });
4934
4372
  }
4935
4373
  if (isDirectInvocation(import.meta.url)) {
4936
- main2();
4374
+ main();
4937
4375
  }