@massu/core 2.5.4 → 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.
- package/CHANGELOG.md +66 -0
- package/dist/cli.js +505 -407
- package/dist/hooks/auto-learning-pipeline.js +26 -2
- package/dist/hooks/classify-failure.js +53 -656
- package/dist/hooks/cost-tracker.js +26 -2
- package/dist/hooks/fix-detector.js +30 -4
- package/dist/hooks/incident-pipeline.js +26 -2
- package/dist/hooks/intent-suggester.js +26 -2
- package/dist/hooks/memory-recall.js +26 -2
- package/dist/hooks/post-edit-context.js +26 -2
- package/dist/hooks/post-tool-use.js +26 -2
- package/dist/hooks/pre-compact.js +26 -2
- package/dist/hooks/pre-delete-check.js +405 -71
- package/dist/hooks/pre-tool-use-gate.js +196 -223
- package/dist/hooks/quality-event.js +26 -2
- package/dist/hooks/rule-enforcement-pipeline.js +26 -2
- package/dist/hooks/security-gate.js +570 -181
- package/dist/hooks/session-end.js +1081 -354
- package/dist/hooks/session-start.js +26 -2
- package/dist/hooks/user-prompt.js +26 -2
- 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 {
|
|
@@ -487,7 +481,8 @@ var init_config = __esm({
|
|
|
487
481
|
input_per_million: z2.number(),
|
|
488
482
|
output_per_million: z2.number(),
|
|
489
483
|
cache_read_per_million: z2.number().optional(),
|
|
490
|
-
cache_write_per_million: z2.number().optional()
|
|
484
|
+
cache_write_per_million: z2.number().optional(),
|
|
485
|
+
cache_write_1h_per_million: z2.number().optional()
|
|
491
486
|
});
|
|
492
487
|
AnalyticsConfigSchema = z2.object({
|
|
493
488
|
quality: z2.object({
|
|
@@ -1865,8 +1860,8 @@ function resolveModelDir() {
|
|
|
1865
1860
|
function resolveWasmDir() {
|
|
1866
1861
|
try {
|
|
1867
1862
|
const req3 = createRequire3(import.meta.url);
|
|
1868
|
-
const
|
|
1869
|
-
return dirname6(
|
|
1863
|
+
const main2 = req3.resolve(ORT_PKG);
|
|
1864
|
+
return dirname6(main2);
|
|
1870
1865
|
} catch {
|
|
1871
1866
|
return null;
|
|
1872
1867
|
}
|
|
@@ -2214,6 +2209,7 @@ __export(memory_db_exports, {
|
|
|
2214
2209
|
pruneToolCostEvents: () => pruneToolCostEvents,
|
|
2215
2210
|
recordRecallHits: () => recordRecallHits,
|
|
2216
2211
|
recordTelemetry: () => recordTelemetry,
|
|
2212
|
+
releaseMetaLease: () => releaseMetaLease,
|
|
2217
2213
|
removePendingSync: () => removePendingSync,
|
|
2218
2214
|
sanitizeFts5Query: () => sanitizeFts5Query,
|
|
2219
2215
|
sanitizeFts5QueryOr: () => sanitizeFts5QueryOr,
|
|
@@ -2222,6 +2218,7 @@ __export(memory_db_exports, {
|
|
|
2222
2218
|
searchObservations: () => searchObservations,
|
|
2223
2219
|
setLastProcessedLine: () => setLastProcessedLine,
|
|
2224
2220
|
setMemoryMeta: () => setMemoryMeta,
|
|
2221
|
+
tryAcquireMetaLease: () => tryAcquireMetaLease,
|
|
2225
2222
|
upsertObservationEmbedding: () => upsertObservationEmbedding,
|
|
2226
2223
|
usageWarmupElapsed: () => usageWarmupElapsed
|
|
2227
2224
|
});
|
|
@@ -2727,6 +2724,15 @@ function initMemorySchema(db) {
|
|
|
2727
2724
|
);
|
|
2728
2725
|
CREATE INDEX IF NOT EXISTS idx_sc_session ON session_costs(session_id);
|
|
2729
2726
|
`);
|
|
2727
|
+
const costCols = db.prepare(`PRAGMA table_info(session_costs)`).all();
|
|
2728
|
+
const addCostCol = (name, ddl) => {
|
|
2729
|
+
if (!costCols.some((c) => c.name === name)) db.exec(`ALTER TABLE session_costs ADD COLUMN ${ddl}`);
|
|
2730
|
+
};
|
|
2731
|
+
addCostCol("cost_status", `cost_status TEXT NOT NULL DEFAULT 'unknown'`);
|
|
2732
|
+
addCostCol("unpriced_models", `unpriced_models TEXT NOT NULL DEFAULT ''`);
|
|
2733
|
+
addCostCol("priced_responses", `priced_responses INTEGER NOT NULL DEFAULT 0`);
|
|
2734
|
+
addCostCol("unpriced_responses", `unpriced_responses INTEGER NOT NULL DEFAULT 0`);
|
|
2735
|
+
addCostCol("pricing_version", `pricing_version INTEGER NOT NULL DEFAULT 0`);
|
|
2730
2736
|
db.exec(`
|
|
2731
2737
|
CREATE TABLE IF NOT EXISTS feature_costs (
|
|
2732
2738
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -3156,6 +3162,18 @@ function getMemoryMeta(db, key) {
|
|
|
3156
3162
|
function setMemoryMeta(db, key, value) {
|
|
3157
3163
|
db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(key, value);
|
|
3158
3164
|
}
|
|
3165
|
+
function tryAcquireMetaLease(db, key, ttlSec, nowSec = Math.floor(Date.now() / 1e3)) {
|
|
3166
|
+
const r = db.prepare(
|
|
3167
|
+
`INSERT INTO memory_meta (key, value) VALUES (?, ?)
|
|
3168
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
3169
|
+
WHERE memory_meta.value = ''
|
|
3170
|
+
OR CAST(substr(memory_meta.value, instr(memory_meta.value, ':') + 1) AS INTEGER) <= ?`
|
|
3171
|
+
).run(key, `${process.pid}:${nowSec + ttlSec}`, nowSec);
|
|
3172
|
+
return Number(r.changes) === 1;
|
|
3173
|
+
}
|
|
3174
|
+
function releaseMetaLease(db, key) {
|
|
3175
|
+
db.prepare(`UPDATE memory_meta SET value = '' WHERE key = ? AND value LIKE ?`).run(key, `${process.pid}:%`);
|
|
3176
|
+
}
|
|
3159
3177
|
function upsertObservationEmbedding(db, observationId, vec, modelId, dim) {
|
|
3160
3178
|
db.prepare(
|
|
3161
3179
|
`INSERT OR REPLACE INTO observation_embeddings (observation_id, model_id, dim, vec, created_at)
|
|
@@ -4024,7 +4042,7 @@ var init_memory_db = __esm({
|
|
|
4024
4042
|
init_rule_candidate_store();
|
|
4025
4043
|
init_hook_failure_signal();
|
|
4026
4044
|
init_memory_embed_sweep();
|
|
4027
|
-
MEMORY_SCHEMA_VERSION =
|
|
4045
|
+
MEMORY_SCHEMA_VERSION = 2;
|
|
4028
4046
|
TOOL_COST_EVENTS_RETENTION_DAYS = 90;
|
|
4029
4047
|
MAX_DRAFT_TEXT_LEN = 16384;
|
|
4030
4048
|
FUNNEL_EVENT_OUTBOX_CAP = 2e4;
|
|
@@ -4039,351 +4057,18 @@ var init_memory_db = __esm({
|
|
|
4039
4057
|
// src/hooks/classify-failure.ts
|
|
4040
4058
|
init_config();
|
|
4041
4059
|
init_memory_db();
|
|
4042
|
-
import { existsSync as
|
|
4043
|
-
import { tmpdir as tmpdir3 } from "os";
|
|
4044
|
-
import { join as join7, basename as basename3 } from "path";
|
|
4045
|
-
|
|
4046
|
-
// src/hooks/fix-detector.ts
|
|
4047
|
-
init_config();
|
|
4048
|
-
import { execFileSync } from "child_process";
|
|
4049
|
-
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";
|
|
4050
4061
|
import { tmpdir as tmpdir2 } from "os";
|
|
4051
|
-
import { join as join6 } from "path";
|
|
4062
|
+
import { join as join6, basename as basename3 } from "path";
|
|
4052
4063
|
|
|
4053
|
-
// src/hooks/lib/
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
strings: [DQ, SQ, { open: "`", close: "`", escapes: true, multiline: true, interpolation: true }],
|
|
4062
|
-
regexLiterals: true
|
|
4063
|
-
};
|
|
4064
|
-
var C = { id: "c", ...C_COMMENTS, strings: [DQ, SQ] };
|
|
4065
|
-
var GO = {
|
|
4066
|
-
id: "go",
|
|
4067
|
-
...C_COMMENTS,
|
|
4068
|
-
strings: [DQ, SQ, { open: "`", close: "`", escapes: false, multiline: true }]
|
|
4069
|
-
};
|
|
4070
|
-
var JVM = {
|
|
4071
|
-
id: "jvm",
|
|
4072
|
-
...C_COMMENTS,
|
|
4073
|
-
strings: [{ open: '"""', close: '"""', escapes: true, multiline: true }, DQ, SQ]
|
|
4074
|
-
};
|
|
4075
|
-
var SWIFT = {
|
|
4076
|
-
id: "swift",
|
|
4077
|
-
...C_COMMENTS,
|
|
4078
|
-
strings: [{ open: '"""', close: '"""', escapes: true, multiline: true }, DQ]
|
|
4079
|
-
};
|
|
4080
|
-
var RUST = {
|
|
4081
|
-
id: "rust",
|
|
4082
|
-
...C_COMMENTS,
|
|
4083
|
-
strings: [{ open: '"', close: '"', escapes: true, multiline: true }],
|
|
4084
|
-
rustChars: true
|
|
4085
|
-
};
|
|
4086
|
-
var PYTHON = {
|
|
4087
|
-
id: "python",
|
|
4088
|
-
lineComments: ["#"],
|
|
4089
|
-
blockComments: [],
|
|
4090
|
-
strings: [
|
|
4091
|
-
{ open: '"""', close: '"""', escapes: true, multiline: true },
|
|
4092
|
-
{ open: "'''", close: "'''", escapes: true, multiline: true },
|
|
4093
|
-
DQ,
|
|
4094
|
-
SQ
|
|
4095
|
-
]
|
|
4096
|
-
};
|
|
4097
|
-
var SHELL = {
|
|
4098
|
-
id: "shell",
|
|
4099
|
-
lineComments: ["#"],
|
|
4100
|
-
blockComments: [],
|
|
4101
|
-
strings: [
|
|
4102
|
-
{ open: '"', close: '"', escapes: true, multiline: true },
|
|
4103
|
-
{ open: "'", close: "'", escapes: false, multiline: true }
|
|
4104
|
-
],
|
|
4105
|
-
hashNeedsBoundary: true
|
|
4106
|
-
};
|
|
4107
|
-
var RUBY = {
|
|
4108
|
-
id: "ruby",
|
|
4109
|
-
lineComments: ["#"],
|
|
4110
|
-
blockComments: [],
|
|
4111
|
-
strings: [
|
|
4112
|
-
{ open: '"', close: '"', escapes: true, multiline: true },
|
|
4113
|
-
{ open: "'", close: "'", escapes: true, multiline: true }
|
|
4114
|
-
]
|
|
4115
|
-
};
|
|
4116
|
-
var LEX_SPECS = Object.freeze({
|
|
4117
|
-
".ts": JS,
|
|
4118
|
-
".tsx": JS,
|
|
4119
|
-
".mts": JS,
|
|
4120
|
-
".cts": JS,
|
|
4121
|
-
".js": JS,
|
|
4122
|
-
".jsx": JS,
|
|
4123
|
-
".mjs": JS,
|
|
4124
|
-
".cjs": JS,
|
|
4125
|
-
".c": C,
|
|
4126
|
-
".h": C,
|
|
4127
|
-
".cc": C,
|
|
4128
|
-
".cpp": C,
|
|
4129
|
-
".hpp": C,
|
|
4130
|
-
".cs": C,
|
|
4131
|
-
".dart": C,
|
|
4132
|
-
".m": C,
|
|
4133
|
-
".mm": C,
|
|
4134
|
-
".go": GO,
|
|
4135
|
-
".java": JVM,
|
|
4136
|
-
".kt": JVM,
|
|
4137
|
-
".kts": JVM,
|
|
4138
|
-
".scala": JVM,
|
|
4139
|
-
".groovy": JVM,
|
|
4140
|
-
".swift": SWIFT,
|
|
4141
|
-
".rs": RUST,
|
|
4142
|
-
".py": PYTHON,
|
|
4143
|
-
".pyi": PYTHON,
|
|
4144
|
-
".sh": SHELL,
|
|
4145
|
-
".bash": SHELL,
|
|
4146
|
-
".zsh": SHELL,
|
|
4147
|
-
".rb": RUBY
|
|
4148
|
-
});
|
|
4149
|
-
function lexSpecFor(path) {
|
|
4150
|
-
const ext = extname(path).toLowerCase();
|
|
4151
|
-
return Object.prototype.hasOwnProperty.call(LEX_SPECS, ext) ? LEX_SPECS[ext] : null;
|
|
4152
|
-
}
|
|
4153
|
-
var RUST_CHAR_RE = /^'(?:\\(?:u\{[0-9a-fA-F]{1,6}\}|x[0-9a-fA-F]{2}|.)|[^\\'\n])'/;
|
|
4154
|
-
var IDENT_CHAR_RE = /[A-Za-z0-9_$]/;
|
|
4155
|
-
var SHELL_WORD_BREAK = /* @__PURE__ */ new Set([" ", " ", ";", "|", "&", "(", ")"]);
|
|
4156
|
-
var REGEX_AFTER_KEYWORD = /* @__PURE__ */ new Set([
|
|
4157
|
-
"return",
|
|
4158
|
-
"typeof",
|
|
4159
|
-
"case",
|
|
4160
|
-
"in",
|
|
4161
|
-
"of",
|
|
4162
|
-
"delete",
|
|
4163
|
-
"void",
|
|
4164
|
-
"throw",
|
|
4165
|
-
"new",
|
|
4166
|
-
"yield",
|
|
4167
|
-
"await",
|
|
4168
|
-
"else",
|
|
4169
|
-
"do"
|
|
4170
|
-
]);
|
|
4171
|
-
function regexLiteralLength(line, i, lastSig) {
|
|
4172
|
-
if (lastSig === ")" || lastSig === "]") return 0;
|
|
4173
|
-
if (lastSig !== "" && IDENT_CHAR_RE.test(lastSig)) {
|
|
4174
|
-
const m = /([A-Za-z_$][A-Za-z0-9_$]*)\s*$/.exec(line.slice(0, i));
|
|
4175
|
-
if (!m || !REGEX_AFTER_KEYWORD.has(m[1])) return 0;
|
|
4176
|
-
}
|
|
4177
|
-
let inClass = false;
|
|
4178
|
-
for (let j = i + 1; j < line.length; j++) {
|
|
4179
|
-
const ch = line[j];
|
|
4180
|
-
if (ch === "\\") {
|
|
4181
|
-
j++;
|
|
4182
|
-
continue;
|
|
4183
|
-
}
|
|
4184
|
-
if (inClass) {
|
|
4185
|
-
if (ch === "]") inClass = false;
|
|
4186
|
-
continue;
|
|
4187
|
-
}
|
|
4188
|
-
if (ch === "[") {
|
|
4189
|
-
inClass = true;
|
|
4190
|
-
continue;
|
|
4191
|
-
}
|
|
4192
|
-
if (ch === "/") return j - i + 1;
|
|
4193
|
-
}
|
|
4194
|
-
return 0;
|
|
4195
|
-
}
|
|
4196
|
-
function projectLine(line, spec, st) {
|
|
4197
|
-
const out = line.split("");
|
|
4198
|
-
const blank = (from, len) => {
|
|
4199
|
-
for (let k = from; k < from + len && k < out.length; k++) out[k] = " ";
|
|
4200
|
-
};
|
|
4201
|
-
let i = 0;
|
|
4202
|
-
while (i < line.length) {
|
|
4203
|
-
const mode = st.mode;
|
|
4204
|
-
if (mode.kind === "block") {
|
|
4205
|
-
if (line.startsWith(mode.close, i)) {
|
|
4206
|
-
blank(i, mode.close.length);
|
|
4207
|
-
i += mode.close.length;
|
|
4208
|
-
st.mode = { kind: "code" };
|
|
4209
|
-
} else {
|
|
4210
|
-
blank(i, 1);
|
|
4211
|
-
i++;
|
|
4212
|
-
}
|
|
4213
|
-
continue;
|
|
4214
|
-
}
|
|
4215
|
-
if (mode.kind === "string") {
|
|
4216
|
-
const r = mode.rule;
|
|
4217
|
-
if (r.escapes && line[i] === "\\") {
|
|
4218
|
-
blank(i, 2);
|
|
4219
|
-
i += 2;
|
|
4220
|
-
continue;
|
|
4221
|
-
}
|
|
4222
|
-
if (r.interpolation && line.startsWith("${", i)) {
|
|
4223
|
-
blank(i, 2);
|
|
4224
|
-
i += 2;
|
|
4225
|
-
st.holes.push(0);
|
|
4226
|
-
st.mode = { kind: "code" };
|
|
4227
|
-
st.lastSig = "";
|
|
4228
|
-
continue;
|
|
4229
|
-
}
|
|
4230
|
-
if (line.startsWith(r.close, i)) {
|
|
4231
|
-
blank(i, r.close.length);
|
|
4232
|
-
i += r.close.length;
|
|
4233
|
-
st.mode = { kind: "code" };
|
|
4234
|
-
st.lastSig = '"';
|
|
4235
|
-
continue;
|
|
4236
|
-
}
|
|
4237
|
-
blank(i, 1);
|
|
4238
|
-
i++;
|
|
4239
|
-
continue;
|
|
4240
|
-
}
|
|
4241
|
-
const ch = line[i];
|
|
4242
|
-
if (st.holes.length > 0) {
|
|
4243
|
-
const top = st.holes.length - 1;
|
|
4244
|
-
if (ch === "{") st.holes[top]++;
|
|
4245
|
-
else if (ch === "}") {
|
|
4246
|
-
if (st.holes[top] === 0) {
|
|
4247
|
-
st.holes.pop();
|
|
4248
|
-
blank(i, 1);
|
|
4249
|
-
i++;
|
|
4250
|
-
st.mode = { kind: "string", rule: spec.strings.find((s) => s.interpolation) };
|
|
4251
|
-
continue;
|
|
4252
|
-
}
|
|
4253
|
-
st.holes[top]--;
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
const lc = spec.lineComments.find((m) => line.startsWith(m, i));
|
|
4257
|
-
if (lc && (!spec.hashNeedsBoundary || i === 0 || SHELL_WORD_BREAK.has(line[i - 1]))) {
|
|
4258
|
-
blank(i, line.length - i);
|
|
4259
|
-
break;
|
|
4260
|
-
}
|
|
4261
|
-
const bc = spec.blockComments.find(([open]) => line.startsWith(open, i));
|
|
4262
|
-
if (bc) {
|
|
4263
|
-
blank(i, bc[0].length);
|
|
4264
|
-
i += bc[0].length;
|
|
4265
|
-
st.mode = { kind: "block", close: bc[1] };
|
|
4266
|
-
continue;
|
|
4267
|
-
}
|
|
4268
|
-
if (spec.rustChars && ch === "'") {
|
|
4269
|
-
const m = RUST_CHAR_RE.exec(line.slice(i));
|
|
4270
|
-
if (m) {
|
|
4271
|
-
blank(i, m[0].length);
|
|
4272
|
-
i += m[0].length;
|
|
4273
|
-
st.lastSig = "'";
|
|
4274
|
-
continue;
|
|
4275
|
-
}
|
|
4276
|
-
i++;
|
|
4277
|
-
continue;
|
|
4278
|
-
}
|
|
4279
|
-
const sr = spec.strings.find((s) => line.startsWith(s.open, i));
|
|
4280
|
-
if (sr) {
|
|
4281
|
-
blank(i, sr.open.length);
|
|
4282
|
-
i += sr.open.length;
|
|
4283
|
-
st.mode = { kind: "string", rule: sr };
|
|
4284
|
-
continue;
|
|
4285
|
-
}
|
|
4286
|
-
if (spec.regexLiterals && ch === "/") {
|
|
4287
|
-
const len = regexLiteralLength(line, i, st.lastSig);
|
|
4288
|
-
if (len > 0) {
|
|
4289
|
-
blank(i, len);
|
|
4290
|
-
i += len;
|
|
4291
|
-
st.lastSig = "/";
|
|
4292
|
-
continue;
|
|
4293
|
-
}
|
|
4294
|
-
}
|
|
4295
|
-
if (ch !== " " && ch !== " ") st.lastSig = ch;
|
|
4296
|
-
i++;
|
|
4297
|
-
}
|
|
4298
|
-
if (st.mode.kind === "string" && !st.mode.rule.multiline) st.mode = { kind: "code" };
|
|
4299
|
-
return out.join("");
|
|
4300
|
-
}
|
|
4301
|
-
function projectCode(lines, spec) {
|
|
4302
|
-
const st = { mode: { kind: "code" }, holes: [], lastSig: "" };
|
|
4303
|
-
return lines.map((l) => projectLine(l, spec, st));
|
|
4304
|
-
}
|
|
4305
|
-
function unquoteGitPath(p) {
|
|
4306
|
-
const s = p.startsWith('"') && p.endsWith('"') ? p.slice(1, -1) : p;
|
|
4307
|
-
return s.startsWith("b/") ? s.slice(2) : s;
|
|
4308
|
-
}
|
|
4309
|
-
function diffCodeView(diff, opts = {}) {
|
|
4310
|
-
const files = [];
|
|
4311
|
-
let cur = null;
|
|
4312
|
-
let hunk = null;
|
|
4313
|
-
let newLine = 0;
|
|
4314
|
-
let inHeader = false;
|
|
4315
|
-
const startFile = (path) => {
|
|
4316
|
-
const f = { path, hunks: [], removed: [] };
|
|
4317
|
-
files.push(f);
|
|
4318
|
-
return f;
|
|
4319
|
-
};
|
|
4320
|
-
for (const line of diff.split("\n")) {
|
|
4321
|
-
if (line.startsWith("diff --git ")) {
|
|
4322
|
-
cur = startFile("");
|
|
4323
|
-
hunk = null;
|
|
4324
|
-
inHeader = true;
|
|
4325
|
-
continue;
|
|
4326
|
-
}
|
|
4327
|
-
if (inHeader && line.startsWith("+++ ")) {
|
|
4328
|
-
const p = line.slice(4).trim();
|
|
4329
|
-
if (cur) cur.path = p === "/dev/null" ? "" : unquoteGitPath(p);
|
|
4330
|
-
continue;
|
|
4331
|
-
}
|
|
4332
|
-
if (inHeader && (line.startsWith("--- ") || !line.startsWith("@@"))) continue;
|
|
4333
|
-
const hh = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
|
|
4334
|
-
if (hh) {
|
|
4335
|
-
inHeader = false;
|
|
4336
|
-
if (!cur) cur = startFile("");
|
|
4337
|
-
hunk = { newSide: [], addedAt: [] };
|
|
4338
|
-
cur.hunks.push(hunk);
|
|
4339
|
-
newLine = parseInt(hh[1], 10);
|
|
4340
|
-
continue;
|
|
4341
|
-
}
|
|
4342
|
-
if (!cur) cur = startFile("");
|
|
4343
|
-
if (!hunk) {
|
|
4344
|
-
hunk = { newSide: [], addedAt: [] };
|
|
4345
|
-
cur.hunks.push(hunk);
|
|
4346
|
-
newLine = 1;
|
|
4347
|
-
}
|
|
4348
|
-
if (line.startsWith("+")) {
|
|
4349
|
-
hunk.addedAt.push({ idx: hunk.newSide.length, lineNo: newLine });
|
|
4350
|
-
hunk.newSide.push(line.slice(1));
|
|
4351
|
-
newLine++;
|
|
4352
|
-
} else if (line.startsWith("-")) {
|
|
4353
|
-
cur.removed.push(line.slice(1));
|
|
4354
|
-
} else if (line.startsWith(" ")) {
|
|
4355
|
-
hunk.newSide.push(line.slice(1));
|
|
4356
|
-
newLine++;
|
|
4357
|
-
}
|
|
4358
|
-
}
|
|
4359
|
-
return files.map((f) => {
|
|
4360
|
-
const spec = f.path ? lexSpecFor(f.path) : null;
|
|
4361
|
-
const rawAdded = f.hunks.flatMap(
|
|
4362
|
-
(h) => h.addedAt.map((a) => ({ lineNo: a.lineNo, raw: h.newSide[a.idx] }))
|
|
4363
|
-
);
|
|
4364
|
-
if (!spec) {
|
|
4365
|
-
return { path: f.path, mode: "raw", added: rawAdded.map((a) => ({ ...a, code: a.raw })), removed: f.removed };
|
|
4366
|
-
}
|
|
4367
|
-
const text = opts.postImage ? opts.postImage(f.path) : null;
|
|
4368
|
-
if (text !== null && text !== void 0) {
|
|
4369
|
-
const post = text.split(/\r\n|\n|\r/);
|
|
4370
|
-
if (rawAdded.every((a) => post[a.lineNo - 1] === a.raw)) {
|
|
4371
|
-
const proj = projectCode(post, spec);
|
|
4372
|
-
return {
|
|
4373
|
-
path: f.path,
|
|
4374
|
-
mode: "post-image",
|
|
4375
|
-
added: rawAdded.map((a) => ({ ...a, code: proj[a.lineNo - 1] })),
|
|
4376
|
-
removed: f.removed
|
|
4377
|
-
};
|
|
4378
|
-
}
|
|
4379
|
-
}
|
|
4380
|
-
const added = [];
|
|
4381
|
-
for (const h of f.hunks) {
|
|
4382
|
-
const proj = projectCode(h.newSide, spec);
|
|
4383
|
-
for (const a of h.addedAt) added.push({ lineNo: a.lineNo, raw: h.newSide[a.idx], code: proj[a.idx] });
|
|
4384
|
-
}
|
|
4385
|
-
return { path: f.path, mode: "hunk", added, removed: f.removed };
|
|
4386
|
-
});
|
|
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));
|
|
4387
4072
|
}
|
|
4388
4073
|
|
|
4389
4074
|
// src/hooks/lib/write-hook-message.ts
|
|
@@ -4421,7 +4106,7 @@ function writeHookContext(hookEventName, message) {
|
|
|
4421
4106
|
);
|
|
4422
4107
|
}
|
|
4423
4108
|
|
|
4424
|
-
// src/hooks/
|
|
4109
|
+
// src/hooks/classify-failure.ts
|
|
4425
4110
|
init_hook_failure_signal();
|
|
4426
4111
|
|
|
4427
4112
|
// src/hooks/lib/is-direct-invocation.ts
|
|
@@ -4436,296 +4121,8 @@ function isDirectInvocation(moduleUrl) {
|
|
|
4436
4121
|
}
|
|
4437
4122
|
}
|
|
4438
4123
|
|
|
4439
|
-
// src/hooks/fix-detector.ts
|
|
4440
|
-
var HOOK_EVENT = "PostToolUse";
|
|
4441
|
-
var TEST_OR_FIXTURE_PATH_PATTERNS = [
|
|
4442
|
-
/(^|\/)(__tests__|__fixtures__|tests?|fixtures?|spec)\//,
|
|
4443
|
-
/\.(test|spec)\.[cm]?[jt]sx?$/,
|
|
4444
|
-
/(^|\/)(test_[^/]+|[^/]+_test)\.py$/
|
|
4445
|
-
];
|
|
4446
|
-
function isTestOrFixturePath(filePath) {
|
|
4447
|
-
return TEST_OR_FIXTURE_PATH_PATTERNS.some((re) => re.test(filePath));
|
|
4448
|
-
}
|
|
4449
|
-
var FIX_HEURISTICS = [
|
|
4450
|
-
{
|
|
4451
|
-
name: "removed_broken_code",
|
|
4452
|
-
test: (diff) => /^-.*\b(bug|broken|wrong|incorrect|typo|crash|error|fail|miss|stale)\b/m.test(diff)
|
|
4453
|
-
},
|
|
4454
|
-
{
|
|
4455
|
-
name: "added_error_handling",
|
|
4456
|
-
// `if.*nil` / `if.*None` were DROPPED here, and that is deduplication rather than a silent
|
|
4457
|
-
// narrowing: `nil_handling_fix` below already matches exactly that shape
|
|
4458
|
-
// (`= nil|= None|is None|!= nil|is not None`), so a nil check scored twice under one banner.
|
|
4459
|
-
// Scored on each added line's CODE projection (`lib/code-spans.ts`), the same lexer the
|
|
4460
|
-
// Stop hook uses: a keyword in a string literal or a comment is not error handling. The
|
|
4461
|
-
// per-edit diff carries its `diff --git` header, so the file's language is known here
|
|
4462
|
-
// without threading a path through this table. A language with no lexer is judged raw.
|
|
4463
|
-
test: (diff) => {
|
|
4464
|
-
const re = /^\+.*\b(try|except|catch|guard|validate|assert|raise|throw)\b/;
|
|
4465
|
-
let added = 0;
|
|
4466
|
-
for (const view of diffCodeView(diff)) {
|
|
4467
|
-
for (const a of view.added) if (re.test(`+${a.code}`)) added++;
|
|
4468
|
-
}
|
|
4469
|
-
return added > 2;
|
|
4470
|
-
}
|
|
4471
|
-
},
|
|
4472
|
-
// ⛔ `method_name_correction` WAS RETIRED HERE 2026-08-20, and the measurement is why.
|
|
4473
|
-
//
|
|
4474
|
-
// test: (diff) => {
|
|
4475
|
-
// const removed = diff.match(/^-.*\.([a-z_]+)\(/m); // FIRST removed method call
|
|
4476
|
-
// const added = diff.match(/^\+.*\.([a-z_]+)\(/m); // FIRST added method call
|
|
4477
|
-
// return !!(removed && added && removed[1] !== added[1]);
|
|
4478
|
-
// }
|
|
4479
|
-
//
|
|
4480
|
-
// It compared the FIRST removed call against the FIRST added call, anywhere in the diff.
|
|
4481
|
-
// The two need no relationship to each other, so it fired whenever a diff contained any
|
|
4482
|
-
// two differently-named method calls — which is nearly every diff that touches code.
|
|
4483
|
-
//
|
|
4484
|
-
// Measured over the last 250 commits repo-wide:
|
|
4485
|
-
// shipped predicate 29 firings (12%)
|
|
4486
|
-
// one requiring the two lines to be identical but for the name 0 firings
|
|
4487
|
-
// Eleven firings read by hand over packages/core/src: `.push()`->`.includes()`,
|
|
4488
|
-
// `.call()`->`.system()`, `.trim()`->`.stringify()`, `.all()`->`.cwd()`, `.now()`->`.push()`,
|
|
4489
|
-
// `.on()`->`.sort()`, `.test()`->`.split()` … NOT ONE was a rename. Precision 0 of 29.
|
|
4490
|
-
//
|
|
4491
|
-
// RETIRED RATHER THAN REPAIRED, on the same numbers: a correct implementation fires ZERO
|
|
4492
|
-
// times in 250 commits, so repairing it buys a branch that never runs — dead weight with a
|
|
4493
|
-
// maintenance cost (CR-71 in spirit: a detector that cannot fire is not a detector). It was
|
|
4494
|
-
// not free while it lived: signals feed `sessionFixes`, which drives the ACTION REQUIRED
|
|
4495
|
-
// banner demanding incidents, so this put noise directly on the retro pipeline — the one
|
|
4496
|
-
// banner that must not cry wolf (CR-83).
|
|
4497
|
-
//
|
|
4498
|
-
// Kept as a comment, not deleted outright, so the next author who reaches for
|
|
4499
|
-
// "detect a method rename from a diff" finds the measurement instead of rebuilding it.
|
|
4500
|
-
//
|
|
4501
|
-
// ⛔ THE FULL WRITE-UP IS DELIBERATELY NOT LINKED FROM HERE. This file SYNCS TO THE PUBLIC
|
|
4502
|
-
// MIRROR and the incident corpus does not, so a path citation would point at something
|
|
4503
|
-
// that does not exist there — and the leak guard refuses it, correctly. It caught exactly
|
|
4504
|
-
// this line. The measurement above is self-contained for that reason; search the internal
|
|
4505
|
-
// incident corpus for "a summary counted signals and listed files" if you need the rest.
|
|
4506
|
-
// (Memory: a-retro-citation-is-publishable-content.)
|
|
4507
|
-
{
|
|
4508
|
-
name: "auth_fix",
|
|
4509
|
-
// `authn`/`authz` are spelled out because `\bauth\b` no longer reaches them, and the
|
|
4510
|
-
// trailing `s?` keeps plurals (`tokens`, `credential`+`s`) that the boundary would drop.
|
|
4511
|
-
//
|
|
4512
|
-
// ⚠️ Backticks, not double quotes, and the plural is split — deliberately. CR-59's
|
|
4513
|
-
// drift-guard forbids a quoted `credentials` segment literal outside credentials.ts via
|
|
4514
|
-
// /['"]credentials['"]/, which cannot tell a string literal in CODE from quotation marks
|
|
4515
|
-
// in PROSE. Writing the word normally here turns that guard red. That is the SAME defect
|
|
4516
|
-
// class this file's own boundary fix addresses, in a second detector — recorded in the
|
|
4517
|
-
// incident rather than fixed here, because widening a security-adjacent guard is its own
|
|
4518
|
-
// change with its own blast radius.
|
|
4519
|
-
test: (diff) => /^\+.*\b(token|auth|authn|authz|header|X-Service|Bearer|credential)s?\b/im.test(diff)
|
|
4520
|
-
},
|
|
4521
|
-
{
|
|
4522
|
-
name: "nil_handling_fix",
|
|
4523
|
-
test: (diff) => /^\+.*(= nil|= None|\.isNil|is None|!= nil|is not None|guard let|if let|optional)/m.test(diff) && /^-/m.test(diff)
|
|
4524
|
-
},
|
|
4525
|
-
{
|
|
4526
|
-
name: "concurrency_fix",
|
|
4527
|
-
// `rate[-_. ]limit` — the original `rate.limit` used an UNESCAPED dot, so `.` matched any
|
|
4528
|
-
// character and the alternative fired on "corporate_limits". The class is now explicit
|
|
4529
|
-
// about which separators are intended.
|
|
4530
|
-
test: (diff) => /^\+.*\b(timeout|semaphore|lock|mutex|throttle|rate[-_. ]limit|max_conn)\b/im.test(diff)
|
|
4531
|
-
}
|
|
4532
|
-
// ⛔ `async_pattern_fix` AND `added_missing_import` WERE RETIRED HERE 2026-08-20.
|
|
4533
|
-
//
|
|
4534
|
-
// async_pattern_fix:
|
|
4535
|
-
// test: (diff) => /^\+.*(@MainActor|async with|asyncio\.timeout|\.await)/.test(diff)
|
|
4536
|
-
// && /^-/m.test(diff),
|
|
4537
|
-
// added_missing_import:
|
|
4538
|
-
// test: (diff) => /^\+.*(import|from.*import|require)/.test(diff)
|
|
4539
|
-
// && !/^-.*(import|from.*import|require)/m.test(diff),
|
|
4540
|
-
//
|
|
4541
|
-
// Both fire ZERO times across the last 250 commits — measured on the same run that produced
|
|
4542
|
-
// the boundary numbers above, over the 87 commits touching non-test source:
|
|
4543
|
-
//
|
|
4544
|
-
// async_pattern_fix 0 / 87
|
|
4545
|
-
// added_missing_import 0 / 87
|
|
4546
|
-
//
|
|
4547
|
-
// This is the identical finding that retired `method_name_correction` below, and it is
|
|
4548
|
-
// retired for the identical reason: a detector that never fires is not a detector, and it
|
|
4549
|
-
// still costs review attention and maintenance on every pass over this table (CR-71 in
|
|
4550
|
-
// spirit — a zero-caller chokepoint is a dead feature).
|
|
4551
|
-
//
|
|
4552
|
-
// WHY THEY CANNOT FIRE HERE, so the next author does not "fix" them back in: their tokens are
|
|
4553
|
-
// largely foreign to this codebase. `@MainActor` is Swift; `async with` is Python. Neither
|
|
4554
|
-
// appears in a TypeScript monorepo. `added_missing_import` requires added imports with NO
|
|
4555
|
-
// removed import anywhere in the same diff, which any edit touching an import block violates.
|
|
4556
|
-
// If this table is ever pointed at a Swift or Python corpus, re-measure before reinstating —
|
|
4557
|
-
// do not reinstate on the assumption that the language mix changed.
|
|
4558
|
-
//
|
|
4559
|
-
// Kept as comments rather than deleted so the measurement travels with the code, and so a
|
|
4560
|
-
// future author reaching for "detect an async-pattern fix" finds the number first.
|
|
4561
|
-
// (Same reasoning, and same publication constraint, as the block below: this file syncs to
|
|
4562
|
-
// the public mirror, so no internal path citations.)
|
|
4563
|
-
];
|
|
4564
|
-
function getSessionFlagPath(sessionId) {
|
|
4565
|
-
const dir = join6(tmpdir2(), "massu-auto-learning");
|
|
4566
|
-
if (!existsSync9(dir)) {
|
|
4567
|
-
mkdirSync7(dir, { recursive: true });
|
|
4568
|
-
}
|
|
4569
|
-
return join6(dir, `fixes-${sessionId.slice(0, 12)}.jsonl`);
|
|
4570
|
-
}
|
|
4571
|
-
function _stateDir() {
|
|
4572
|
-
const dir = join6(tmpdir2(), "massu-fix-detector-state");
|
|
4573
|
-
if (!existsSync9(dir)) {
|
|
4574
|
-
mkdirSync7(dir, { recursive: true });
|
|
4575
|
-
}
|
|
4576
|
-
return dir;
|
|
4577
|
-
}
|
|
4578
|
-
function _cwdHash(cwd) {
|
|
4579
|
-
let h = 5381;
|
|
4580
|
-
for (let i = 0; i < cwd.length; i++) {
|
|
4581
|
-
h = (h << 5) + h + cwd.charCodeAt(i) | 0;
|
|
4582
|
-
}
|
|
4583
|
-
return Math.abs(h).toString(36);
|
|
4584
|
-
}
|
|
4585
|
-
function isGitWorkTreeFast(cwd) {
|
|
4586
|
-
try {
|
|
4587
|
-
const cachePath = join6(_stateDir(), `worktree-${_cwdHash(cwd)}.json`);
|
|
4588
|
-
if (existsSync9(cachePath)) {
|
|
4589
|
-
const cached = JSON.parse(readFileSync6(cachePath, "utf-8"));
|
|
4590
|
-
const ageMs = Date.now() - cached.ts;
|
|
4591
|
-
if (ageMs < 36e5) return cached.isWorkTree;
|
|
4592
|
-
}
|
|
4593
|
-
let isWorkTree = false;
|
|
4594
|
-
try {
|
|
4595
|
-
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, timeout: 500, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
|
|
4596
|
-
isWorkTree = true;
|
|
4597
|
-
} catch {
|
|
4598
|
-
isWorkTree = false;
|
|
4599
|
-
}
|
|
4600
|
-
try {
|
|
4601
|
-
appendFileSync3(cachePath, "");
|
|
4602
|
-
const fs = __require("fs");
|
|
4603
|
-
fs.writeFileSync(cachePath, JSON.stringify({ isWorkTree, ts: Date.now() }));
|
|
4604
|
-
} catch {
|
|
4605
|
-
}
|
|
4606
|
-
return isWorkTree;
|
|
4607
|
-
} catch {
|
|
4608
|
-
return false;
|
|
4609
|
-
}
|
|
4610
|
-
}
|
|
4611
|
-
function _disabledFlagPath(sessionId) {
|
|
4612
|
-
return join6(_stateDir(), `disabled-${sessionId.slice(0, 12)}.flag`);
|
|
4613
|
-
}
|
|
4614
|
-
function isSessionAutoDisabled(sessionId) {
|
|
4615
|
-
try {
|
|
4616
|
-
return existsSync9(_disabledFlagPath(sessionId));
|
|
4617
|
-
} catch {
|
|
4618
|
-
return false;
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
function markSessionAutoDisabled(sessionId, elapsedMs) {
|
|
4622
|
-
try {
|
|
4623
|
-
const fs = __require("fs");
|
|
4624
|
-
fs.writeFileSync(_disabledFlagPath(sessionId), `disabled: previous git diff took ${elapsedMs}ms
|
|
4625
|
-
`);
|
|
4626
|
-
} catch {
|
|
4627
|
-
}
|
|
4628
|
-
}
|
|
4629
|
-
async function main() {
|
|
4630
|
-
try {
|
|
4631
|
-
const input = await readStdin();
|
|
4632
|
-
const hookInput = JSON.parse(input);
|
|
4633
|
-
const filePath = hookInput.tool_input?.file_path;
|
|
4634
|
-
if (!filePath || !existsSync9(filePath)) {
|
|
4635
|
-
process.exit(0);
|
|
4636
|
-
return;
|
|
4637
|
-
}
|
|
4638
|
-
if (!/\.(py|swift|ts|tsx|js|jsx|rs|go|rb|sh)$/.test(filePath)) {
|
|
4639
|
-
process.exit(0);
|
|
4640
|
-
return;
|
|
4641
|
-
}
|
|
4642
|
-
const config = getConfig();
|
|
4643
|
-
const incidentDir = config.autoLearning?.incidentDir ?? "docs/incidents";
|
|
4644
|
-
const memoryDir = config.autoLearning?.memoryDir ?? "memory";
|
|
4645
|
-
if (filePath.includes(incidentDir) || filePath.includes(memoryDir) || filePath.includes("MEMORY.md")) {
|
|
4646
|
-
process.exit(0);
|
|
4647
|
-
return;
|
|
4648
|
-
}
|
|
4649
|
-
if (isTestOrFixturePath(filePath)) {
|
|
4650
|
-
process.exit(0);
|
|
4651
|
-
return;
|
|
4652
|
-
}
|
|
4653
|
-
if (config.autoLearning?.enabled === false || config.autoLearning?.fixDetection?.enabled === false) {
|
|
4654
|
-
process.exit(0);
|
|
4655
|
-
return;
|
|
4656
|
-
}
|
|
4657
|
-
const root = getProjectRoot();
|
|
4658
|
-
if (!isGitWorkTreeFast(root) || isSessionAutoDisabled(hookInput.session_id)) {
|
|
4659
|
-
process.exit(0);
|
|
4660
|
-
return;
|
|
4661
|
-
}
|
|
4662
|
-
let diff = "";
|
|
4663
|
-
try {
|
|
4664
|
-
const startMs = Date.now();
|
|
4665
|
-
diff = execFileSync("git", ["diff", "--", filePath], { cwd: root, timeout: 3e3, encoding: "utf-8" });
|
|
4666
|
-
if (!diff) {
|
|
4667
|
-
diff = execFileSync("git", ["diff", "HEAD", "--", filePath], { cwd: root, timeout: 3e3, encoding: "utf-8" });
|
|
4668
|
-
}
|
|
4669
|
-
const elapsedMs = Date.now() - startMs;
|
|
4670
|
-
if (elapsedMs > 2e3) {
|
|
4671
|
-
markSessionAutoDisabled(hookInput.session_id, elapsedMs);
|
|
4672
|
-
}
|
|
4673
|
-
} catch {
|
|
4674
|
-
process.exit(0);
|
|
4675
|
-
return;
|
|
4676
|
-
}
|
|
4677
|
-
if (!diff) {
|
|
4678
|
-
process.exit(0);
|
|
4679
|
-
return;
|
|
4680
|
-
}
|
|
4681
|
-
const enabledSignals = new Set(config.autoLearning?.fixDetection?.signals ?? FIX_HEURISTICS.map((h) => h.name));
|
|
4682
|
-
const detected = [];
|
|
4683
|
-
for (const heuristic of FIX_HEURISTICS) {
|
|
4684
|
-
if (enabledSignals.has(heuristic.name) && heuristic.test(diff)) {
|
|
4685
|
-
detected.push(heuristic.name);
|
|
4686
|
-
}
|
|
4687
|
-
}
|
|
4688
|
-
if (detected.length === 0) {
|
|
4689
|
-
process.exit(0);
|
|
4690
|
-
return;
|
|
4691
|
-
}
|
|
4692
|
-
const signal = {
|
|
4693
|
-
file: filePath,
|
|
4694
|
-
signals: detected,
|
|
4695
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4696
|
-
};
|
|
4697
|
-
const flagPath = getSessionFlagPath(hookInput.session_id);
|
|
4698
|
-
appendFileSync3(flagPath, JSON.stringify(signal) + "\n");
|
|
4699
|
-
const lines = readFileSync6(flagPath, "utf-8").split("\n").filter(Boolean);
|
|
4700
|
-
if (lines.length === 1) {
|
|
4701
|
-
writeHookContext(
|
|
4702
|
-
HOOK_EVENT,
|
|
4703
|
-
`[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.`
|
|
4704
|
-
);
|
|
4705
|
-
}
|
|
4706
|
-
} catch (err) {
|
|
4707
|
-
recordHookFailure("fix-detector", err);
|
|
4708
|
-
}
|
|
4709
|
-
process.exit(0);
|
|
4710
|
-
}
|
|
4711
|
-
function readStdin() {
|
|
4712
|
-
return new Promise((resolve5) => {
|
|
4713
|
-
let data = "";
|
|
4714
|
-
process.stdin.setEncoding("utf-8");
|
|
4715
|
-
process.stdin.on("data", (chunk) => {
|
|
4716
|
-
data += chunk;
|
|
4717
|
-
});
|
|
4718
|
-
process.stdin.on("end", () => resolve5(data));
|
|
4719
|
-
setTimeout(() => resolve5(data), 3e3);
|
|
4720
|
-
});
|
|
4721
|
-
}
|
|
4722
|
-
if (isDirectInvocation(import.meta.url)) {
|
|
4723
|
-
main();
|
|
4724
|
-
}
|
|
4725
|
-
|
|
4726
4124
|
// src/hooks/classify-failure.ts
|
|
4727
|
-
|
|
4728
|
-
var HOOK_EVENT2 = "PostToolUse";
|
|
4125
|
+
var HOOK_EVENT = "PostToolUse";
|
|
4729
4126
|
var BUG_FIX_INDICATORS = [
|
|
4730
4127
|
/\b(catch|error|throw|fail|fix|bug|broken|missing|crash|wrong|typo|incorrect)\b/i
|
|
4731
4128
|
];
|
|
@@ -4733,7 +4130,7 @@ var CODE_EXTENSIONS = /\.(ts|tsx|js|jsx|py|swift|rs|go|rb|sh)$/;
|
|
|
4733
4130
|
function getDedupeMarkerPath(sessionId, filePath) {
|
|
4734
4131
|
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4735
4132
|
const hash = simpleHash(filePath);
|
|
4736
|
-
return
|
|
4133
|
+
return join6(tmpdir2(), `massu-classify-${day}-${sessionId.slice(0, 8)}-${hash}`);
|
|
4737
4134
|
}
|
|
4738
4135
|
function simpleHash(str) {
|
|
4739
4136
|
let h = 0;
|
|
@@ -4743,13 +4140,13 @@ function simpleHash(str) {
|
|
|
4743
4140
|
return Math.abs(h).toString(36);
|
|
4744
4141
|
}
|
|
4745
4142
|
function readFailureContextFiles() {
|
|
4746
|
-
const dir =
|
|
4143
|
+
const dir = tmpdir2();
|
|
4747
4144
|
let context = "";
|
|
4748
4145
|
try {
|
|
4749
4146
|
const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
|
|
4750
4147
|
for (const file of files) {
|
|
4751
4148
|
try {
|
|
4752
|
-
context += " " +
|
|
4149
|
+
context += " " + readFileSync6(join6(dir, file), "utf-8");
|
|
4753
4150
|
} catch {
|
|
4754
4151
|
}
|
|
4755
4152
|
}
|
|
@@ -4758,21 +4155,21 @@ function readFailureContextFiles() {
|
|
|
4758
4155
|
return context.trim();
|
|
4759
4156
|
}
|
|
4760
4157
|
function cleanupFailureContextFiles() {
|
|
4761
|
-
const dir =
|
|
4158
|
+
const dir = tmpdir2();
|
|
4762
4159
|
try {
|
|
4763
4160
|
const files = readdirSync2(dir).filter((f) => f.startsWith("massu-failure-context-"));
|
|
4764
4161
|
for (const file of files) {
|
|
4765
4162
|
try {
|
|
4766
|
-
unlinkSync2(
|
|
4163
|
+
unlinkSync2(join6(dir, file));
|
|
4767
4164
|
} catch {
|
|
4768
4165
|
}
|
|
4769
4166
|
}
|
|
4770
4167
|
} catch {
|
|
4771
4168
|
}
|
|
4772
4169
|
}
|
|
4773
|
-
async function
|
|
4170
|
+
async function main() {
|
|
4774
4171
|
try {
|
|
4775
|
-
const input = await
|
|
4172
|
+
const input = await readStdin();
|
|
4776
4173
|
const hookInput = JSON.parse(input);
|
|
4777
4174
|
const filePath = hookInput.tool_input?.file_path;
|
|
4778
4175
|
if (!filePath) {
|
|
@@ -4829,7 +4226,7 @@ async function main2() {
|
|
|
4829
4226
|
return;
|
|
4830
4227
|
}
|
|
4831
4228
|
const dedupeMarker = getDedupeMarkerPath(hookInput.session_id, filePath);
|
|
4832
|
-
if (
|
|
4229
|
+
if (existsSync9(dedupeMarker)) {
|
|
4833
4230
|
process.exit(0);
|
|
4834
4231
|
return;
|
|
4835
4232
|
}
|
|
@@ -4871,7 +4268,7 @@ function outputKnownPattern(match) {
|
|
|
4871
4268
|
lines.push(` Covered by: ${match.rules.join(", ")}`);
|
|
4872
4269
|
}
|
|
4873
4270
|
lines.push(" No new rules needed. Reference existing incident if logging.");
|
|
4874
|
-
writeHookContext(
|
|
4271
|
+
writeHookContext(HOOK_EVENT, lines.join("\n"));
|
|
4875
4272
|
}
|
|
4876
4273
|
function outputSimilarPattern(match) {
|
|
4877
4274
|
const lines = [];
|
|
@@ -4881,7 +4278,7 @@ function outputSimilarPattern(match) {
|
|
|
4881
4278
|
lines.push(` Check if existing rules cover this case: ${match.rules.join(", ")}`);
|
|
4882
4279
|
}
|
|
4883
4280
|
lines.push(" If genuinely new: create incident + prevention rule + enforcement.");
|
|
4884
|
-
writeHookContext(
|
|
4281
|
+
writeHookContext(HOOK_EVENT, lines.join("\n"));
|
|
4885
4282
|
}
|
|
4886
4283
|
function outputNewPattern(fileName, match) {
|
|
4887
4284
|
const lines = [];
|
|
@@ -4895,9 +4292,9 @@ function outputNewPattern(fileName, match) {
|
|
|
4895
4292
|
lines.push(" 1. INCIDENT REPORT");
|
|
4896
4293
|
lines.push(" 2. PREVENTION RULE (if new failure pattern)");
|
|
4897
4294
|
lines.push(" 3. ENFORCEMENT (hook or static check)");
|
|
4898
|
-
writeHookContext(
|
|
4295
|
+
writeHookContext(HOOK_EVENT, lines.join("\n"));
|
|
4899
4296
|
}
|
|
4900
|
-
function
|
|
4297
|
+
function readStdin() {
|
|
4901
4298
|
return new Promise((resolve5) => {
|
|
4902
4299
|
let data = "";
|
|
4903
4300
|
process.stdin.setEncoding("utf-8");
|
|
@@ -4909,5 +4306,5 @@ function readStdin2() {
|
|
|
4909
4306
|
});
|
|
4910
4307
|
}
|
|
4911
4308
|
if (isDirectInvocation(import.meta.url)) {
|
|
4912
|
-
|
|
4309
|
+
main();
|
|
4913
4310
|
}
|