@massu/core 2.5.0 → 2.5.2

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.
@@ -697,6 +697,13 @@ var init_config = __esm({
697
697
  });
698
698
  LanguageFrameworkEntrySchema = z2.object({
699
699
  framework: z2.string().optional(),
700
+ // `source_dirs` is emitted by `init` (commands/init.ts:417) and existence-checked
701
+ // by config validation (init.ts:868-876), but until 2026-08-14 it survived only via
702
+ // `.passthrough()` — declared in the file, untyped in the schema. `lib/source-layout.ts`
703
+ // derives the index builders' candidate set from it, so it is typed here rather than
704
+ // read as an unknown: a consumer that has to shape-check its own SoT is one branch away
705
+ // from silently contributing `undefined` to a path predicate.
706
+ source_dirs: z2.array(z2.string()).optional(),
700
707
  test_framework: z2.string().optional(),
701
708
  test: z2.string().optional(),
702
709
  runtime: z2.string().optional(),
@@ -1339,6 +1346,16 @@ var init_memory_vector = __esm({
1339
1346
  }
1340
1347
  });
1341
1348
 
1349
+ // src/lib/timestamps.ts
1350
+ function nowIso() {
1351
+ return (/* @__PURE__ */ new Date()).toISOString();
1352
+ }
1353
+ var init_timestamps = __esm({
1354
+ "src/lib/timestamps.ts"() {
1355
+ "use strict";
1356
+ }
1357
+ });
1358
+
1342
1359
  // src/db-backup.ts
1343
1360
  import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync, statSync, unlinkSync, copyFileSync, rmSync as rmSync4 } from "fs";
1344
1361
  import { resolve as resolve3, join as join3, basename, dirname as dirname4 } from "path";
@@ -3599,8 +3616,8 @@ function linkSessionToTask(db, sessionId, taskId) {
3599
3616
  }
3600
3617
  function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantResponse, toolCallsJson, toolCallCount, promptTokens, responseTokens) {
3601
3618
  const result = db.prepare(`
3602
- INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens)
3603
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3619
+ INSERT INTO conversation_turns (session_id, turn_number, user_prompt, assistant_response, tool_calls_json, tool_call_count, prompt_tokens, response_tokens, created_at)
3620
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3604
3621
  `).run(
3605
3622
  sessionId,
3606
3623
  turnNumber,
@@ -3609,14 +3626,15 @@ function addConversationTurn(db, sessionId, turnNumber, userPrompt, assistantRes
3609
3626
  toolCallsJson,
3610
3627
  toolCallCount,
3611
3628
  promptTokens,
3612
- responseTokens
3629
+ responseTokens,
3630
+ nowIso()
3613
3631
  );
3614
3632
  return Number(result.lastInsertRowid);
3615
3633
  }
3616
3634
  function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, inputSize, outputSize, success, filesInvolved) {
3617
3635
  db.prepare(`
3618
- INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved)
3619
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
3636
+ INSERT INTO tool_call_details (session_id, turn_number, tool_name, tool_input_summary, tool_input_size, tool_output_size, tool_success, files_involved, created_at)
3637
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
3620
3638
  `).run(
3621
3639
  sessionId,
3622
3640
  turnNumber,
@@ -3625,7 +3643,8 @@ function addToolCallDetail(db, sessionId, turnNumber, toolName, inputSummary, in
3625
3643
  inputSize,
3626
3644
  outputSize,
3627
3645
  success ? 1 : 0,
3628
- filesInvolved ? JSON.stringify(filesInvolved) : null
3646
+ filesInvolved ? JSON.stringify(filesInvolved) : null,
3647
+ nowIso()
3629
3648
  );
3630
3649
  }
3631
3650
  function getLastProcessedLine(db, sessionId) {
@@ -3884,6 +3903,7 @@ var init_memory_db = __esm({
3884
3903
  init_db_driver();
3885
3904
  init_config();
3886
3905
  init_memory_vector();
3906
+ init_timestamps();
3887
3907
  init_db_backup();
3888
3908
  init_rule_delivery();
3889
3909
  init_rule_candidate_store();
@@ -3975,27 +3995,256 @@ var init_hook_failure_signal = __esm({
3975
3995
  // src/hooks/auto-learning-pipeline.ts
3976
3996
  init_config();
3977
3997
  import { execFileSync } from "child_process";
3978
- import { existsSync as existsSync9, readFileSync as readFileSync6, unlinkSync as unlinkSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3998
+ import { existsSync as existsSync10, readFileSync as readFileSync7, unlinkSync as unlinkSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
3979
3999
  import { tmpdir as tmpdir2 } from "os";
3980
4000
  import { join as join6 } from "path";
3981
4001
 
3982
4002
  // src/hooks/lib/write-hook-message.ts
3983
- function writeHookMessage(message) {
3984
- process.stdout.write(JSON.stringify({ message }) + "\n");
4003
+ var HOOK_EVENTS = [
4004
+ "UserPromptSubmit",
4005
+ "UserPromptExpansion",
4006
+ "SessionStart",
4007
+ "PreToolUse",
4008
+ "PostToolUse",
4009
+ "Stop",
4010
+ "SubagentStop",
4011
+ "PreCompact"
4012
+ ];
4013
+ var ADDITIONAL_CONTEXT_MAX_CHARS = 1e4;
4014
+ function isHookEvent(value) {
4015
+ return typeof value === "string" && HOOK_EVENTS.includes(value);
4016
+ }
4017
+ function writeHookContext(hookEventName, message) {
4018
+ if (!isHookEvent(hookEventName)) {
4019
+ process.stderr.write(
4020
+ `[massu] writeHookContext: refusing to emit \u2014 unrecognised hook event ${JSON.stringify(hookEventName)}. Known: ${HOOK_EVENTS.join(", ")}.
4021
+ `
4022
+ );
4023
+ return;
4024
+ }
4025
+ if (typeof message !== "string" || message.length === 0) return;
4026
+ let additionalContext = message;
4027
+ if (additionalContext.length > ADDITIONAL_CONTEXT_MAX_CHARS) {
4028
+ const marker = `
4029
+ [massu] \u2026truncated at ${ADDITIONAL_CONTEXT_MAX_CHARS} chars (spec cap).`;
4030
+ additionalContext = additionalContext.slice(0, ADDITIONAL_CONTEXT_MAX_CHARS - marker.length) + marker;
4031
+ }
4032
+ process.stdout.write(
4033
+ JSON.stringify({ hookSpecificOutput: { hookEventName, additionalContext } }) + "\n"
4034
+ );
3985
4035
  }
3986
4036
 
3987
4037
  // src/hooks/auto-learning-pipeline.ts
3988
4038
  init_hook_failure_signal();
4039
+
4040
+ // src/hooks/lib/is-direct-invocation.ts
4041
+ import { pathToFileURL } from "url";
4042
+ function isDirectInvocation(moduleUrl) {
4043
+ const entry = process.argv[1];
4044
+ if (!entry) return false;
4045
+ try {
4046
+ return moduleUrl === pathToFileURL(entry).href;
4047
+ } catch {
4048
+ return false;
4049
+ }
4050
+ }
4051
+
4052
+ // src/hooks/lib/session-touched-files.ts
4053
+ import { readFileSync as readFileSync6, existsSync as existsSync9, realpathSync } from "fs";
4054
+ import { relative, isAbsolute, resolve as resolve5 } from "path";
4055
+ var FILE_WRITING_TOOLS = Object.freeze({
4056
+ Edit: ["file_path"],
4057
+ Write: ["file_path"],
4058
+ MultiEdit: ["file_path"],
4059
+ NotebookEdit: ["notebook_path", "file_path"]
4060
+ });
4061
+ function canonical(p) {
4062
+ try {
4063
+ return realpathSync(p);
4064
+ } catch {
4065
+ return p;
4066
+ }
4067
+ }
4068
+ function toRepoRelative(root, filePath) {
4069
+ if (typeof filePath !== "string" || filePath === "") return null;
4070
+ const absRaw = isAbsolute(filePath) ? filePath : resolve5(root, filePath);
4071
+ const canonicalRoot = canonical(root);
4072
+ const abs = resolve5(canonical(resolve5(absRaw, "..")), absRaw.split("/").pop() ?? "");
4073
+ const rel = relative(canonicalRoot, abs).split("\\").join("/");
4074
+ if (rel === "" || rel.startsWith("../")) return null;
4075
+ return rel;
4076
+ }
4077
+ function transcriptTouchedFiles(root, transcriptPath) {
4078
+ let raw;
4079
+ try {
4080
+ raw = readFileSync6(transcriptPath, "utf-8");
4081
+ } catch {
4082
+ return null;
4083
+ }
4084
+ const out = /* @__PURE__ */ new Set();
4085
+ for (const line of raw.split("\n")) {
4086
+ if (!line.trim()) continue;
4087
+ let entry;
4088
+ try {
4089
+ entry = JSON.parse(line);
4090
+ } catch {
4091
+ continue;
4092
+ }
4093
+ const message = entry.message;
4094
+ const content = message?.content;
4095
+ if (!Array.isArray(content)) continue;
4096
+ for (const block of content) {
4097
+ if (!block || typeof block !== "object") continue;
4098
+ const b = block;
4099
+ if (b.type !== "tool_use") continue;
4100
+ const keys = b.name ? FILE_WRITING_TOOLS[b.name] : void 0;
4101
+ if (!keys || !b.input) continue;
4102
+ for (const key of keys) {
4103
+ const rel = toRepoRelative(root, b.input[key]);
4104
+ if (rel) out.add(rel);
4105
+ }
4106
+ }
4107
+ }
4108
+ return out;
4109
+ }
4110
+ function flagFileTouchedFiles(root, flagPath) {
4111
+ if (!existsSync9(flagPath)) return null;
4112
+ let raw;
4113
+ try {
4114
+ raw = readFileSync6(flagPath, "utf-8");
4115
+ } catch {
4116
+ return null;
4117
+ }
4118
+ const out = /* @__PURE__ */ new Set();
4119
+ for (const line of raw.split("\n")) {
4120
+ if (!line.trim()) continue;
4121
+ try {
4122
+ const rel = toRepoRelative(root, JSON.parse(line).file ?? "");
4123
+ if (rel) out.add(rel);
4124
+ } catch {
4125
+ continue;
4126
+ }
4127
+ }
4128
+ return out;
4129
+ }
4130
+ function sessionTouchedFiles(opts) {
4131
+ const sources = [];
4132
+ if (opts.transcriptPath) sources.push(transcriptTouchedFiles(opts.root, opts.transcriptPath));
4133
+ if (opts.flagPath) sources.push(flagFileTouchedFiles(opts.root, opts.flagPath));
4134
+ const readable = sources.filter((s) => s !== null);
4135
+ if (readable.length === 0) return null;
4136
+ const union = /* @__PURE__ */ new Set();
4137
+ for (const s of readable) for (const f of s) union.add(f);
4138
+ return union;
4139
+ }
4140
+
4141
+ // src/hooks/lib/git-safe-env.ts
4142
+ var GIT_ENV_LEAKS = [
4143
+ "GIT_DIR",
4144
+ "GIT_WORK_TREE",
4145
+ "GIT_INDEX_FILE",
4146
+ "GIT_OBJECT_DIRECTORY",
4147
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
4148
+ "GIT_COMMON_DIR",
4149
+ "GIT_PREFIX"
4150
+ ];
4151
+ function gitSafeEnv(extra = {}, source = process.env) {
4152
+ const e = { ...source, ...extra };
4153
+ for (const k of GIT_ENV_LEAKS) delete e[k];
4154
+ return e;
4155
+ }
4156
+
4157
+ // src/hooks/auto-learning-pipeline.ts
4158
+ var HOOK_EVENT = "Stop";
3989
4159
  var MAX_FULL_DIFF_BYTES = 2 * 1024 * 1024;
4160
+ var DOC_EXCLUDE_PATHSPEC = [
4161
+ ":(exclude)*.md",
4162
+ ":(exclude)*.markdown",
4163
+ ":(exclude)*.mdx",
4164
+ ":(exclude)*.rst",
4165
+ ":(exclude)*.adoc",
4166
+ ":(exclude)*.txt",
4167
+ ":(exclude)docs/**",
4168
+ // massu's OWN runtime artifacts: agent-result JSONL, workflow logs, rule candidates.
4169
+ // Audit findings quote source lines verbatim, so leaving these in scope re-creates the
4170
+ // prose-as-code false positive with the hook's own output as the input.
4171
+ ":(exclude).massu/**"
4172
+ ];
4173
+ var CODE_ONLY_PATHSPEC = ["--", ".", ...DOC_EXCLUDE_PATHSPEC];
4174
+ var MAX_SESSION_PATHSPEC_FILES = 500;
4175
+ var FIX_PATTERN_RE = /^\+.*(?:\b(?:try|except|catch|guard|throw|raise|assert|validate)\b|\bif\b.*\b(?:null|nil|None|undefined)\b)/gm;
4176
+ var REMOVED_BROKEN_RE = /^-.*\b(?:bug|broken|crash|wrong|incorrect|typo|fail|error|miss|stale)\b/gm;
3990
4177
  function getSessionFlagPath(sessionId) {
3991
4178
  return join6(tmpdir2(), "massu-auto-learning", `fixes-${sessionId.slice(0, 12)}.jsonl`);
3992
4179
  }
4180
+ var RECENT_INCIDENT_WINDOW_MS = 12 * 60 * 60 * 1e3;
4181
+ function recentIncidentFiles(dir, nowMs = Date.now()) {
4182
+ try {
4183
+ return readdirSync2(dir).filter((f) => f.endsWith(".md")).filter((f) => {
4184
+ try {
4185
+ return nowMs - statSync2(join6(dir, f)).mtimeMs <= RECENT_INCIDENT_WINDOW_MS;
4186
+ } catch {
4187
+ return false;
4188
+ }
4189
+ }).sort();
4190
+ } catch {
4191
+ return [];
4192
+ }
4193
+ }
4194
+ function scanUncommittedForFix(opts) {
4195
+ const git = opts.git ?? ((args, t, m) => execFileSync("git", [...args], {
4196
+ cwd: opts.root,
4197
+ // `cwd` does NOT scope git: GIT_DIR in the environment outranks it, and a leaked one
4198
+ // would make this read a different repository while looking correct. This hook runs
4199
+ // at Stop and is never reachable from a git hook, so nothing here depends on the
4200
+ // index a commit-time hook is handed — the variables are removed rather than asserted.
4201
+ env: gitSafeEnv(),
4202
+ timeout: t,
4203
+ encoding: "utf-8",
4204
+ maxBuffer: m
4205
+ }));
4206
+ if (opts.sessionTouched === null) {
4207
+ return { uncommittedFix: false, reason: "unattributable", filesScanned: 0 };
4208
+ }
4209
+ if (opts.sessionTouched.size === 0) {
4210
+ return { uncommittedFix: false, reason: "not-this-session", filesScanned: 0 };
4211
+ }
4212
+ try {
4213
+ const nameOnly = git(["diff", "--name-only", ...CODE_ONLY_PATHSPEC], 3e3, 1024 * 1024);
4214
+ const changed = nameOnly.split("\n").map((s) => s.trim()).filter(Boolean);
4215
+ if (changed.length === 0) {
4216
+ return { uncommittedFix: false, reason: "no-changes", filesScanned: 0 };
4217
+ }
4218
+ const mine = changed.filter((f) => opts.sessionTouched.has(f));
4219
+ if (mine.length === 0) {
4220
+ return { uncommittedFix: false, reason: "not-this-session", filesScanned: 0 };
4221
+ }
4222
+ const scoped = mine.length <= MAX_SESSION_PATHSPEC_FILES ? ["--", ...mine, ...DOC_EXCLUDE_PATHSPEC] : [...CODE_ONLY_PATHSPEC];
4223
+ const shortstat = git(["diff", "--shortstat", ...scoped], 2e3, 64 * 1024);
4224
+ const insertions = parseInt(shortstat.match(/(\d+) insertion/)?.[1] ?? "0", 10);
4225
+ const deletions = parseInt(shortstat.match(/(\d+) deletion/)?.[1] ?? "0", 10);
4226
+ if ((insertions + deletions) * 80 > MAX_FULL_DIFF_BYTES) {
4227
+ return { uncommittedFix: false, reason: "over-cap", filesScanned: mine.length };
4228
+ }
4229
+ const fullDiff = git(["diff", ...scoped], 5e3, MAX_FULL_DIFF_BYTES);
4230
+ const fixPatterns = (fullDiff.match(FIX_PATTERN_RE) || []).length;
4231
+ const removedBroken = (fullDiff.match(REMOVED_BROKEN_RE) || []).length;
4232
+ const hit = fixPatterns > 3 || removedBroken > 1;
4233
+ return {
4234
+ uncommittedFix: hit,
4235
+ reason: hit ? "fix-detected" : "no-signal",
4236
+ filesScanned: mine.length
4237
+ };
4238
+ } catch {
4239
+ return { uncommittedFix: false, reason: "git-unavailable", filesScanned: 0 };
4240
+ }
4241
+ }
3993
4242
  async function main() {
3994
4243
  try {
3995
4244
  const input = await readStdin();
3996
4245
  const hookInput = JSON.parse(input);
3997
4246
  const config = getConfig();
3998
- if (config.autoLearning?.enabled === false) {
4247
+ if (config.autoLearning?.enabled === false || config.autoLearning?.fixDetection?.enabled === false) {
3999
4248
  process.exit(0);
4000
4249
  return;
4001
4250
  }
@@ -4005,46 +4254,20 @@ async function main() {
4005
4254
  const autoLearn = config.autoLearning;
4006
4255
  const flagPath = getSessionFlagPath(hookInput.session_id);
4007
4256
  let sessionFixes = [];
4008
- if (existsSync9(flagPath)) {
4257
+ if (existsSync10(flagPath)) {
4009
4258
  try {
4010
- sessionFixes = readFileSync6(flagPath, "utf-8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
4259
+ sessionFixes = readFileSync7(flagPath, "utf-8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
4011
4260
  } catch {
4012
4261
  }
4013
4262
  }
4014
- let uncommittedFix = false;
4015
- try {
4016
- const nameOnly = execFileSync("git", ["diff", "--name-only"], {
4017
- cwd: root,
4018
- timeout: 3e3,
4019
- encoding: "utf-8",
4020
- maxBuffer: 1024 * 1024
4021
- });
4022
- if (nameOnly.trim()) {
4023
- const shortstat = execFileSync("git", ["diff", "--shortstat"], {
4024
- cwd: root,
4025
- timeout: 2e3,
4026
- encoding: "utf-8",
4027
- maxBuffer: 64 * 1024
4028
- });
4029
- const insertions = parseInt(shortstat.match(/(\d+) insertion/)?.[1] ?? "0", 10);
4030
- const deletions = parseInt(shortstat.match(/(\d+) deletion/)?.[1] ?? "0", 10);
4031
- const estimatedBytes = (insertions + deletions) * 80;
4032
- if (estimatedBytes <= MAX_FULL_DIFF_BYTES) {
4033
- const fullDiff = execFileSync("git", ["diff"], {
4034
- cwd: root,
4035
- timeout: 5e3,
4036
- encoding: "utf-8",
4037
- maxBuffer: MAX_FULL_DIFF_BYTES
4038
- });
4039
- const fixPatterns = (fullDiff.match(/^\+.*(try|except|catch|guard|throw|raise|assert|validate|if.*null|if.*nil|if.*None|if.*undefined)/gm) || []).length;
4040
- const removedBroken = (fullDiff.match(/^-.*(bug|broken|crash|wrong|incorrect|typo|fail|error|miss|stale)/gm) || []).length;
4041
- if (fixPatterns > 3 || removedBroken > 1) {
4042
- uncommittedFix = true;
4043
- }
4044
- }
4045
- }
4046
- } catch {
4047
- }
4263
+ const uncommittedFix = scanUncommittedForFix({
4264
+ root,
4265
+ sessionTouched: sessionTouchedFiles({
4266
+ root,
4267
+ transcriptPath: hookInput.transcript_path,
4268
+ flagPath
4269
+ })
4270
+ }).uncommittedFix;
4048
4271
  if (sessionFixes.length === 0 && !uncommittedFix) {
4049
4272
  cleanup(flagPath);
4050
4273
  process.exit(0);
@@ -4073,10 +4296,18 @@ async function main() {
4073
4296
  lines.push("");
4074
4297
  lines.push(" Additional uncommitted fix patterns detected in git diff.");
4075
4298
  }
4299
+ const todaysIncidents = recentIncidentFiles(join6(root, incidentDir));
4076
4300
  lines.push("");
4077
- lines.push(" Complete these steps before this session ends:");
4301
+ if (todaysIncidents.length > 0) {
4302
+ lines.push(" ALREADY SATISFIED TODAY (verify these cover the fixes above):");
4303
+ for (const f of todaysIncidents.slice(0, 5)) lines.push(` - ${incidentDir}/${f}`);
4304
+ lines.push("");
4305
+ lines.push(" If they do, nothing further is required. Otherwise:");
4306
+ } else {
4307
+ lines.push(" Complete these steps before this session ends:");
4308
+ }
4078
4309
  lines.push("");
4079
- if (autoLearn?.pipeline?.requireIncidentReport !== false) {
4310
+ if (autoLearn?.pipeline?.requireIncidentReport !== false && todaysIncidents.length === 0) {
4080
4311
  lines.push(" STEP 1: INCIDENT REPORT");
4081
4312
  lines.push(` For each distinct bug fixed, create: ${incidentDir}/YYYY-MM-DD-<slug>.md`);
4082
4313
  lines.push(" Include: Date, Severity, Symptoms, Root Cause, Fix, Files Changed, Prevention Rules");
@@ -4104,7 +4335,7 @@ async function main() {
4104
4335
  lines.push("");
4105
4336
  lines.push("============================================================================");
4106
4337
  lines.push("");
4107
- writeHookMessage(lines.join("\n"));
4338
+ writeHookContext(HOOK_EVENT, lines.join("\n"));
4108
4339
  cleanup(flagPath);
4109
4340
  } catch (err) {
4110
4341
  recordHookFailure("auto-learning-pipeline", err);
@@ -4113,9 +4344,9 @@ async function main() {
4113
4344
  }
4114
4345
  function cleanup(flagPath) {
4115
4346
  try {
4116
- if (existsSync9(flagPath)) unlinkSync2(flagPath);
4347
+ if (existsSync10(flagPath)) unlinkSync2(flagPath);
4117
4348
  const dir = join6(tmpdir2(), "massu-auto-learning");
4118
- if (existsSync9(dir)) {
4349
+ if (existsSync10(dir)) {
4119
4350
  const now = Date.now();
4120
4351
  for (const file of readdirSync2(dir)) {
4121
4352
  const fullPath = join6(dir, file);
@@ -4132,17 +4363,27 @@ function cleanup(flagPath) {
4132
4363
  }
4133
4364
  }
4134
4365
  function readStdin() {
4135
- return new Promise((resolve5) => {
4366
+ return new Promise((resolve6) => {
4136
4367
  let data = "";
4137
4368
  process.stdin.setEncoding("utf-8");
4138
4369
  process.stdin.on("data", (chunk) => {
4139
4370
  data += chunk;
4140
4371
  });
4141
- process.stdin.on("end", () => resolve5(data));
4142
- setTimeout(() => resolve5(data), 5e3);
4372
+ process.stdin.on("end", () => resolve6(data));
4373
+ setTimeout(() => resolve6(data), 5e3);
4143
4374
  });
4144
4375
  }
4145
- main();
4376
+ if (isDirectInvocation(import.meta.url)) {
4377
+ main();
4378
+ }
4146
4379
  export {
4147
- MAX_FULL_DIFF_BYTES
4380
+ CODE_ONLY_PATHSPEC,
4381
+ DOC_EXCLUDE_PATHSPEC,
4382
+ FIX_PATTERN_RE,
4383
+ MAX_FULL_DIFF_BYTES,
4384
+ MAX_SESSION_PATHSPEC_FILES,
4385
+ RECENT_INCIDENT_WINDOW_MS,
4386
+ REMOVED_BROKEN_RE,
4387
+ recentIncidentFiles,
4388
+ scanUncommittedForFix
4148
4389
  };