mobbdev 1.2.57 → 1.2.59

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.
@@ -56,32 +56,22 @@ declare const PromptItemZ: z.ZodObject<{
56
56
  name: string;
57
57
  parameters: string;
58
58
  result: string;
59
- accepted?: boolean | undefined;
60
59
  rawArguments?: string | undefined;
60
+ accepted?: boolean | undefined;
61
61
  mcpServer?: string | undefined;
62
62
  mcpToolName?: string | undefined;
63
63
  }, {
64
64
  name: string;
65
65
  parameters: string;
66
66
  result: string;
67
- accepted?: boolean | undefined;
68
67
  rawArguments?: string | undefined;
68
+ accepted?: boolean | undefined;
69
69
  mcpServer?: string | undefined;
70
70
  mcpToolName?: string | undefined;
71
71
  }>>;
72
72
  }, "strip", z.ZodTypeAny, {
73
73
  type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING" | "MCP_TOOL_CALL";
74
- tool?: {
75
- name: string;
76
- parameters: string;
77
- result: string;
78
- accepted?: boolean | undefined;
79
- rawArguments?: string | undefined;
80
- mcpServer?: string | undefined;
81
- mcpToolName?: string | undefined;
82
- } | undefined;
83
74
  date?: Date | undefined;
84
- text?: string | undefined;
85
75
  attachedFiles?: {
86
76
  relativePath: string;
87
77
  startLine?: number | undefined;
@@ -90,19 +80,19 @@ declare const PromptItemZ: z.ZodObject<{
90
80
  inputCount: number;
91
81
  outputCount: number;
92
82
  } | undefined;
93
- }, {
94
- type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING" | "MCP_TOOL_CALL";
83
+ text?: string | undefined;
95
84
  tool?: {
96
85
  name: string;
97
86
  parameters: string;
98
87
  result: string;
99
- accepted?: boolean | undefined;
100
88
  rawArguments?: string | undefined;
89
+ accepted?: boolean | undefined;
101
90
  mcpServer?: string | undefined;
102
91
  mcpToolName?: string | undefined;
103
92
  } | undefined;
93
+ }, {
94
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING" | "MCP_TOOL_CALL";
104
95
  date?: Date | undefined;
105
- text?: string | undefined;
106
96
  attachedFiles?: {
107
97
  relativePath: string;
108
98
  startLine?: number | undefined;
@@ -111,6 +101,16 @@ declare const PromptItemZ: z.ZodObject<{
111
101
  inputCount: number;
112
102
  outputCount: number;
113
103
  } | undefined;
104
+ text?: string | undefined;
105
+ tool?: {
106
+ name: string;
107
+ parameters: string;
108
+ result: string;
109
+ rawArguments?: string | undefined;
110
+ accepted?: boolean | undefined;
111
+ mcpServer?: string | undefined;
112
+ mcpToolName?: string | undefined;
113
+ } | undefined;
114
114
  }>;
115
115
  type PromptItem = z.infer<typeof PromptItemZ>;
116
116
  declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
@@ -149,32 +149,22 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
149
149
  name: string;
150
150
  parameters: string;
151
151
  result: string;
152
- accepted?: boolean | undefined;
153
152
  rawArguments?: string | undefined;
153
+ accepted?: boolean | undefined;
154
154
  mcpServer?: string | undefined;
155
155
  mcpToolName?: string | undefined;
156
156
  }, {
157
157
  name: string;
158
158
  parameters: string;
159
159
  result: string;
160
- accepted?: boolean | undefined;
161
160
  rawArguments?: string | undefined;
161
+ accepted?: boolean | undefined;
162
162
  mcpServer?: string | undefined;
163
163
  mcpToolName?: string | undefined;
164
164
  }>>;
165
165
  }, "strip", z.ZodTypeAny, {
166
166
  type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING" | "MCP_TOOL_CALL";
167
- tool?: {
168
- name: string;
169
- parameters: string;
170
- result: string;
171
- accepted?: boolean | undefined;
172
- rawArguments?: string | undefined;
173
- mcpServer?: string | undefined;
174
- mcpToolName?: string | undefined;
175
- } | undefined;
176
167
  date?: Date | undefined;
177
- text?: string | undefined;
178
168
  attachedFiles?: {
179
169
  relativePath: string;
180
170
  startLine?: number | undefined;
@@ -183,19 +173,19 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
183
173
  inputCount: number;
184
174
  outputCount: number;
185
175
  } | undefined;
186
- }, {
187
- type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING" | "MCP_TOOL_CALL";
176
+ text?: string | undefined;
188
177
  tool?: {
189
178
  name: string;
190
179
  parameters: string;
191
180
  result: string;
192
- accepted?: boolean | undefined;
193
181
  rawArguments?: string | undefined;
182
+ accepted?: boolean | undefined;
194
183
  mcpServer?: string | undefined;
195
184
  mcpToolName?: string | undefined;
196
185
  } | undefined;
186
+ }, {
187
+ type: "USER_PROMPT" | "AI_RESPONSE" | "TOOL_EXECUTION" | "AI_THINKING" | "MCP_TOOL_CALL";
197
188
  date?: Date | undefined;
198
- text?: string | undefined;
199
189
  attachedFiles?: {
200
190
  relativePath: string;
201
191
  startLine?: number | undefined;
@@ -204,6 +194,16 @@ declare const PromptItemArrayZ: z.ZodArray<z.ZodObject<{
204
194
  inputCount: number;
205
195
  outputCount: number;
206
196
  } | undefined;
197
+ text?: string | undefined;
198
+ tool?: {
199
+ name: string;
200
+ parameters: string;
201
+ result: string;
202
+ rawArguments?: string | undefined;
203
+ accepted?: boolean | undefined;
204
+ mcpServer?: string | undefined;
205
+ mcpToolName?: string | undefined;
206
+ } | undefined;
207
207
  }>, "many">;
208
208
  type PromptItemArray = z.infer<typeof PromptItemArrayZ>;
209
209
  /**
@@ -2722,7 +2722,7 @@ function parseCommitLine(line) {
2722
2722
  message: parts[6]
2723
2723
  };
2724
2724
  }
2725
- var PrepareGitBlameMessageZ, PrepareGitBlameResponseMessageZ, CommitMetadataZ, LineToCommitMapZ, CommitMetadataMapZ, BlameInfoZ, LineRangeZ, PrContextZ, PrepareCommitBlameMessageZ, BlameLineInfoZ, FileBlameDataZ, ChunkFetchResultZ, FileBlameResponseEntryZ, CommitBlameDataZ, CommitInfoZ, GitIdentityZ, COMMIT_LOG_FORMAT, CommitDataZ, PrDiffDataZ, PrStatsZ, CommitsManifestZ, BlameLineEntryZ, BlameLinesDataZ, PrepareCommitBlameResponseMessageZ;
2725
+ var PrepareGitBlameMessageZ, PrepareGitBlameResponseMessageZ, CommitMetadataZ, LineToCommitMapZ, CommitMetadataMapZ, BlameInfoZ, LineRangeZ, PrContextZ, PrepareCommitBlameMessageZ, BlameLineInfoZ, FileBlameDataZ, ChunkFetchResultZ, FileBlameResponseEntryZ, CommitBlameDataZ, CommitInfoZ, GitIdentityZ, COMMIT_LOG_FORMAT, CommitDataZ, PrDiffDataZ, PrStatsZ, CommitsManifestZ, BlameLineEntryZ, BlameLinesDataZ, PrepareCommitBlameResponseMessageZ, VulnerabilityAttributionMessageZ;
2726
2726
  var init_gitBlameTypes = __esm({
2727
2727
  "src/utils/blame/gitBlameTypes.ts"() {
2728
2728
  "use strict";
@@ -2897,6 +2897,11 @@ var init_gitBlameTypes = __esm({
2897
2897
  /** User email passed through from request for single commit blame attribution analysis trigger. */
2898
2898
  userEmail: z19.string().optional()
2899
2899
  });
2900
+ VulnerabilityAttributionMessageZ = z19.object({
2901
+ fixReportId: z19.string().uuid(),
2902
+ vulnerabilityAttributionRequestId: z19.string().uuid(),
2903
+ userEmail: z19.string()
2904
+ });
2900
2905
  }
2901
2906
  });
2902
2907
 
package/dist/index.mjs CHANGED
@@ -2774,7 +2774,7 @@ function parseCommitLine(line) {
2774
2774
  message: parts[6]
2775
2775
  };
2776
2776
  }
2777
- var PrepareGitBlameMessageZ, PrepareGitBlameResponseMessageZ, CommitMetadataZ, LineToCommitMapZ, CommitMetadataMapZ, BlameInfoZ, LineRangeZ, PrContextZ, PrepareCommitBlameMessageZ, BlameLineInfoZ, FileBlameDataZ, ChunkFetchResultZ, FileBlameResponseEntryZ, CommitBlameDataZ, CommitInfoZ, GitIdentityZ, COMMIT_LOG_FORMAT, CommitDataZ, PrDiffDataZ, PrStatsZ, CommitsManifestZ, BlameLineEntryZ, BlameLinesDataZ, PrepareCommitBlameResponseMessageZ;
2777
+ var PrepareGitBlameMessageZ, PrepareGitBlameResponseMessageZ, CommitMetadataZ, LineToCommitMapZ, CommitMetadataMapZ, BlameInfoZ, LineRangeZ, PrContextZ, PrepareCommitBlameMessageZ, BlameLineInfoZ, FileBlameDataZ, ChunkFetchResultZ, FileBlameResponseEntryZ, CommitBlameDataZ, CommitInfoZ, GitIdentityZ, COMMIT_LOG_FORMAT, CommitDataZ, PrDiffDataZ, PrStatsZ, CommitsManifestZ, BlameLineEntryZ, BlameLinesDataZ, PrepareCommitBlameResponseMessageZ, VulnerabilityAttributionMessageZ;
2778
2778
  var init_gitBlameTypes = __esm({
2779
2779
  "src/utils/blame/gitBlameTypes.ts"() {
2780
2780
  "use strict";
@@ -2949,6 +2949,11 @@ var init_gitBlameTypes = __esm({
2949
2949
  /** User email passed through from request for single commit blame attribution analysis trigger. */
2950
2950
  userEmail: z18.string().optional()
2951
2951
  });
2952
+ VulnerabilityAttributionMessageZ = z18.object({
2953
+ fixReportId: z18.string().uuid(),
2954
+ vulnerabilityAttributionRequestId: z18.string().uuid(),
2955
+ userEmail: z18.string()
2956
+ });
2952
2957
  }
2953
2958
  });
2954
2959
 
@@ -13705,11 +13710,33 @@ function withTimeout(promise, ms, label) {
13705
13710
 
13706
13711
  // src/features/analysis/graphql/tracy-batch-upload.ts
13707
13712
  var debug10 = Debug9("mobbdev:tracy-batch-upload");
13713
+ function timedStep(label, fn) {
13714
+ const start = Date.now();
13715
+ const result = fn();
13716
+ const maybePromise = result instanceof Promise ? result : Promise.resolve(result);
13717
+ return maybePromise.then(
13718
+ (val) => {
13719
+ debug10("[perf] %s: %dms", label, Date.now() - start);
13720
+ return val;
13721
+ },
13722
+ (err) => {
13723
+ debug10("[perf] %s FAILED: %dms", label, Date.now() - start);
13724
+ throw err;
13725
+ }
13726
+ );
13727
+ }
13708
13728
  var BATCH_TIMEOUT_MS = 3e4;
13709
13729
  async function sanitizeRawData(rawData) {
13730
+ const start = Date.now();
13710
13731
  try {
13711
13732
  const sanitized = await sanitizeData(rawData);
13712
- return JSON.stringify(sanitized);
13733
+ const serialized = JSON.stringify(sanitized);
13734
+ debug10(
13735
+ "[perf] sanitizeRawData: %dms (%d bytes)",
13736
+ Date.now() - start,
13737
+ serialized.length
13738
+ );
13739
+ return serialized;
13713
13740
  } catch (err) {
13714
13741
  console.warn(
13715
13742
  "[tracy] sanitizeRawData failed, falling back to unsanitized:",
@@ -13718,27 +13745,35 @@ async function sanitizeRawData(rawData) {
13718
13745
  return JSON.stringify(rawData);
13719
13746
  }
13720
13747
  }
13721
- async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
13722
- const repositoryUrl = await getRepositoryUrl(workingDir);
13748
+ async function prepareAndSendTracyRecords(client, rawRecords, workingDir, options) {
13723
13749
  const { computerName, userName } = getSystemInfo();
13724
- const clientVersion = packageJson.version;
13725
- debug10("[step:sanitize] Sanitizing %d records", rawRecords.length);
13750
+ const defaultClientVersion = packageJson.version;
13751
+ const shouldSanitize = options?.sanitize ?? true;
13752
+ const defaultRepoUrl = rawRecords[0]?.repositoryUrl ? void 0 : await getRepositoryUrl(workingDir) ?? void 0;
13753
+ debug10(
13754
+ "[step:sanitize] %s %d records",
13755
+ shouldSanitize ? "Sanitizing" : "Serializing",
13756
+ rawRecords.length
13757
+ );
13726
13758
  const serializedRawDataByIndex = /* @__PURE__ */ new Map();
13727
- const records = await Promise.all(
13728
- rawRecords.map(async (record, index) => {
13729
- if (record.rawData != null) {
13730
- const serialized = await sanitizeRawData(record.rawData);
13731
- serializedRawDataByIndex.set(index, serialized);
13732
- }
13733
- const { rawData: _rawData, ...rest } = record;
13734
- return {
13735
- ...rest,
13736
- repositoryUrl: repositoryUrl ?? void 0,
13737
- computerName,
13738
- userName,
13739
- clientVersion
13740
- };
13741
- })
13759
+ const records = await timedStep(
13760
+ `${shouldSanitize ? "sanitize" : "serialize"} ${rawRecords.length} records`,
13761
+ () => Promise.all(
13762
+ rawRecords.map(async (record, index) => {
13763
+ if (record.rawData != null) {
13764
+ const serialized = shouldSanitize ? await sanitizeRawData(record.rawData) : JSON.stringify(record.rawData);
13765
+ serializedRawDataByIndex.set(index, serialized);
13766
+ }
13767
+ const { rawData: _rawData, ...rest } = record;
13768
+ return {
13769
+ ...rest,
13770
+ repositoryUrl: record.repositoryUrl ?? defaultRepoUrl,
13771
+ computerName,
13772
+ userName,
13773
+ clientVersion: record.clientVersion ?? defaultClientVersion
13774
+ };
13775
+ })
13776
+ )
13742
13777
  );
13743
13778
  const recordsWithRawData = rawRecords.map((r, i) => ({ recordId: r.recordId, index: i })).filter((entry) => serializedRawDataByIndex.has(entry.index));
13744
13779
  if (recordsWithRawData.length > 0) {
@@ -13783,6 +13818,7 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
13783
13818
  "[step:s3-upload] Uploading %d files to S3",
13784
13819
  recordsWithRawData.length
13785
13820
  );
13821
+ const s3Start = Date.now();
13786
13822
  const uploadResults = await Promise.allSettled(
13787
13823
  recordsWithRawData.map(async (entry) => {
13788
13824
  const rawDataJson = serializedRawDataByIndex.get(entry.index);
@@ -13804,6 +13840,11 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
13804
13840
  records[entry.index].rawDataS3Key = uploadKey;
13805
13841
  })
13806
13842
  );
13843
+ debug10(
13844
+ "[perf] s3-upload %d files: %dms",
13845
+ recordsWithRawData.length,
13846
+ Date.now() - s3Start
13847
+ );
13807
13848
  const uploadErrors = uploadResults.filter((r) => r.status === "rejected").map((r) => r.reason.message);
13808
13849
  if (uploadErrors.length > 0) {
13809
13850
  debug10("[step:s3-upload] S3 upload errors: %O", uploadErrors);
@@ -13826,10 +13867,13 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
13826
13867
  }
13827
13868
  debug10("[step:gql-submit] Submitting %d records via GraphQL", records.length);
13828
13869
  try {
13829
- const result = await withTimeout(
13830
- client.uploadTracyRecords({ records }),
13831
- BATCH_TIMEOUT_MS,
13832
- "[step:gql-submit] uploadTracyRecords"
13870
+ const result = await timedStep(
13871
+ `gql-submit ${records.length} records`,
13872
+ () => withTimeout(
13873
+ client.uploadTracyRecords({ records }),
13874
+ BATCH_TIMEOUT_MS,
13875
+ "[step:gql-submit] uploadTracyRecords"
13876
+ )
13833
13877
  );
13834
13878
  if (result.uploadTracyRecords.status !== "OK") {
13835
13879
  return {
@@ -16381,7 +16425,7 @@ async function analyzeHandler(args) {
16381
16425
  // src/features/claude_code/data_collector.ts
16382
16426
  import { createHash as createHash2 } from "crypto";
16383
16427
  import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
16384
- import path13 from "path";
16428
+ import path14 from "path";
16385
16429
  import { z as z33 } from "zod";
16386
16430
  init_client_generates();
16387
16431
 
@@ -16666,7 +16710,7 @@ function createLogger(config2) {
16666
16710
 
16667
16711
  // src/features/claude_code/hook_logger.ts
16668
16712
  var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
16669
- var CLI_VERSION = true ? "1.2.57" : "unknown";
16713
+ var CLI_VERSION = true ? "1.2.59" : "unknown";
16670
16714
  var NAMESPACE = "mobbdev-claude-code-hook-logs";
16671
16715
  function createHookLogger(scopePath) {
16672
16716
  return createLogger({
@@ -16690,13 +16734,13 @@ function flushLogs() {
16690
16734
  for (const scoped of activeScopedLoggers) {
16691
16735
  scoped.flushLogs();
16692
16736
  }
16693
- activeScopedLoggers.length = 0;
16694
16737
  }
16695
16738
  async function flushDdLogs() {
16696
16739
  await logger.flushDdAsync();
16697
16740
  for (const scoped of activeScopedLoggers) {
16698
16741
  await scoped.flushDdAsync();
16699
16742
  }
16743
+ activeScopedLoggers.length = 0;
16700
16744
  }
16701
16745
  function createScopedHookLog(scopePath) {
16702
16746
  const scoped = createHookLogger(scopePath);
@@ -16704,14 +16748,136 @@ function createScopedHookLog(scopePath) {
16704
16748
  return scoped;
16705
16749
  }
16706
16750
 
16751
+ // src/features/claude_code/install_hook.ts
16752
+ import fsPromises4 from "fs/promises";
16753
+ import os5 from "os";
16754
+ import path13 from "path";
16755
+ import chalk11 from "chalk";
16756
+ var CLAUDE_SETTINGS_PATH = path13.join(os5.homedir(), ".claude", "settings.json");
16757
+ var RECOMMENDED_MATCHER = "Bash|Write|Edit|Agent|Read";
16758
+ var STALE_MATCHERS = /* @__PURE__ */ new Set(["", "*", "Edit|Write"]);
16759
+ async function claudeSettingsExists() {
16760
+ try {
16761
+ await fsPromises4.access(CLAUDE_SETTINGS_PATH);
16762
+ return true;
16763
+ } catch {
16764
+ return false;
16765
+ }
16766
+ }
16767
+ async function readClaudeSettings() {
16768
+ const settingsContent = await fsPromises4.readFile(
16769
+ CLAUDE_SETTINGS_PATH,
16770
+ "utf-8"
16771
+ );
16772
+ return JSON.parse(settingsContent);
16773
+ }
16774
+ async function writeClaudeSettings(settings) {
16775
+ await fsPromises4.writeFile(
16776
+ CLAUDE_SETTINGS_PATH,
16777
+ JSON.stringify(settings, null, 2),
16778
+ "utf-8"
16779
+ );
16780
+ }
16781
+ async function autoUpgradeMatcherIfStale() {
16782
+ try {
16783
+ if (!await claudeSettingsExists()) return false;
16784
+ const settings = await readClaudeSettings();
16785
+ const hooks = settings.hooks?.PostToolUse;
16786
+ if (!hooks) return false;
16787
+ let upgraded = false;
16788
+ for (const hook of hooks) {
16789
+ const isMobbHook = hook.hooks.some(
16790
+ (h) => h.command?.includes("claude-code-process-hook") && (h.command?.includes("mobbdev") || h.command?.includes("mobbdev@"))
16791
+ );
16792
+ if (isMobbHook && STALE_MATCHERS.has(hook.matcher)) {
16793
+ hook.matcher = RECOMMENDED_MATCHER;
16794
+ upgraded = true;
16795
+ }
16796
+ }
16797
+ if (upgraded) {
16798
+ await writeClaudeSettings(settings);
16799
+ }
16800
+ return upgraded;
16801
+ } catch {
16802
+ return false;
16803
+ }
16804
+ }
16805
+ async function installMobbHooks(options = {}) {
16806
+ console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
16807
+ if (!await claudeSettingsExists()) {
16808
+ console.log(chalk11.red("\u274C Claude Code settings file not found"));
16809
+ console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
16810
+ console.log(chalk11.yellow("Is Claude Code installed on your system?"));
16811
+ console.log(chalk11.yellow("Please install Claude Code and try again."));
16812
+ throw new Error(
16813
+ "Claude Code settings file not found. Is Claude Code installed?"
16814
+ );
16815
+ }
16816
+ const settings = await readClaudeSettings();
16817
+ if (!settings.hooks) {
16818
+ settings.hooks = {};
16819
+ }
16820
+ if (!settings.hooks.PostToolUse) {
16821
+ settings.hooks.PostToolUse = [];
16822
+ }
16823
+ let command = "npx --yes mobbdev@latest claude-code-process-hook";
16824
+ if (options.saveEnv) {
16825
+ const envVars = [];
16826
+ if (process.env["WEB_APP_URL"]) {
16827
+ envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
16828
+ }
16829
+ if (process.env["API_URL"]) {
16830
+ envVars.push(`API_URL="${process.env["API_URL"]}"`);
16831
+ }
16832
+ if (envVars.length > 0) {
16833
+ command = `${envVars.join(" ")} ${command}`;
16834
+ console.log(
16835
+ chalk11.blue(
16836
+ `Adding environment variables to hook command: ${envVars.join(", ")}`
16837
+ )
16838
+ );
16839
+ }
16840
+ }
16841
+ const mobbHookConfig = {
16842
+ // Only fire on tools that indicate meaningful work — skip high-frequency
16843
+ // read-only tools (Grep, Glob, WebSearch, WebFetch) to reduce CPU overhead
16844
+ // from process startup (~1.7s user CPU per invocation).
16845
+ matcher: RECOMMENDED_MATCHER,
16846
+ hooks: [
16847
+ {
16848
+ type: "command",
16849
+ command
16850
+ }
16851
+ ]
16852
+ };
16853
+ const existingHookIndex = settings.hooks.PostToolUse.findIndex(
16854
+ (hook) => hook.hooks.some(
16855
+ (h) => h.command?.includes("mobbdev@latest claude-code-process-hook")
16856
+ )
16857
+ );
16858
+ if (existingHookIndex >= 0) {
16859
+ console.log(chalk11.yellow("Mobb hook already exists, updating..."));
16860
+ settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
16861
+ } else {
16862
+ console.log(chalk11.green("Adding new Mobb hook..."));
16863
+ settings.hooks.PostToolUse.push(mobbHookConfig);
16864
+ }
16865
+ await writeClaudeSettings(settings);
16866
+ console.log(
16867
+ chalk11.green(
16868
+ `\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
16869
+ )
16870
+ );
16871
+ }
16872
+
16707
16873
  // src/features/claude_code/data_collector.ts
16708
16874
  var GLOBAL_COOLDOWN_MS = 5e3;
16709
- var HOOK_COOLDOWN_MS = 1e4;
16875
+ var HOOK_COOLDOWN_MS = 15e3;
16710
16876
  var ACTIVE_LOCK_TTL_MS = 6e4;
16711
16877
  var GQL_AUTH_TIMEOUT_MS = 15e3;
16712
16878
  var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
16713
16879
  var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
16714
- var MAX_ENTRIES_PER_INVOCATION = 100;
16880
+ var MAX_ENTRIES_PER_INVOCATION = 50;
16715
16881
  var COOLDOWN_KEY = "lastHookRunAt";
16716
16882
  var ACTIVE_KEY = "hookActiveAt";
16717
16883
  var HookDataSchema = z33.object({
@@ -16789,12 +16955,12 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
16789
16955
  return transcriptPath;
16790
16956
  } catch {
16791
16957
  }
16792
- const filename = path13.basename(transcriptPath);
16793
- const dirName = path13.basename(path13.dirname(transcriptPath));
16794
- const projectsDir = path13.dirname(path13.dirname(transcriptPath));
16958
+ const filename = path14.basename(transcriptPath);
16959
+ const dirName = path14.basename(path14.dirname(transcriptPath));
16960
+ const projectsDir = path14.dirname(path14.dirname(transcriptPath));
16795
16961
  const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
16796
16962
  if (baseDirName !== dirName) {
16797
- const candidate = path13.join(projectsDir, baseDirName, filename);
16963
+ const candidate = path14.join(projectsDir, baseDirName, filename);
16798
16964
  try {
16799
16965
  await access(candidate);
16800
16966
  hookLog.info(
@@ -16816,7 +16982,7 @@ async function resolveTranscriptPath(transcriptPath, sessionId) {
16816
16982
  const dirs = await readdir(projectsDir);
16817
16983
  for (const dir of dirs) {
16818
16984
  if (dir === dirName) continue;
16819
- const candidate = path13.join(projectsDir, dir, filename);
16985
+ const candidate = path14.join(projectsDir, dir, filename);
16820
16986
  try {
16821
16987
  await access(candidate);
16822
16988
  hookLog.info(
@@ -16851,7 +17017,11 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
16851
17017
  fileSize = stat.size;
16852
17018
  if (cursor.byteOffset >= stat.size) {
16853
17019
  hookLog.info({ data: { sessionId } }, "No new data in transcript file");
16854
- return { entries: [], endByteOffset: fileSize };
17020
+ return {
17021
+ entries: [],
17022
+ endByteOffset: fileSize,
17023
+ resolvedTranscriptPath: transcriptPath
17024
+ };
16855
17025
  }
16856
17026
  const buf = Buffer.alloc(stat.size - cursor.byteOffset);
16857
17027
  await fh.read(buf, 0, buf.length, cursor.byteOffset);
@@ -16937,7 +17107,11 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
16937
17107
  "Resuming from byte offset"
16938
17108
  );
16939
17109
  }
16940
- return { entries: parsed, endByteOffset };
17110
+ return {
17111
+ entries: parsed,
17112
+ endByteOffset,
17113
+ resolvedTranscriptPath: transcriptPath
17114
+ };
16941
17115
  }
16942
17116
  var FILTERED_PROGRESS_SUBTYPES = /* @__PURE__ */ new Set([
16943
17117
  // Incremental streaming output from running bash commands, emitted every
@@ -17005,13 +17179,13 @@ async function cleanupStaleSessions(sessionStore) {
17005
17179
  }
17006
17180
  const now = Date.now();
17007
17181
  const prefix = getSessionFilePrefix();
17008
- const configDir = path13.dirname(sessionStore.path);
17182
+ const configDir = path14.dirname(sessionStore.path);
17009
17183
  try {
17010
17184
  const files = await readdir(configDir);
17011
17185
  let deletedCount = 0;
17012
17186
  for (const file of files) {
17013
17187
  if (!file.startsWith(prefix) || !file.endsWith(".json")) continue;
17014
- const filePath = path13.join(configDir, file);
17188
+ const filePath = path14.join(configDir, file);
17015
17189
  try {
17016
17190
  const content = JSON.parse(await readFile(filePath, "utf-8"));
17017
17191
  let newest = 0;
@@ -17046,6 +17220,16 @@ async function processAndUploadTranscriptEntries() {
17046
17220
  return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
17047
17221
  }
17048
17222
  configStore.set("claudeCode.globalLastHookRunAt", globalNow);
17223
+ const lastUpgradeVersion = configStore.get(
17224
+ "claudeCode.matcherUpgradeVersion"
17225
+ );
17226
+ if (lastUpgradeVersion !== packageJson.version) {
17227
+ const upgraded = await autoUpgradeMatcherIfStale();
17228
+ configStore.set("claudeCode.matcherUpgradeVersion", packageJson.version);
17229
+ if (upgraded) {
17230
+ hookLog.info("Auto-upgraded hook matcher to reduce CPU usage");
17231
+ }
17232
+ }
17049
17233
  const rawData = await readStdinData();
17050
17234
  const hookData = validateHookData(rawData);
17051
17235
  const sessionStore = createSessionConfigStore(hookData.session_id);
@@ -17093,12 +17277,19 @@ async function processAndUploadTranscriptEntries() {
17093
17277
  }
17094
17278
  }
17095
17279
  async function processTranscript(hookData, sessionStore, log2) {
17096
- const cursorKey = getCursorKey(hookData.transcript_path);
17097
- const { entries: rawEntries, endByteOffset } = await readNewTranscriptEntries(
17098
- hookData.transcript_path,
17099
- hookData.session_id,
17100
- sessionStore
17280
+ const {
17281
+ entries: rawEntries,
17282
+ endByteOffset,
17283
+ resolvedTranscriptPath
17284
+ } = await log2.timed(
17285
+ "Read transcript",
17286
+ () => readNewTranscriptEntries(
17287
+ hookData.transcript_path,
17288
+ hookData.session_id,
17289
+ sessionStore
17290
+ )
17101
17291
  );
17292
+ const cursorKey = getCursorKey(resolvedTranscriptPath);
17102
17293
  if (rawEntries.length === 0) {
17103
17294
  log2.info("No new entries to upload");
17104
17295
  return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
@@ -17176,20 +17367,27 @@ async function processTranscript(hookData, sessionStore, log2) {
17176
17367
  rawData: rawEntry
17177
17368
  };
17178
17369
  });
17370
+ const totalRawDataBytes = records.reduce((sum, r) => {
17371
+ return sum + (r.rawData ? JSON.stringify(r.rawData).length : 0);
17372
+ }, 0);
17179
17373
  log2.info(
17180
17374
  {
17181
17375
  data: {
17182
17376
  count: records.length,
17183
17377
  skipped: filteredOut,
17378
+ rawDataBytes: totalRawDataBytes,
17184
17379
  firstRecordId: records[0]?.recordId,
17185
17380
  lastRecordId: records[records.length - 1]?.recordId
17186
17381
  }
17187
17382
  },
17188
17383
  "Uploading batch"
17189
17384
  );
17385
+ const sanitize = process.env["MOBBDEV_HOOK_SANITIZE"] === "1";
17190
17386
  const result = await log2.timed(
17191
17387
  "Batch upload",
17192
- () => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd)
17388
+ () => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd, {
17389
+ sanitize
17390
+ })
17193
17391
  );
17194
17392
  if (result.ok) {
17195
17393
  const lastRawEntry = rawEntries[rawEntries.length - 1];
@@ -17221,100 +17419,6 @@ async function processTranscript(hookData, sessionStore, log2) {
17221
17419
  };
17222
17420
  }
17223
17421
 
17224
- // src/features/claude_code/install_hook.ts
17225
- import fsPromises4 from "fs/promises";
17226
- import os5 from "os";
17227
- import path14 from "path";
17228
- import chalk11 from "chalk";
17229
- var CLAUDE_SETTINGS_PATH = path14.join(os5.homedir(), ".claude", "settings.json");
17230
- async function claudeSettingsExists() {
17231
- try {
17232
- await fsPromises4.access(CLAUDE_SETTINGS_PATH);
17233
- return true;
17234
- } catch {
17235
- return false;
17236
- }
17237
- }
17238
- async function readClaudeSettings() {
17239
- const settingsContent = await fsPromises4.readFile(
17240
- CLAUDE_SETTINGS_PATH,
17241
- "utf-8"
17242
- );
17243
- return JSON.parse(settingsContent);
17244
- }
17245
- async function writeClaudeSettings(settings) {
17246
- await fsPromises4.writeFile(
17247
- CLAUDE_SETTINGS_PATH,
17248
- JSON.stringify(settings, null, 2),
17249
- "utf-8"
17250
- );
17251
- }
17252
- async function installMobbHooks(options = {}) {
17253
- console.log(chalk11.blue("Installing Mobb hooks in Claude Code settings..."));
17254
- if (!await claudeSettingsExists()) {
17255
- console.log(chalk11.red("\u274C Claude Code settings file not found"));
17256
- console.log(chalk11.yellow(`Expected location: ${CLAUDE_SETTINGS_PATH}`));
17257
- console.log(chalk11.yellow("Is Claude Code installed on your system?"));
17258
- console.log(chalk11.yellow("Please install Claude Code and try again."));
17259
- throw new Error(
17260
- "Claude Code settings file not found. Is Claude Code installed?"
17261
- );
17262
- }
17263
- const settings = await readClaudeSettings();
17264
- if (!settings.hooks) {
17265
- settings.hooks = {};
17266
- }
17267
- if (!settings.hooks.PostToolUse) {
17268
- settings.hooks.PostToolUse = [];
17269
- }
17270
- let command = "npx --yes mobbdev@latest claude-code-process-hook";
17271
- if (options.saveEnv) {
17272
- const envVars = [];
17273
- if (process.env["WEB_APP_URL"]) {
17274
- envVars.push(`WEB_APP_URL="${process.env["WEB_APP_URL"]}"`);
17275
- }
17276
- if (process.env["API_URL"]) {
17277
- envVars.push(`API_URL="${process.env["API_URL"]}"`);
17278
- }
17279
- if (envVars.length > 0) {
17280
- command = `${envVars.join(" ")} ${command}`;
17281
- console.log(
17282
- chalk11.blue(
17283
- `Adding environment variables to hook command: ${envVars.join(", ")}`
17284
- )
17285
- );
17286
- }
17287
- }
17288
- const mobbHookConfig = {
17289
- // Empty matcher = match all tools (Claude Code hook spec: empty string matches every PostToolUse event)
17290
- matcher: "",
17291
- hooks: [
17292
- {
17293
- type: "command",
17294
- command
17295
- }
17296
- ]
17297
- };
17298
- const existingHookIndex = settings.hooks.PostToolUse.findIndex(
17299
- (hook) => hook.hooks.some(
17300
- (h) => h.command?.includes("mobbdev@latest claude-code-process-hook")
17301
- )
17302
- );
17303
- if (existingHookIndex >= 0) {
17304
- console.log(chalk11.yellow("Mobb hook already exists, updating..."));
17305
- settings.hooks.PostToolUse[existingHookIndex] = mobbHookConfig;
17306
- } else {
17307
- console.log(chalk11.green("Adding new Mobb hook..."));
17308
- settings.hooks.PostToolUse.push(mobbHookConfig);
17309
- }
17310
- await writeClaudeSettings(settings);
17311
- console.log(
17312
- chalk11.green(
17313
- `\u2705 Mobb hooks ${options.saveEnv ? "and environment variables " : ""}installed successfully in ${CLAUDE_SETTINGS_PATH}`
17314
- )
17315
- );
17316
- }
17317
-
17318
17422
  // src/args/commands/claude_code.ts
17319
17423
  var claudeCodeInstallHookBuilder = (yargs2) => {
17320
17424
  return yargs2.option("save-env", {
@@ -17346,13 +17450,15 @@ var claudeCodeInstallHookHandler = async (argv) => {
17346
17450
  }
17347
17451
  };
17348
17452
  var claudeCodeProcessHookHandler = async () => {
17453
+ const startupMs = Math.round(process.uptime() * 1e3);
17454
+ const debugMode = process.env["MOBBDEV_HOOK_DEBUG"] === "1";
17349
17455
  async function flushAndExit(code) {
17350
17456
  try {
17351
17457
  flushLogs();
17352
17458
  await flushDdLogs();
17353
17459
  } catch {
17354
17460
  } finally {
17355
- process.exit(code);
17461
+ process.exit(debugMode ? code : 0);
17356
17462
  }
17357
17463
  }
17358
17464
  process.on("uncaughtException", (error) => {
@@ -17375,14 +17481,20 @@ var claudeCodeProcessHookHandler = async () => {
17375
17481
  void flushAndExit(1);
17376
17482
  });
17377
17483
  let exitCode = 0;
17484
+ const hookStart = Date.now();
17378
17485
  try {
17379
17486
  const result = await processAndUploadTranscriptEntries();
17487
+ if (result.errors > 0) {
17488
+ exitCode = 1;
17489
+ }
17380
17490
  hookLog.info(
17381
17491
  {
17382
17492
  data: {
17383
17493
  entriesUploaded: result.entriesUploaded,
17384
17494
  entriesSkipped: result.entriesSkipped,
17385
- errors: result.errors
17495
+ errors: result.errors,
17496
+ startupMs,
17497
+ durationMs: Date.now() - hookStart
17386
17498
  }
17387
17499
  },
17388
17500
  "Claude Code upload complete"
@@ -17393,7 +17505,8 @@ var claudeCodeProcessHookHandler = async () => {
17393
17505
  {
17394
17506
  data: {
17395
17507
  error: String(error),
17396
- stack: error instanceof Error ? error.stack : void 0
17508
+ stack: error instanceof Error ? error.stack : void 0,
17509
+ durationMs: Date.now() - hookStart
17397
17510
  }
17398
17511
  },
17399
17512
  "Failed to process Claude Code hook"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobbdev",
3
- "version": "1.2.57",
3
+ "version": "1.2.59",
4
4
  "description": "Automated secure code remediation tool",
5
5
  "repository": "git+https://github.com/mobb-dev/bugsy.git",
6
6
  "main": "dist/index.mjs",