mobbdev 1.2.57 → 1.2.58
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/dist/args/commands/upload_ai_blame.mjs +6 -1
- package/dist/index.mjs +255 -142
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
13725
|
-
|
|
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
|
|
13728
|
-
|
|
13729
|
-
|
|
13730
|
-
|
|
13731
|
-
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
...rest
|
|
13736
|
-
|
|
13737
|
-
|
|
13738
|
-
|
|
13739
|
-
|
|
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
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
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
|
|
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.
|
|
16713
|
+
var CLI_VERSION = true ? "1.2.58" : "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 =
|
|
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 =
|
|
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 =
|
|
16793
|
-
const dirName =
|
|
16794
|
-
const projectsDir =
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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
|
|
17097
|
-
|
|
17098
|
-
|
|
17099
|
-
|
|
17100
|
-
|
|
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"
|