@massu/core 2.5.5 → 2.5.6

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
  }
@@ -4063,351 +4057,18 @@ var init_memory_db = __esm({
4063
4057
  // src/hooks/classify-failure.ts
4064
4058
  init_config();
4065
4059
  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";
4060
+ import { existsSync as existsSync9, readFileSync as readFileSync6, readdirSync as readdirSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
4074
4061
  import { tmpdir as tmpdir2 } from "os";
4075
- import { join as join6 } from "path";
4062
+ import { join as join6, basename as basename3 } from "path";
4076
4063
 
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
- });
4064
+ // src/hooks/lib/test-or-fixture-path.ts
4065
+ var TEST_OR_FIXTURE_PATH_PATTERNS = [
4066
+ /(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
4067
+ /\.(test|spec)\.[cm]?[jt]sx?$/,
4068
+ /(^|\/)(test_[^/]+|[^/]+_test)\.py$/
4069
+ ];
4070
+ function isTestOrFixturePath(filePath) {
4071
+ return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
4411
4072
  }
4412
4073
 
4413
4074
  // src/hooks/lib/write-hook-message.ts
@@ -4445,7 +4106,7 @@ function writeHookContext(hookEventName, message) {
4445
4106
  );
4446
4107
  }
4447
4108
 
4448
- // src/hooks/fix-detector.ts
4109
+ // src/hooks/classify-failure.ts
4449
4110
  init_hook_failure_signal();
4450
4111
 
4451
4112
  // src/hooks/lib/is-direct-invocation.ts
@@ -4460,296 +4121,8 @@ function isDirectInvocation(moduleUrl) {
4460
4121
  }
4461
4122
  }
4462
4123
 
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) {
4610
- 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;
4631
- } catch {
4632
- return false;
4633
- }
4634
- }
4635
- function _disabledFlagPath(sessionId) {
4636
- return join6(_stateDir(), `disabled-${sessionId.slice(0, 12)}.flag`);
4637
- }
4638
- function isSessionAutoDisabled(sessionId) {
4639
- try {
4640
- return existsSync9(_disabledFlagPath(sessionId));
4641
- } catch {
4642
- return false;
4643
- }
4644
- }
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
-
4750
4124
  // src/hooks/classify-failure.ts
4751
- init_hook_failure_signal();
4752
- var HOOK_EVENT2 = "PostToolUse";
4125
+ var HOOK_EVENT = "PostToolUse";
4753
4126
  var BUG_FIX_INDICATORS = [
4754
4127
  /\b(catch|error|throw|fail|fix|bug|broken|missing|crash|wrong|typo|incorrect)\b/i
4755
4128
  ];
@@ -4757,7 +4130,7 @@ var CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|swift|rs|go|rb|sh)$/;
4757
4130
  function getDedupeMarkerPath(sessionId, filePath) {
4758
4131
  const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4759
4132
  const hash = simpleHash(filePath);
4760
- return join7(tmpdir3(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
4133
+ return join6(tmpdir2(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
4761
4134
  }
4762
4135
  function simpleHash(str) {
4763
4136
  let h = 0;
@@ -4767,13 +4140,13 @@ function simpleHash(str) {
4767
4140
  return Math.abs(h).toString(36);
4768
4141
  }
4769
4142
  function readFailureContextFiles() {
4770
- const dir = tmpdir3();
4143
+ const dir = tmpdir2();
4771
4144
  let context = "";
4772
4145
  try {
4773
4146
  const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
4774
4147
  for (const file of files) {
4775
4148
  try {
4776
- context += " " + readFileSync7(join7(dir, file), "utf-8");
4149
+ context += " " + readFileSync6(join6(dir, file), "utf-8");
4777
4150
  } catch {
4778
4151
  }
4779
4152
  }
@@ -4782,21 +4155,21 @@ function readFailureContextFiles() {
4782
4155
  return context.trim();
4783
4156
  }
4784
4157
  function cleanupFailureContextFiles() {
4785
- const dir = tmpdir3();
4158
+ const dir = tmpdir2();
4786
4159
  try {
4787
4160
  const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
4788
4161
  for (const file of files) {
4789
4162
  try {
4790
- unlinkSync2(join7(dir, file));
4163
+ unlinkSync2(join6(dir, file));
4791
4164
  } catch {
4792
4165
  }
4793
4166
  }
4794
4167
  } catch {
4795
4168
  }
4796
4169
  }
4797
- async function main2() {
4170
+ async function main() {
4798
4171
  try {
4799
- const input = await readStdin2();
4172
+ const input = await readStdin();
4800
4173
  const hookInput = JSON.parse(input);
4801
4174
  const filePath = hookInput.tool_input?.file_path;
4802
4175
  if (!filePath) {
@@ -4853,7 +4226,7 @@ async function main2() {
4853
4226
  return;
4854
4227
  }
4855
4228
  const dedupeMarker = getDedupeMarkerPath(hookInput.session_id, filePath);
4856
- if (existsSync10(dedupeMarker)) {
4229
+ if (existsSync9(dedupeMarker)) {
4857
4230
  process.exit(0);
4858
4231
  return;
4859
4232
  }
@@ -4895,7 +4268,7 @@ function outputKnownPattern(match) {
4895
4268
  lines.push(` Covered by: ${match.rules.join(", ")}`);
4896
4269
  }
4897
4270
  lines.push(" No new rules needed. Reference existing incident if logging.");
4898
- writeHookContext(HOOK_EVENT2, lines.join("\n"));
4271
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4899
4272
  }
4900
4273
  function outputSimilarPattern(match) {
4901
4274
  const lines = [];
@@ -4905,7 +4278,7 @@ function outputSimilarPattern(match) {
4905
4278
  lines.push(` Check if existing rules cover this case: ${match.rules.join(", ")}`);
4906
4279
  }
4907
4280
  lines.push(" If genuinely new: create incident + prevention rule + enforcement.");
4908
- writeHookContext(HOOK_EVENT2, lines.join("\n"));
4281
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4909
4282
  }
4910
4283
  function outputNewPattern(fileName, match) {
4911
4284
  const lines = [];
@@ -4919,9 +4292,9 @@ function outputNewPattern(fileName, match) {
4919
4292
  lines.push(" 1. INCIDENT REPORT");
4920
4293
  lines.push(" 2. PREVENTION RULE (if new failure pattern)");
4921
4294
  lines.push(" 3. ENFORCEMENT (hook or static check)");
4922
- writeHookContext(HOOK_EVENT2, lines.join("\n"));
4295
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4923
4296
  }
4924
- function readStdin2() {
4297
+ function readStdin() {
4925
4298
  return new Promise((resolve5) => {
4926
4299
  let data = "";
4927
4300
  process.stdin.setEncoding("utf-8");
@@ -4933,5 +4306,5 @@ function readStdin2() {
4933
4306
  });
4934
4307
  }
4935
4308
  if (isDirectInvocation(import.meta.url)) {
4936
- main2();
4309
+ main();
4937
4310
  }