@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.
- package/CHANGELOG.md +63 -0
- package/dist/cli.js +167 -46
- package/dist/hooks/auto-learning-pipeline.js +81 -16
- package/dist/hooks/classify-failure.js +90 -652
- package/dist/hooks/cost-tracker.js +69 -4
- package/dist/hooks/fix-detector.js +79 -12
- package/dist/hooks/incident-pipeline.js +72 -7
- package/dist/hooks/intent-suggester.js +69 -4
- package/dist/hooks/memory-recall.js +89 -6
- package/dist/hooks/post-edit-context.js +69 -4
- package/dist/hooks/post-tool-use.js +76 -11
- package/dist/hooks/pre-compact.js +69 -4
- package/dist/hooks/pre-delete-check.js +446 -71
- package/dist/hooks/pre-tool-use-gate.js +237 -223
- package/dist/hooks/quality-event.js +69 -4
- package/dist/hooks/rule-enforcement-pipeline.js +72 -7
- package/dist/hooks/security-gate.js +611 -181
- package/dist/hooks/session-end.js +607 -73
- package/dist/hooks/session-start.js +112 -8
- package/dist/hooks/user-prompt.js +83 -18
- package/package.json +1 -1
|
@@ -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
|
|
1870
|
-
return dirname6(
|
|
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
|
-
|
|
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
|
|
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/
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
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/
|
|
4146
|
+
// src/hooks/classify-failure.ts
|
|
4449
4147
|
init_hook_failure_signal();
|
|
4450
4148
|
|
|
4451
|
-
// src/
|
|
4452
|
-
import {
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
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
|
|
4166
|
+
return realpathSync(a) === realpathSync(b);
|
|
4458
4167
|
} catch {
|
|
4459
4168
|
return false;
|
|
4460
4169
|
}
|
|
4461
4170
|
}
|
|
4462
4171
|
|
|
4463
|
-
// src/hooks/
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 += " " +
|
|
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 =
|
|
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
|
-
|
|
4228
|
+
unlinkSync3(join6(dir, file));
|
|
4791
4229
|
} catch {
|
|
4792
4230
|
}
|
|
4793
4231
|
}
|
|
4794
4232
|
} catch {
|
|
4795
4233
|
}
|
|
4796
4234
|
}
|
|
4797
|
-
async function
|
|
4235
|
+
async function main() {
|
|
4798
4236
|
try {
|
|
4799
|
-
const input = await
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
4360
|
+
writeHookContext(HOOK_EVENT, lines.join("\n"));
|
|
4923
4361
|
}
|
|
4924
|
-
function
|
|
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
|
-
|
|
4374
|
+
main();
|
|
4937
4375
|
}
|