mobbdev 1.2.55 → 1.2.57
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/.env +3 -2
- package/dist/args/commands/upload_ai_blame.mjs +30 -5
- package/dist/index.mjs +433 -177
- package/package.json +1 -1
package/.env
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# production@
|
|
1
|
+
# production@v21
|
|
2
2
|
API_URL="https://api.mobb.ai/v1/graphql"
|
|
3
3
|
WEB_APP_URL="https://app.mobb.ai"
|
|
4
4
|
GITLAB_API_TOKEN=""
|
|
@@ -6,4 +6,5 @@ GITHUB_API_TOKEN=""
|
|
|
6
6
|
ADO_TEST_ACCESS_TOKEN=""
|
|
7
7
|
HASURA_ACCESS_KEY=""
|
|
8
8
|
LOCAL_GRAPHQL_ENDPOINT=""
|
|
9
|
-
GIT_PROXY_HOST="http://tinyproxy:8888"
|
|
9
|
+
GIT_PROXY_HOST="http://tinyproxy:8888"
|
|
10
|
+
DD_RUM_TOKEN="pubf59c0182545bfb4c299175119f1abf9b"
|
|
@@ -1445,8 +1445,16 @@ var init_analysis = __esm({
|
|
|
1445
1445
|
originalUrl: z3.string(),
|
|
1446
1446
|
reference: z3.string(),
|
|
1447
1447
|
commitSha: z3.string(),
|
|
1448
|
-
isKnownBranch: z3.boolean().
|
|
1449
|
-
})
|
|
1448
|
+
isKnownBranch: z3.boolean().nullish().default(true)
|
|
1449
|
+
}).nullable().transform(
|
|
1450
|
+
(repo) => repo ?? {
|
|
1451
|
+
name: null,
|
|
1452
|
+
originalUrl: "",
|
|
1453
|
+
reference: "",
|
|
1454
|
+
commitSha: "",
|
|
1455
|
+
isKnownBranch: true
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1450
1458
|
vulnerabilityReport: z3.object({
|
|
1451
1459
|
id: z3.string().uuid(),
|
|
1452
1460
|
vendor: z3.nativeEnum(Vulnerability_Report_Vendor_Enum),
|
|
@@ -1937,7 +1945,15 @@ var init_types = __esm({
|
|
|
1937
1945
|
reference: z7.string(),
|
|
1938
1946
|
commitSha: z7.string(),
|
|
1939
1947
|
isKnownBranch: z7.boolean().nullish().default(true)
|
|
1940
|
-
})
|
|
1948
|
+
}).nullable().transform(
|
|
1949
|
+
(repo) => repo ?? {
|
|
1950
|
+
name: null,
|
|
1951
|
+
originalUrl: "",
|
|
1952
|
+
reference: "",
|
|
1953
|
+
commitSha: "",
|
|
1954
|
+
isKnownBranch: true
|
|
1955
|
+
}
|
|
1956
|
+
),
|
|
1941
1957
|
vulnerabilityReportIssuesFixedCount: z7.object({
|
|
1942
1958
|
vulnerabilityReportIssues_aggregate: z7.object({
|
|
1943
1959
|
aggregate: z7.object({ count: z7.number() })
|
|
@@ -1958,7 +1974,7 @@ var init_types = __esm({
|
|
|
1958
1974
|
file: z7.object({
|
|
1959
1975
|
id: z7.string().uuid(),
|
|
1960
1976
|
path: z7.string()
|
|
1961
|
-
}),
|
|
1977
|
+
}).nullable(),
|
|
1962
1978
|
pending: z7.object({
|
|
1963
1979
|
aggregate: z7.object({
|
|
1964
1980
|
count: z7.number()
|
|
@@ -2159,7 +2175,13 @@ var init_types = __esm({
|
|
|
2159
2175
|
originalUrl: z7.string(),
|
|
2160
2176
|
reference: z7.string(),
|
|
2161
2177
|
name: z7.string()
|
|
2162
|
-
})
|
|
2178
|
+
}).nullable().transform(
|
|
2179
|
+
(repo) => repo ?? {
|
|
2180
|
+
originalUrl: "",
|
|
2181
|
+
reference: "",
|
|
2182
|
+
name: ""
|
|
2183
|
+
}
|
|
2184
|
+
),
|
|
2163
2185
|
createdByUser: z7.object({
|
|
2164
2186
|
email: z7.string()
|
|
2165
2187
|
}).nullable(),
|
|
@@ -7323,6 +7345,9 @@ async function sanitizeDataWithCounts(obj) {
|
|
|
7323
7345
|
return { sanitizedData, counts };
|
|
7324
7346
|
}
|
|
7325
7347
|
|
|
7348
|
+
// src/utils/with-timeout.ts
|
|
7349
|
+
import { setTimeout as delay } from "timers/promises";
|
|
7350
|
+
|
|
7326
7351
|
// src/features/analysis/upload-file.ts
|
|
7327
7352
|
import Debug7 from "debug";
|
|
7328
7353
|
import fetch3, { File, fileFrom, FormData } from "node-fetch";
|
package/dist/index.mjs
CHANGED
|
@@ -1660,8 +1660,16 @@ var init_analysis = __esm({
|
|
|
1660
1660
|
originalUrl: z8.string(),
|
|
1661
1661
|
reference: z8.string(),
|
|
1662
1662
|
commitSha: z8.string(),
|
|
1663
|
-
isKnownBranch: z8.boolean().
|
|
1664
|
-
})
|
|
1663
|
+
isKnownBranch: z8.boolean().nullish().default(true)
|
|
1664
|
+
}).nullable().transform(
|
|
1665
|
+
(repo) => repo ?? {
|
|
1666
|
+
name: null,
|
|
1667
|
+
originalUrl: "",
|
|
1668
|
+
reference: "",
|
|
1669
|
+
commitSha: "",
|
|
1670
|
+
isKnownBranch: true
|
|
1671
|
+
}
|
|
1672
|
+
),
|
|
1665
1673
|
vulnerabilityReport: z8.object({
|
|
1666
1674
|
id: z8.string().uuid(),
|
|
1667
1675
|
vendor: z8.nativeEnum(Vulnerability_Report_Vendor_Enum),
|
|
@@ -1982,7 +1990,15 @@ var init_types = __esm({
|
|
|
1982
1990
|
reference: z11.string(),
|
|
1983
1991
|
commitSha: z11.string(),
|
|
1984
1992
|
isKnownBranch: z11.boolean().nullish().default(true)
|
|
1985
|
-
})
|
|
1993
|
+
}).nullable().transform(
|
|
1994
|
+
(repo) => repo ?? {
|
|
1995
|
+
name: null,
|
|
1996
|
+
originalUrl: "",
|
|
1997
|
+
reference: "",
|
|
1998
|
+
commitSha: "",
|
|
1999
|
+
isKnownBranch: true
|
|
2000
|
+
}
|
|
2001
|
+
),
|
|
1986
2002
|
vulnerabilityReportIssuesFixedCount: z11.object({
|
|
1987
2003
|
vulnerabilityReportIssues_aggregate: z11.object({
|
|
1988
2004
|
aggregate: z11.object({ count: z11.number() })
|
|
@@ -2003,7 +2019,7 @@ var init_types = __esm({
|
|
|
2003
2019
|
file: z11.object({
|
|
2004
2020
|
id: z11.string().uuid(),
|
|
2005
2021
|
path: z11.string()
|
|
2006
|
-
}),
|
|
2022
|
+
}).nullable(),
|
|
2007
2023
|
pending: z11.object({
|
|
2008
2024
|
aggregate: z11.object({
|
|
2009
2025
|
count: z11.number()
|
|
@@ -2204,7 +2220,13 @@ var init_types = __esm({
|
|
|
2204
2220
|
originalUrl: z11.string(),
|
|
2205
2221
|
reference: z11.string(),
|
|
2206
2222
|
name: z11.string()
|
|
2207
|
-
})
|
|
2223
|
+
}).nullable().transform(
|
|
2224
|
+
(repo) => repo ?? {
|
|
2225
|
+
originalUrl: "",
|
|
2226
|
+
reference: "",
|
|
2227
|
+
name: ""
|
|
2228
|
+
}
|
|
2229
|
+
),
|
|
2208
2230
|
createdByUser: z11.object({
|
|
2209
2231
|
email: z11.string()
|
|
2210
2232
|
}).nullable(),
|
|
@@ -6695,9 +6717,9 @@ function transformVisualStudioUrl(url) {
|
|
|
6695
6717
|
}
|
|
6696
6718
|
function _getPublicAdoClient({
|
|
6697
6719
|
orgName,
|
|
6698
|
-
origin
|
|
6720
|
+
origin
|
|
6699
6721
|
}) {
|
|
6700
|
-
const orgUrl = `${
|
|
6722
|
+
const orgUrl = `${origin}/${orgName}`;
|
|
6701
6723
|
const authHandler = api.getPersonalAccessTokenHandler("");
|
|
6702
6724
|
authHandler.canHandleAuthentication = () => false;
|
|
6703
6725
|
authHandler.prepareRequest = (_options) => {
|
|
@@ -6759,10 +6781,10 @@ async function getAdoConnectData({
|
|
|
6759
6781
|
org: tokenOrg
|
|
6760
6782
|
};
|
|
6761
6783
|
}
|
|
6762
|
-
const { owner, origin
|
|
6784
|
+
const { owner, origin, prefixPath } = parseAdoOwnerAndRepo(url);
|
|
6763
6785
|
return {
|
|
6764
6786
|
org: owner,
|
|
6765
|
-
origin: prefixPath ? `${
|
|
6787
|
+
origin: prefixPath ? `${origin}/${prefixPath}` : origin
|
|
6766
6788
|
};
|
|
6767
6789
|
}
|
|
6768
6790
|
if (!tokenOrg) {
|
|
@@ -6787,17 +6809,17 @@ function isAdoOnCloud(url) {
|
|
|
6787
6809
|
return urlObj.origin.toLowerCase() === DEFUALT_ADO_ORIGIN || urlObj.hostname.toLowerCase().endsWith(".visualstudio.com");
|
|
6788
6810
|
}
|
|
6789
6811
|
async function getAdoApiClient(params) {
|
|
6790
|
-
const { origin
|
|
6812
|
+
const { origin = DEFUALT_ADO_ORIGIN, orgName } = params;
|
|
6791
6813
|
if (params.tokenType === "NONE" /* NONE */ || // note: move to public client if the token is not associated with the PAT org
|
|
6792
6814
|
// we're only doing it the ado on the cloud
|
|
6793
|
-
params.tokenType === "PAT" /* PAT */ && params.patTokenOrg !== orgName && isAdoOnCloud(
|
|
6794
|
-
return _getPublicAdoClient({ orgName, origin
|
|
6815
|
+
params.tokenType === "PAT" /* PAT */ && params.patTokenOrg !== orgName && isAdoOnCloud(origin)) {
|
|
6816
|
+
return _getPublicAdoClient({ orgName, origin });
|
|
6795
6817
|
}
|
|
6796
|
-
const orgUrl = `${
|
|
6818
|
+
const orgUrl = `${origin}/${orgName}`;
|
|
6797
6819
|
if (params.tokenType === "OAUTH" /* OAUTH */) {
|
|
6798
|
-
if (!isAdoOnCloud(
|
|
6820
|
+
if (!isAdoOnCloud(origin)) {
|
|
6799
6821
|
throw new Error(
|
|
6800
|
-
`Oauth token is not supported for ADO on prem - ${
|
|
6822
|
+
`Oauth token is not supported for ADO on prem - ${origin} `
|
|
6801
6823
|
);
|
|
6802
6824
|
}
|
|
6803
6825
|
const connection2 = new api.WebApi(
|
|
@@ -6833,7 +6855,7 @@ function getAdoTokenInfo(token) {
|
|
|
6833
6855
|
async function getAdoClientParams(params) {
|
|
6834
6856
|
const { url, accessToken, tokenOrg } = params;
|
|
6835
6857
|
const adoTokenInfo = getAdoTokenInfo(accessToken);
|
|
6836
|
-
const { org, origin
|
|
6858
|
+
const { org, origin } = await getAdoConnectData({
|
|
6837
6859
|
url,
|
|
6838
6860
|
tokenOrg,
|
|
6839
6861
|
adoTokenInfo
|
|
@@ -6842,14 +6864,14 @@ async function getAdoClientParams(params) {
|
|
|
6842
6864
|
case "NONE" /* NONE */:
|
|
6843
6865
|
return {
|
|
6844
6866
|
tokenType: "NONE" /* NONE */,
|
|
6845
|
-
origin
|
|
6867
|
+
origin,
|
|
6846
6868
|
orgName: org.toLowerCase()
|
|
6847
6869
|
};
|
|
6848
6870
|
case "OAUTH" /* OAUTH */: {
|
|
6849
6871
|
return {
|
|
6850
6872
|
tokenType: "OAUTH" /* OAUTH */,
|
|
6851
6873
|
accessToken: adoTokenInfo.accessToken,
|
|
6852
|
-
origin
|
|
6874
|
+
origin,
|
|
6853
6875
|
orgName: org.toLowerCase()
|
|
6854
6876
|
};
|
|
6855
6877
|
}
|
|
@@ -6858,7 +6880,7 @@ async function getAdoClientParams(params) {
|
|
|
6858
6880
|
tokenType: "PAT" /* PAT */,
|
|
6859
6881
|
accessToken: adoTokenInfo.accessToken,
|
|
6860
6882
|
patTokenOrg: z17.string().parse(tokenOrg).toLowerCase(),
|
|
6861
|
-
origin
|
|
6883
|
+
origin,
|
|
6862
6884
|
orgName: org.toLowerCase()
|
|
6863
6885
|
};
|
|
6864
6886
|
}
|
|
@@ -7044,7 +7066,7 @@ async function getAdoSdk(params) {
|
|
|
7044
7066
|
}) {
|
|
7045
7067
|
const { owner, repo, projectName, prefixPath } = parseAdoOwnerAndRepo(repoUrl);
|
|
7046
7068
|
const url = new URL(repoUrl);
|
|
7047
|
-
const
|
|
7069
|
+
const origin = url.origin.toLowerCase().endsWith(".visualstudio.com") ? DEFUALT_ADO_ORIGIN : url.origin.toLowerCase();
|
|
7048
7070
|
const params2 = `path=/&versionDescriptor[versionOptions]=0&versionDescriptor[versionType]=commit&versionDescriptor[version]=${branch}&resolveLfs=true&$format=zip&api-version=5.0&download=true`;
|
|
7049
7071
|
const path27 = [
|
|
7050
7072
|
prefixPath,
|
|
@@ -7057,7 +7079,7 @@ async function getAdoSdk(params) {
|
|
|
7057
7079
|
"items",
|
|
7058
7080
|
"items"
|
|
7059
7081
|
].filter(Boolean).join("/");
|
|
7060
|
-
return new URL(`${path27}?${params2}`,
|
|
7082
|
+
return new URL(`${path27}?${params2}`, origin).toString();
|
|
7061
7083
|
},
|
|
7062
7084
|
async getAdoBranchList({ repoUrl }) {
|
|
7063
7085
|
try {
|
|
@@ -7338,7 +7360,8 @@ async function getAdoSdk(params) {
|
|
|
7338
7360
|
async function getAdoRepoList({
|
|
7339
7361
|
orgName,
|
|
7340
7362
|
tokenOrg,
|
|
7341
|
-
accessToken
|
|
7363
|
+
accessToken,
|
|
7364
|
+
url
|
|
7342
7365
|
}) {
|
|
7343
7366
|
let orgs = [];
|
|
7344
7367
|
const adoTokenInfo = getAdoTokenInfo(accessToken);
|
|
@@ -7348,10 +7371,11 @@ async function getAdoRepoList({
|
|
|
7348
7371
|
if (adoTokenInfo.type === "OAUTH" /* OAUTH */) {
|
|
7349
7372
|
orgs = await getOrgsForOauthToken({ oauthToken: accessToken });
|
|
7350
7373
|
}
|
|
7351
|
-
|
|
7374
|
+
const effectiveOrgName = orgName || tokenOrg;
|
|
7375
|
+
if (orgs.length === 0 && !effectiveOrgName) {
|
|
7352
7376
|
throw new Error(`no orgs for ADO`);
|
|
7353
|
-
} else if (orgs.length === 0 &&
|
|
7354
|
-
orgs = [
|
|
7377
|
+
} else if (orgs.length === 0 && effectiveOrgName) {
|
|
7378
|
+
orgs = [effectiveOrgName];
|
|
7355
7379
|
}
|
|
7356
7380
|
const repos = (await Promise.allSettled(
|
|
7357
7381
|
orgs.map(async (org) => {
|
|
@@ -7359,7 +7383,7 @@ async function getAdoRepoList({
|
|
|
7359
7383
|
...await getAdoClientParams({
|
|
7360
7384
|
accessToken,
|
|
7361
7385
|
tokenOrg: tokenOrg || org,
|
|
7362
|
-
url
|
|
7386
|
+
url
|
|
7363
7387
|
}),
|
|
7364
7388
|
orgName: org
|
|
7365
7389
|
});
|
|
@@ -7627,15 +7651,11 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7627
7651
|
}
|
|
7628
7652
|
async getRepoList(scmOrg) {
|
|
7629
7653
|
this._validateAccessToken();
|
|
7630
|
-
if (this.url && new URL(this.url).origin !== scmCloudUrl.Ado) {
|
|
7631
|
-
throw new Error(
|
|
7632
|
-
`Oauth token is not supported for ADO on prem - ${origin} `
|
|
7633
|
-
);
|
|
7634
|
-
}
|
|
7635
7654
|
return getAdoRepoList({
|
|
7636
7655
|
orgName: scmOrg,
|
|
7637
7656
|
accessToken: this.accessToken,
|
|
7638
|
-
tokenOrg: this.scmOrg
|
|
7657
|
+
tokenOrg: this.scmOrg,
|
|
7658
|
+
url: this.url
|
|
7639
7659
|
});
|
|
7640
7660
|
}
|
|
7641
7661
|
async getBranchList() {
|
|
@@ -7840,15 +7860,11 @@ var AdoSCMLib = class extends SCMLib {
|
|
|
7840
7860
|
// getRepositoriesPaged() API per-project for true server-side pagination.
|
|
7841
7861
|
async searchRepos(params) {
|
|
7842
7862
|
this._validateAccessToken();
|
|
7843
|
-
if (this.url && new URL(this.url).origin !== scmCloudUrl.Ado) {
|
|
7844
|
-
throw new Error(
|
|
7845
|
-
`Oauth token is not supported for ADO on prem - ${this.url}`
|
|
7846
|
-
);
|
|
7847
|
-
}
|
|
7848
7863
|
const allRepos = await getAdoRepoList({
|
|
7849
7864
|
orgName: params.scmOrg,
|
|
7850
7865
|
accessToken: this.accessToken,
|
|
7851
|
-
tokenOrg: this.scmOrg
|
|
7866
|
+
tokenOrg: this.scmOrg,
|
|
7867
|
+
url: this.url
|
|
7852
7868
|
});
|
|
7853
7869
|
allRepos.sort((a, b) => {
|
|
7854
7870
|
const dateA = a.repoUpdatedAt ? new Date(a.repoUpdatedAt).getTime() : 0;
|
|
@@ -8945,17 +8961,30 @@ function getGithubSdk(params = {}) {
|
|
|
8945
8961
|
},
|
|
8946
8962
|
async getGithubRepoList() {
|
|
8947
8963
|
try {
|
|
8948
|
-
const
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8964
|
+
const allRepos = [];
|
|
8965
|
+
let page = 1;
|
|
8966
|
+
const perPage = 100;
|
|
8967
|
+
let hasMore = true;
|
|
8968
|
+
while (hasMore) {
|
|
8969
|
+
const githubRepos = await octokit.request(GET_USER_REPOS, {
|
|
8970
|
+
sort: "updated",
|
|
8971
|
+
per_page: perPage,
|
|
8972
|
+
page
|
|
8973
|
+
});
|
|
8974
|
+
for (const repo of githubRepos.data) {
|
|
8975
|
+
allRepos.push({
|
|
8976
|
+
repoName: repo.name,
|
|
8977
|
+
repoUrl: repo.html_url,
|
|
8978
|
+
repoOwner: repo.owner.login,
|
|
8979
|
+
repoLanguages: repo.language ? [repo.language] : [],
|
|
8980
|
+
repoIsPublic: !repo.private,
|
|
8981
|
+
repoUpdatedAt: repo.updated_at
|
|
8982
|
+
});
|
|
8983
|
+
}
|
|
8984
|
+
hasMore = githubRepos.data.length >= perPage;
|
|
8985
|
+
page++;
|
|
8986
|
+
}
|
|
8987
|
+
return allRepos;
|
|
8959
8988
|
} catch (e) {
|
|
8960
8989
|
if (e instanceof RequestError && e.status === 401) {
|
|
8961
8990
|
console.warn(
|
|
@@ -9378,7 +9407,7 @@ function getGithubSdk(params = {}) {
|
|
|
9378
9407
|
if (!org) {
|
|
9379
9408
|
throw new Error("Organization is required for repository search");
|
|
9380
9409
|
}
|
|
9381
|
-
const query = `org:${org}`;
|
|
9410
|
+
const query = `org:${org} fork:true`;
|
|
9382
9411
|
const githubSortField = sort.field === "name" ? void 0 : "updated";
|
|
9383
9412
|
const response = await octokit.rest.search.repos({
|
|
9384
9413
|
q: query,
|
|
@@ -10139,7 +10168,9 @@ async function searchGitlabProjects({
|
|
|
10139
10168
|
url,
|
|
10140
10169
|
accessToken,
|
|
10141
10170
|
perPage = 20,
|
|
10142
|
-
page = 1
|
|
10171
|
+
page = 1,
|
|
10172
|
+
orderBy = "created_at",
|
|
10173
|
+
sort = "desc"
|
|
10143
10174
|
}) {
|
|
10144
10175
|
if (perPage > GITLAB_MAX_PER_PAGE) {
|
|
10145
10176
|
throw new Error(
|
|
@@ -10147,31 +10178,28 @@ async function searchGitlabProjects({
|
|
|
10147
10178
|
);
|
|
10148
10179
|
}
|
|
10149
10180
|
const api2 = getGitBeaker({ url, gitlabAuthToken: accessToken });
|
|
10181
|
+
const fetchProjects = (effectiveOrderBy) => api2.Projects.all({
|
|
10182
|
+
membership: true,
|
|
10183
|
+
orderBy: effectiveOrderBy,
|
|
10184
|
+
sort,
|
|
10185
|
+
pagination: "offset",
|
|
10186
|
+
perPage,
|
|
10187
|
+
page,
|
|
10188
|
+
showExpanded: true
|
|
10189
|
+
});
|
|
10150
10190
|
let response;
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
}
|
|
10161
|
-
}
|
|
10162
|
-
|
|
10163
|
-
"[searchGitlabProjects] order_by=last_activity_at failed, falling back to created_at: %s",
|
|
10164
|
-
e instanceof Error ? e.message : String(e)
|
|
10165
|
-
);
|
|
10166
|
-
response = await api2.Projects.all({
|
|
10167
|
-
membership: true,
|
|
10168
|
-
orderBy: "created_at",
|
|
10169
|
-
sort: "desc",
|
|
10170
|
-
pagination: "offset",
|
|
10171
|
-
perPage,
|
|
10172
|
-
page,
|
|
10173
|
-
showExpanded: true
|
|
10174
|
-
});
|
|
10191
|
+
if (orderBy === "last_activity_at") {
|
|
10192
|
+
try {
|
|
10193
|
+
response = await fetchProjects("last_activity_at");
|
|
10194
|
+
} catch (e) {
|
|
10195
|
+
debug4(
|
|
10196
|
+
"[searchGitlabProjects] order_by=last_activity_at failed, falling back to created_at: %s",
|
|
10197
|
+
e instanceof Error ? e.message : String(e)
|
|
10198
|
+
);
|
|
10199
|
+
response = await fetchProjects("created_at");
|
|
10200
|
+
}
|
|
10201
|
+
} else {
|
|
10202
|
+
response = await fetchProjects(orderBy);
|
|
10175
10203
|
}
|
|
10176
10204
|
const projects = response.data.map((p) => ({
|
|
10177
10205
|
id: p.id,
|
|
@@ -10933,11 +10961,15 @@ var GitlabSCMLib = class extends SCMLib {
|
|
|
10933
10961
|
}
|
|
10934
10962
|
const page = parseCursorSafe(params.cursor, 1);
|
|
10935
10963
|
const perPage = params.limit || 10;
|
|
10964
|
+
const sort = params.sort || { field: "updated", order: "desc" };
|
|
10965
|
+
const orderBy = sort.field === "updated" ? "last_activity_at" : "created_at";
|
|
10936
10966
|
const { projects, hasMore } = await searchGitlabProjects({
|
|
10937
10967
|
url: this.url,
|
|
10938
10968
|
accessToken: this.accessToken,
|
|
10939
10969
|
perPage,
|
|
10940
|
-
page
|
|
10970
|
+
page,
|
|
10971
|
+
orderBy,
|
|
10972
|
+
sort: sort.order
|
|
10941
10973
|
});
|
|
10942
10974
|
const includeLanguages = params.includeLanguages !== false;
|
|
10943
10975
|
const languageMap = /* @__PURE__ */ new Map();
|
|
@@ -11127,29 +11159,29 @@ var StubSCMLib = class extends SCMLib {
|
|
|
11127
11159
|
};
|
|
11128
11160
|
|
|
11129
11161
|
// src/features/analysis/scm/scmFactory.ts
|
|
11130
|
-
async function createScmLib({ url, accessToken, scmType, scmOrg }, { propagateExceptions = false } = {}) {
|
|
11162
|
+
async function createScmLib({ url, accessToken, scmType, scmOrg }, { propagateExceptions = false, skipValidation = false } = {}) {
|
|
11131
11163
|
const trimmedUrl = url ? url.trim().replace(/\/$/, "").replace(/.git$/i, "") : void 0;
|
|
11132
11164
|
try {
|
|
11133
11165
|
switch (scmType) {
|
|
11134
11166
|
case "GITHUB" /* GITHUB */: {
|
|
11135
11167
|
const scm = new GithubSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
11136
|
-
await scm.validateParams();
|
|
11168
|
+
if (!skipValidation) await scm.validateParams();
|
|
11137
11169
|
return scm;
|
|
11138
11170
|
}
|
|
11139
11171
|
case "GITLAB" /* GITLAB */: {
|
|
11140
11172
|
const scm = new GitlabSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
11141
|
-
await scm.validateParams();
|
|
11173
|
+
if (!skipValidation) await scm.validateParams();
|
|
11142
11174
|
return scm;
|
|
11143
11175
|
}
|
|
11144
11176
|
case "ADO" /* ADO */: {
|
|
11145
11177
|
const scm = new AdoSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
11146
11178
|
await scm.getAdoSdk();
|
|
11147
|
-
await scm.validateParams();
|
|
11179
|
+
if (!skipValidation) await scm.validateParams();
|
|
11148
11180
|
return scm;
|
|
11149
11181
|
}
|
|
11150
11182
|
case "BITBUCKET" /* BITBUCKET */: {
|
|
11151
11183
|
const scm = new BitbucketSCMLib(trimmedUrl, accessToken, scmOrg);
|
|
11152
|
-
await scm.validateParams();
|
|
11184
|
+
if (!skipValidation) await scm.validateParams();
|
|
11153
11185
|
return scm;
|
|
11154
11186
|
}
|
|
11155
11187
|
}
|
|
@@ -13659,8 +13691,21 @@ async function uploadAiBlameCommandHandler(args) {
|
|
|
13659
13691
|
await uploadAiBlameHandler({ args });
|
|
13660
13692
|
}
|
|
13661
13693
|
|
|
13694
|
+
// src/utils/with-timeout.ts
|
|
13695
|
+
import { setTimeout as delay } from "timers/promises";
|
|
13696
|
+
function withTimeout(promise, ms, label) {
|
|
13697
|
+
const ac = new AbortController();
|
|
13698
|
+
return Promise.race([
|
|
13699
|
+
promise.finally(() => ac.abort()),
|
|
13700
|
+
delay(ms, void 0, { signal: ac.signal }).then(() => {
|
|
13701
|
+
throw new Error(`${label} timed out after ${ms}ms`);
|
|
13702
|
+
})
|
|
13703
|
+
]);
|
|
13704
|
+
}
|
|
13705
|
+
|
|
13662
13706
|
// src/features/analysis/graphql/tracy-batch-upload.ts
|
|
13663
13707
|
var debug10 = Debug9("mobbdev:tracy-batch-upload");
|
|
13708
|
+
var BATCH_TIMEOUT_MS = 3e4;
|
|
13664
13709
|
async function sanitizeRawData(rawData) {
|
|
13665
13710
|
try {
|
|
13666
13711
|
const sanitized = await sanitizeData(rawData);
|
|
@@ -13677,6 +13722,7 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
|
|
|
13677
13722
|
const repositoryUrl = await getRepositoryUrl(workingDir);
|
|
13678
13723
|
const { computerName, userName } = getSystemInfo();
|
|
13679
13724
|
const clientVersion = packageJson.version;
|
|
13725
|
+
debug10("[step:sanitize] Sanitizing %d records", rawRecords.length);
|
|
13680
13726
|
const serializedRawDataByIndex = /* @__PURE__ */ new Map();
|
|
13681
13727
|
const records = await Promise.all(
|
|
13682
13728
|
rawRecords.map(async (record, index) => {
|
|
@@ -13696,21 +13742,47 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
|
|
|
13696
13742
|
);
|
|
13697
13743
|
const recordsWithRawData = rawRecords.map((r, i) => ({ recordId: r.recordId, index: i })).filter((entry) => serializedRawDataByIndex.has(entry.index));
|
|
13698
13744
|
if (recordsWithRawData.length > 0) {
|
|
13699
|
-
debug10(
|
|
13700
|
-
|
|
13745
|
+
debug10(
|
|
13746
|
+
"[step:s3-url] Requesting presigned URL for %d rawData files",
|
|
13747
|
+
recordsWithRawData.length
|
|
13748
|
+
);
|
|
13749
|
+
let uploadUrlResult;
|
|
13750
|
+
try {
|
|
13751
|
+
uploadUrlResult = await withTimeout(
|
|
13752
|
+
client.getTracyRawDataUploadUrl(),
|
|
13753
|
+
BATCH_TIMEOUT_MS,
|
|
13754
|
+
"[step:s3-url] getTracyRawDataUploadUrl"
|
|
13755
|
+
);
|
|
13756
|
+
} catch (err) {
|
|
13757
|
+
return {
|
|
13758
|
+
ok: false,
|
|
13759
|
+
errors: [
|
|
13760
|
+
`[step:s3-url] Failed to fetch S3 upload URL: ${err.message}`
|
|
13761
|
+
]
|
|
13762
|
+
};
|
|
13763
|
+
}
|
|
13701
13764
|
const { url, uploadFieldsJSON, keyPrefix } = uploadUrlResult.getTracyRawDataUploadUrl;
|
|
13702
13765
|
if (!url || !uploadFieldsJSON || !keyPrefix) {
|
|
13703
13766
|
return {
|
|
13704
13767
|
ok: false,
|
|
13705
|
-
errors: [
|
|
13768
|
+
errors: [
|
|
13769
|
+
`[step:s3-url] Missing S3 upload fields (url=${!!url}, fields=${!!uploadFieldsJSON}, prefix=${!!keyPrefix})`
|
|
13770
|
+
]
|
|
13706
13771
|
};
|
|
13707
13772
|
}
|
|
13708
13773
|
let uploadFields;
|
|
13709
13774
|
try {
|
|
13710
13775
|
uploadFields = JSON.parse(uploadFieldsJSON);
|
|
13711
13776
|
} catch {
|
|
13712
|
-
return {
|
|
13777
|
+
return {
|
|
13778
|
+
ok: false,
|
|
13779
|
+
errors: ["[step:s3-url] Malformed uploadFieldsJSON from server"]
|
|
13780
|
+
};
|
|
13713
13781
|
}
|
|
13782
|
+
debug10(
|
|
13783
|
+
"[step:s3-upload] Uploading %d files to S3",
|
|
13784
|
+
recordsWithRawData.length
|
|
13785
|
+
);
|
|
13714
13786
|
const uploadResults = await Promise.allSettled(
|
|
13715
13787
|
recordsWithRawData.map(async (entry) => {
|
|
13716
13788
|
const rawDataJson = serializedRawDataByIndex.get(entry.index);
|
|
@@ -13719,48 +13791,59 @@ async function prepareAndSendTracyRecords(client, rawRecords, workingDir) {
|
|
|
13719
13791
|
return;
|
|
13720
13792
|
}
|
|
13721
13793
|
const uploadKey = `${keyPrefix}${entry.recordId}.json`;
|
|
13722
|
-
await
|
|
13723
|
-
|
|
13724
|
-
|
|
13725
|
-
|
|
13726
|
-
|
|
13727
|
-
|
|
13794
|
+
await withTimeout(
|
|
13795
|
+
uploadFile({
|
|
13796
|
+
file: Buffer.from(rawDataJson, "utf-8"),
|
|
13797
|
+
url,
|
|
13798
|
+
uploadKey,
|
|
13799
|
+
uploadFields
|
|
13800
|
+
}),
|
|
13801
|
+
BATCH_TIMEOUT_MS,
|
|
13802
|
+
`[step:s3-upload] uploadFile ${entry.recordId}`
|
|
13803
|
+
);
|
|
13728
13804
|
records[entry.index].rawDataS3Key = uploadKey;
|
|
13729
13805
|
})
|
|
13730
13806
|
);
|
|
13731
13807
|
const uploadErrors = uploadResults.filter((r) => r.status === "rejected").map((r) => r.reason.message);
|
|
13732
13808
|
if (uploadErrors.length > 0) {
|
|
13733
|
-
debug10("S3 upload errors: %O", uploadErrors);
|
|
13809
|
+
debug10("[step:s3-upload] S3 upload errors: %O", uploadErrors);
|
|
13734
13810
|
}
|
|
13735
13811
|
const missingS3Keys = recordsWithRawData.filter(
|
|
13736
13812
|
(entry) => !records[entry.index].rawDataS3Key
|
|
13737
13813
|
);
|
|
13738
13814
|
if (missingS3Keys.length > 0) {
|
|
13739
13815
|
const missingIds = missingS3Keys.map((e) => e.recordId);
|
|
13740
|
-
debug10("Records missing S3 keys
|
|
13816
|
+
debug10("[step:s3-upload] Records missing S3 keys: %O", missingIds);
|
|
13741
13817
|
return {
|
|
13742
13818
|
ok: false,
|
|
13743
13819
|
errors: [
|
|
13744
|
-
`Failed to upload rawData
|
|
13820
|
+
`[step:s3-upload] Failed to upload rawData for ${missingS3Keys.length} record(s): ${missingIds.join(", ")}`,
|
|
13745
13821
|
...uploadErrors
|
|
13746
13822
|
]
|
|
13747
13823
|
};
|
|
13748
13824
|
}
|
|
13749
|
-
debug10("S3 uploads complete");
|
|
13825
|
+
debug10("[step:s3-upload] S3 uploads complete");
|
|
13750
13826
|
}
|
|
13827
|
+
debug10("[step:gql-submit] Submitting %d records via GraphQL", records.length);
|
|
13751
13828
|
try {
|
|
13752
|
-
const result = await
|
|
13829
|
+
const result = await withTimeout(
|
|
13830
|
+
client.uploadTracyRecords({ records }),
|
|
13831
|
+
BATCH_TIMEOUT_MS,
|
|
13832
|
+
"[step:gql-submit] uploadTracyRecords"
|
|
13833
|
+
);
|
|
13753
13834
|
if (result.uploadTracyRecords.status !== "OK") {
|
|
13754
13835
|
return {
|
|
13755
13836
|
ok: false,
|
|
13756
|
-
errors: [
|
|
13837
|
+
errors: [
|
|
13838
|
+
`[step:gql-submit] Server rejected: ${result.uploadTracyRecords.error ?? "Unknown server error"}`
|
|
13839
|
+
]
|
|
13757
13840
|
};
|
|
13758
13841
|
}
|
|
13759
13842
|
} catch (err) {
|
|
13760
|
-
debug10("Upload failed: %s", err.message);
|
|
13843
|
+
debug10("[step:gql-submit] Upload failed: %s", err.message);
|
|
13761
13844
|
return {
|
|
13762
13845
|
ok: false,
|
|
13763
|
-
errors: [err.message]
|
|
13846
|
+
errors: [`[step:gql-submit] ${err.message}`]
|
|
13764
13847
|
};
|
|
13765
13848
|
}
|
|
13766
13849
|
return { ok: true, errors: null };
|
|
@@ -16297,7 +16380,7 @@ async function analyzeHandler(args) {
|
|
|
16297
16380
|
|
|
16298
16381
|
// src/features/claude_code/data_collector.ts
|
|
16299
16382
|
import { createHash as createHash2 } from "crypto";
|
|
16300
|
-
import { open as open4, readdir, readFile, unlink } from "fs/promises";
|
|
16383
|
+
import { access, open as open4, readdir, readFile, unlink } from "fs/promises";
|
|
16301
16384
|
import path13 from "path";
|
|
16302
16385
|
import { z as z33 } from "zod";
|
|
16303
16386
|
init_client_generates();
|
|
@@ -16582,8 +16665,8 @@ function createLogger(config2) {
|
|
|
16582
16665
|
}
|
|
16583
16666
|
|
|
16584
16667
|
// src/features/claude_code/hook_logger.ts
|
|
16585
|
-
var DD_RUM_TOKEN = true ? "" : "";
|
|
16586
|
-
var CLI_VERSION = true ? "1.2.
|
|
16668
|
+
var DD_RUM_TOKEN = true ? "pubf59c0182545bfb4c299175119f1abf9b" : "";
|
|
16669
|
+
var CLI_VERSION = true ? "1.2.57" : "unknown";
|
|
16587
16670
|
var NAMESPACE = "mobbdev-claude-code-hook-logs";
|
|
16588
16671
|
function createHookLogger(scopePath) {
|
|
16589
16672
|
return createLogger({
|
|
@@ -16622,10 +16705,15 @@ function createScopedHookLog(scopePath) {
|
|
|
16622
16705
|
}
|
|
16623
16706
|
|
|
16624
16707
|
// src/features/claude_code/data_collector.ts
|
|
16708
|
+
var GLOBAL_COOLDOWN_MS = 5e3;
|
|
16625
16709
|
var HOOK_COOLDOWN_MS = 1e4;
|
|
16710
|
+
var ACTIVE_LOCK_TTL_MS = 6e4;
|
|
16711
|
+
var GQL_AUTH_TIMEOUT_MS = 15e3;
|
|
16626
16712
|
var STALE_KEY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
16627
16713
|
var CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
16714
|
+
var MAX_ENTRIES_PER_INVOCATION = 100;
|
|
16628
16715
|
var COOLDOWN_KEY = "lastHookRunAt";
|
|
16716
|
+
var ACTIVE_KEY = "hookActiveAt";
|
|
16629
16717
|
var HookDataSchema = z33.object({
|
|
16630
16718
|
session_id: z33.string(),
|
|
16631
16719
|
transcript_path: z33.string(),
|
|
@@ -16658,9 +16746,12 @@ async function readStdinData() {
|
|
|
16658
16746
|
clearTimeout(timer);
|
|
16659
16747
|
try {
|
|
16660
16748
|
const parsedData = JSON.parse(inputData);
|
|
16661
|
-
hookLog.debug(
|
|
16662
|
-
|
|
16663
|
-
|
|
16749
|
+
hookLog.debug(
|
|
16750
|
+
{
|
|
16751
|
+
data: { keys: Object.keys(parsedData) }
|
|
16752
|
+
},
|
|
16753
|
+
"Parsed stdin data"
|
|
16754
|
+
);
|
|
16664
16755
|
resolve(parsedData);
|
|
16665
16756
|
} catch (error) {
|
|
16666
16757
|
const msg = `Failed to parse JSON from stdin: ${error.message}`;
|
|
@@ -16672,7 +16763,10 @@ async function readStdinData() {
|
|
|
16672
16763
|
if (settled) return;
|
|
16673
16764
|
settled = true;
|
|
16674
16765
|
clearTimeout(timer);
|
|
16675
|
-
hookLog.error(
|
|
16766
|
+
hookLog.error(
|
|
16767
|
+
{ data: { error: error.message } },
|
|
16768
|
+
"Error reading from stdin"
|
|
16769
|
+
);
|
|
16676
16770
|
reject(new Error(`Error reading from stdin: ${error.message}`));
|
|
16677
16771
|
});
|
|
16678
16772
|
});
|
|
@@ -16689,7 +16783,63 @@ function getCursorKey(transcriptPath) {
|
|
|
16689
16783
|
const hash = createHash2("sha256").update(transcriptPath).digest("hex").slice(0, 12);
|
|
16690
16784
|
return `cursor.${hash}`;
|
|
16691
16785
|
}
|
|
16786
|
+
async function resolveTranscriptPath(transcriptPath, sessionId) {
|
|
16787
|
+
try {
|
|
16788
|
+
await access(transcriptPath);
|
|
16789
|
+
return transcriptPath;
|
|
16790
|
+
} catch {
|
|
16791
|
+
}
|
|
16792
|
+
const filename = path13.basename(transcriptPath);
|
|
16793
|
+
const dirName = path13.basename(path13.dirname(transcriptPath));
|
|
16794
|
+
const projectsDir = path13.dirname(path13.dirname(transcriptPath));
|
|
16795
|
+
const baseDirName = dirName.replace(/[-.]claude-worktrees-.+$/, "");
|
|
16796
|
+
if (baseDirName !== dirName) {
|
|
16797
|
+
const candidate = path13.join(projectsDir, baseDirName, filename);
|
|
16798
|
+
try {
|
|
16799
|
+
await access(candidate);
|
|
16800
|
+
hookLog.info(
|
|
16801
|
+
{
|
|
16802
|
+
data: {
|
|
16803
|
+
original: transcriptPath,
|
|
16804
|
+
resolved: candidate,
|
|
16805
|
+
sessionId,
|
|
16806
|
+
method: "worktree-strip"
|
|
16807
|
+
}
|
|
16808
|
+
},
|
|
16809
|
+
"Transcript path resolved via fallback"
|
|
16810
|
+
);
|
|
16811
|
+
return candidate;
|
|
16812
|
+
} catch {
|
|
16813
|
+
}
|
|
16814
|
+
}
|
|
16815
|
+
try {
|
|
16816
|
+
const dirs = await readdir(projectsDir);
|
|
16817
|
+
for (const dir of dirs) {
|
|
16818
|
+
if (dir === dirName) continue;
|
|
16819
|
+
const candidate = path13.join(projectsDir, dir, filename);
|
|
16820
|
+
try {
|
|
16821
|
+
await access(candidate);
|
|
16822
|
+
hookLog.info(
|
|
16823
|
+
{
|
|
16824
|
+
data: {
|
|
16825
|
+
original: transcriptPath,
|
|
16826
|
+
resolved: candidate,
|
|
16827
|
+
sessionId,
|
|
16828
|
+
method: "sibling-scan"
|
|
16829
|
+
}
|
|
16830
|
+
},
|
|
16831
|
+
"Transcript path resolved via fallback"
|
|
16832
|
+
);
|
|
16833
|
+
return candidate;
|
|
16834
|
+
} catch {
|
|
16835
|
+
}
|
|
16836
|
+
}
|
|
16837
|
+
} catch {
|
|
16838
|
+
}
|
|
16839
|
+
return transcriptPath;
|
|
16840
|
+
}
|
|
16692
16841
|
async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore) {
|
|
16842
|
+
transcriptPath = await resolveTranscriptPath(transcriptPath, sessionId);
|
|
16693
16843
|
const cursor = sessionStore.get(getCursorKey(transcriptPath));
|
|
16694
16844
|
let content;
|
|
16695
16845
|
let fileSize;
|
|
@@ -16700,8 +16850,8 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
16700
16850
|
const stat = await fh.stat();
|
|
16701
16851
|
fileSize = stat.size;
|
|
16702
16852
|
if (cursor.byteOffset >= stat.size) {
|
|
16703
|
-
hookLog.info("No new data in transcript file"
|
|
16704
|
-
return { entries: [], fileSize };
|
|
16853
|
+
hookLog.info({ data: { sessionId } }, "No new data in transcript file");
|
|
16854
|
+
return { entries: [], endByteOffset: fileSize };
|
|
16705
16855
|
}
|
|
16706
16856
|
const buf = Buffer.alloc(stat.size - cursor.byteOffset);
|
|
16707
16857
|
await fh.read(buf, 0, buf.length, cursor.byteOffset);
|
|
@@ -16710,53 +16860,84 @@ async function readNewTranscriptEntries(transcriptPath, sessionId, sessionStore)
|
|
|
16710
16860
|
await fh.close();
|
|
16711
16861
|
}
|
|
16712
16862
|
lineIndexOffset = cursor.byteOffset;
|
|
16713
|
-
hookLog.debug(
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16863
|
+
hookLog.debug(
|
|
16864
|
+
{
|
|
16865
|
+
data: {
|
|
16866
|
+
transcriptPath,
|
|
16867
|
+
byteOffset: cursor.byteOffset,
|
|
16868
|
+
bytesRead: content.length
|
|
16869
|
+
}
|
|
16870
|
+
},
|
|
16871
|
+
"Read transcript file from offset"
|
|
16872
|
+
);
|
|
16718
16873
|
} else {
|
|
16719
16874
|
content = await readFile(transcriptPath, "utf-8");
|
|
16720
16875
|
fileSize = Buffer.byteLength(content, "utf-8");
|
|
16721
16876
|
lineIndexOffset = 0;
|
|
16722
|
-
hookLog.debug(
|
|
16723
|
-
transcriptPath,
|
|
16724
|
-
|
|
16725
|
-
|
|
16877
|
+
hookLog.debug(
|
|
16878
|
+
{ data: { transcriptPath, totalBytes: fileSize } },
|
|
16879
|
+
"Read full transcript file"
|
|
16880
|
+
);
|
|
16726
16881
|
}
|
|
16727
|
-
const
|
|
16882
|
+
const startOffset = cursor?.byteOffset ?? 0;
|
|
16883
|
+
const allLines = content.split("\n");
|
|
16728
16884
|
const parsed = [];
|
|
16729
16885
|
let malformedLines = 0;
|
|
16730
|
-
|
|
16886
|
+
let bytesConsumed = 0;
|
|
16887
|
+
let parsedLineIndex = 0;
|
|
16888
|
+
for (let i = 0; i < allLines.length; i++) {
|
|
16889
|
+
const line = allLines[i];
|
|
16890
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (i < allLines.length - 1 ? 1 : 0);
|
|
16891
|
+
if (parsed.length >= MAX_ENTRIES_PER_INVOCATION) break;
|
|
16892
|
+
bytesConsumed += lineBytes;
|
|
16893
|
+
if (line.trim().length === 0) continue;
|
|
16731
16894
|
try {
|
|
16732
|
-
const entry = JSON.parse(
|
|
16895
|
+
const entry = JSON.parse(line);
|
|
16733
16896
|
const recordId = entry.uuid ?? generateSyntheticId(
|
|
16734
16897
|
entry.sessionId,
|
|
16735
16898
|
entry.timestamp,
|
|
16736
16899
|
entry.type,
|
|
16737
|
-
lineIndexOffset +
|
|
16900
|
+
lineIndexOffset + parsedLineIndex
|
|
16738
16901
|
);
|
|
16739
16902
|
parsed.push({ ...entry, _recordId: recordId });
|
|
16740
16903
|
} catch {
|
|
16741
16904
|
malformedLines++;
|
|
16742
16905
|
}
|
|
16906
|
+
parsedLineIndex++;
|
|
16743
16907
|
}
|
|
16908
|
+
const endByteOffset = startOffset + bytesConsumed;
|
|
16909
|
+
const capped = parsed.length >= MAX_ENTRIES_PER_INVOCATION;
|
|
16744
16910
|
if (malformedLines > 0) {
|
|
16745
|
-
hookLog.warn(
|
|
16911
|
+
hookLog.warn(
|
|
16912
|
+
{ data: { malformedLines, transcriptPath } },
|
|
16913
|
+
"Skipped malformed lines"
|
|
16914
|
+
);
|
|
16746
16915
|
}
|
|
16747
|
-
if (
|
|
16748
|
-
hookLog.info(
|
|
16749
|
-
|
|
16750
|
-
|
|
16751
|
-
|
|
16916
|
+
if (capped) {
|
|
16917
|
+
hookLog.info(
|
|
16918
|
+
{
|
|
16919
|
+
data: {
|
|
16920
|
+
sessionId,
|
|
16921
|
+
entriesParsed: parsed.length,
|
|
16922
|
+
totalLines: allLines.length
|
|
16923
|
+
}
|
|
16924
|
+
},
|
|
16925
|
+
"Capped at MAX_ENTRIES_PER_INVOCATION, remaining entries deferred"
|
|
16926
|
+
);
|
|
16927
|
+
} else if (!cursor) {
|
|
16928
|
+
hookLog.info(
|
|
16929
|
+
{ data: { sessionId, totalEntries: parsed.length } },
|
|
16930
|
+
"First invocation for session \u2014 uploading all entries"
|
|
16931
|
+
);
|
|
16752
16932
|
} else {
|
|
16753
|
-
hookLog.info(
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
16757
|
-
|
|
16933
|
+
hookLog.info(
|
|
16934
|
+
{
|
|
16935
|
+
data: { sessionId, byteOffset: startOffset, newEntries: parsed.length }
|
|
16936
|
+
},
|
|
16937
|
+
"Resuming from byte offset"
|
|
16938
|
+
);
|
|
16758
16939
|
}
|
|
16759
|
-
return { entries: parsed,
|
|
16940
|
+
return { entries: parsed, endByteOffset };
|
|
16760
16941
|
}
|
|
16761
16942
|
var FILTERED_PROGRESS_SUBTYPES = /* @__PURE__ */ new Set([
|
|
16762
16943
|
// Incremental streaming output from running bash commands, emitted every
|
|
@@ -16851,7 +17032,7 @@ async function cleanupStaleSessions(sessionStore) {
|
|
|
16851
17032
|
}
|
|
16852
17033
|
}
|
|
16853
17034
|
if (deletedCount > 0) {
|
|
16854
|
-
hookLog.info("Cleaned up stale session files"
|
|
17035
|
+
hookLog.info({ data: { deletedCount } }, "Cleaned up stale session files");
|
|
16855
17036
|
}
|
|
16856
17037
|
} catch {
|
|
16857
17038
|
}
|
|
@@ -16859,31 +17040,61 @@ async function cleanupStaleSessions(sessionStore) {
|
|
|
16859
17040
|
}
|
|
16860
17041
|
async function processAndUploadTranscriptEntries() {
|
|
16861
17042
|
hookLog.info("Hook invoked");
|
|
17043
|
+
const globalLastRun = configStore.get("claudeCode.globalLastHookRunAt");
|
|
17044
|
+
const globalNow = Date.now();
|
|
17045
|
+
if (globalLastRun && globalNow - globalLastRun < GLOBAL_COOLDOWN_MS) {
|
|
17046
|
+
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17047
|
+
}
|
|
17048
|
+
configStore.set("claudeCode.globalLastHookRunAt", globalNow);
|
|
16862
17049
|
const rawData = await readStdinData();
|
|
16863
17050
|
const hookData = validateHookData(rawData);
|
|
16864
17051
|
const sessionStore = createSessionConfigStore(hookData.session_id);
|
|
16865
17052
|
await cleanupStaleSessions(sessionStore);
|
|
17053
|
+
const now = Date.now();
|
|
16866
17054
|
const lastRunAt = sessionStore.get(COOLDOWN_KEY);
|
|
16867
|
-
if (lastRunAt &&
|
|
17055
|
+
if (lastRunAt && now - lastRunAt < HOOK_COOLDOWN_MS) {
|
|
17056
|
+
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
17057
|
+
}
|
|
17058
|
+
const activeAt = sessionStore.get(ACTIVE_KEY);
|
|
17059
|
+
if (activeAt && now - activeAt < ACTIVE_LOCK_TTL_MS) {
|
|
17060
|
+
const activeDuration = now - activeAt;
|
|
17061
|
+
if (activeDuration > HOOK_COOLDOWN_MS) {
|
|
17062
|
+
hookLog.warn(
|
|
17063
|
+
{
|
|
17064
|
+
data: {
|
|
17065
|
+
activeDurationMs: activeDuration,
|
|
17066
|
+
sessionId: hookData.session_id
|
|
17067
|
+
}
|
|
17068
|
+
},
|
|
17069
|
+
"Hook still active \u2014 possible slow upload or hung process"
|
|
17070
|
+
);
|
|
17071
|
+
}
|
|
16868
17072
|
return { entriesUploaded: 0, entriesSkipped: 0, errors: 0 };
|
|
16869
17073
|
}
|
|
16870
|
-
sessionStore.set(
|
|
17074
|
+
sessionStore.set(ACTIVE_KEY, now);
|
|
17075
|
+
sessionStore.set(COOLDOWN_KEY, now);
|
|
16871
17076
|
const log2 = createScopedHookLog(hookData.cwd);
|
|
16872
|
-
log2.info(
|
|
16873
|
-
|
|
16874
|
-
|
|
16875
|
-
|
|
16876
|
-
|
|
16877
|
-
|
|
17077
|
+
log2.info(
|
|
17078
|
+
{
|
|
17079
|
+
data: {
|
|
17080
|
+
sessionId: hookData.session_id,
|
|
17081
|
+
toolName: hookData.tool_name,
|
|
17082
|
+
hookEvent: hookData.hook_event_name,
|
|
17083
|
+
cwd: hookData.cwd
|
|
17084
|
+
}
|
|
17085
|
+
},
|
|
17086
|
+
"Hook data validated"
|
|
17087
|
+
);
|
|
16878
17088
|
try {
|
|
16879
17089
|
return await processTranscript(hookData, sessionStore, log2);
|
|
16880
17090
|
} finally {
|
|
17091
|
+
sessionStore.delete(ACTIVE_KEY);
|
|
16881
17092
|
log2.flushLogs();
|
|
16882
17093
|
}
|
|
16883
17094
|
}
|
|
16884
17095
|
async function processTranscript(hookData, sessionStore, log2) {
|
|
16885
17096
|
const cursorKey = getCursorKey(hookData.transcript_path);
|
|
16886
|
-
const { entries: rawEntries,
|
|
17097
|
+
const { entries: rawEntries, endByteOffset } = await readNewTranscriptEntries(
|
|
16887
17098
|
hookData.transcript_path,
|
|
16888
17099
|
hookData.session_id,
|
|
16889
17100
|
sessionStore
|
|
@@ -16894,10 +17105,10 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
16894
17105
|
}
|
|
16895
17106
|
const { filtered: entries, filteredOut } = filterEntries(rawEntries);
|
|
16896
17107
|
if (filteredOut > 0) {
|
|
16897
|
-
log2.info(
|
|
16898
|
-
filteredOut,
|
|
16899
|
-
|
|
16900
|
-
|
|
17108
|
+
log2.info(
|
|
17109
|
+
{ data: { filteredOut, remaining: entries.length } },
|
|
17110
|
+
"Filtered out noise entries"
|
|
17111
|
+
);
|
|
16901
17112
|
}
|
|
16902
17113
|
if (entries.length === 0) {
|
|
16903
17114
|
log2.info("All entries filtered out, nothing to upload");
|
|
@@ -16905,7 +17116,7 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
16905
17116
|
const prevCursor = sessionStore.get(cursorKey);
|
|
16906
17117
|
const cursor = {
|
|
16907
17118
|
id: lastEntry._recordId,
|
|
16908
|
-
byteOffset:
|
|
17119
|
+
byteOffset: endByteOffset,
|
|
16909
17120
|
updatedAt: Date.now(),
|
|
16910
17121
|
lastModel: prevCursor?.lastModel
|
|
16911
17122
|
};
|
|
@@ -16916,10 +17127,32 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
16916
17127
|
errors: 0
|
|
16917
17128
|
};
|
|
16918
17129
|
}
|
|
16919
|
-
|
|
16920
|
-
|
|
16921
|
-
|
|
16922
|
-
|
|
17130
|
+
let gqlClient;
|
|
17131
|
+
try {
|
|
17132
|
+
gqlClient = await log2.timed(
|
|
17133
|
+
"GQL auth",
|
|
17134
|
+
() => withTimeout(
|
|
17135
|
+
getAuthenticatedGQLClient({ isSkipPrompts: true }),
|
|
17136
|
+
GQL_AUTH_TIMEOUT_MS,
|
|
17137
|
+
"GQL auth"
|
|
17138
|
+
)
|
|
17139
|
+
);
|
|
17140
|
+
} catch (err) {
|
|
17141
|
+
log2.error(
|
|
17142
|
+
{
|
|
17143
|
+
data: {
|
|
17144
|
+
error: String(err),
|
|
17145
|
+
stack: err instanceof Error ? err.stack : void 0
|
|
17146
|
+
}
|
|
17147
|
+
},
|
|
17148
|
+
"GQL auth failed"
|
|
17149
|
+
);
|
|
17150
|
+
return {
|
|
17151
|
+
entriesUploaded: 0,
|
|
17152
|
+
entriesSkipped: filteredOut,
|
|
17153
|
+
errors: entries.length
|
|
17154
|
+
};
|
|
17155
|
+
}
|
|
16923
17156
|
const cursorForModel = sessionStore.get(cursorKey);
|
|
16924
17157
|
let lastSeenModel = cursorForModel?.lastModel ?? null;
|
|
16925
17158
|
const records = entries.map((entry) => {
|
|
@@ -16943,12 +17176,17 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
16943
17176
|
rawData: rawEntry
|
|
16944
17177
|
};
|
|
16945
17178
|
});
|
|
16946
|
-
log2.info(
|
|
16947
|
-
|
|
16948
|
-
|
|
16949
|
-
|
|
16950
|
-
|
|
16951
|
-
|
|
17179
|
+
log2.info(
|
|
17180
|
+
{
|
|
17181
|
+
data: {
|
|
17182
|
+
count: records.length,
|
|
17183
|
+
skipped: filteredOut,
|
|
17184
|
+
firstRecordId: records[0]?.recordId,
|
|
17185
|
+
lastRecordId: records[records.length - 1]?.recordId
|
|
17186
|
+
}
|
|
17187
|
+
},
|
|
17188
|
+
"Uploading batch"
|
|
17189
|
+
);
|
|
16952
17190
|
const result = await log2.timed(
|
|
16953
17191
|
"Batch upload",
|
|
16954
17192
|
() => prepareAndSendTracyRecords(gqlClient, records, hookData.cwd)
|
|
@@ -16957,7 +17195,7 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
16957
17195
|
const lastRawEntry = rawEntries[rawEntries.length - 1];
|
|
16958
17196
|
const cursor = {
|
|
16959
17197
|
id: lastRawEntry._recordId,
|
|
16960
|
-
byteOffset:
|
|
17198
|
+
byteOffset: endByteOffset,
|
|
16961
17199
|
updatedAt: Date.now(),
|
|
16962
17200
|
lastModel: lastSeenModel ?? void 0
|
|
16963
17201
|
};
|
|
@@ -16972,7 +17210,10 @@ async function processTranscript(hookData, sessionStore, log2) {
|
|
|
16972
17210
|
errors: 0
|
|
16973
17211
|
};
|
|
16974
17212
|
}
|
|
16975
|
-
log2.error(
|
|
17213
|
+
log2.error(
|
|
17214
|
+
{ data: { errors: result.errors, recordCount: entries.length } },
|
|
17215
|
+
"Batch upload had errors"
|
|
17216
|
+
);
|
|
16976
17217
|
return {
|
|
16977
17218
|
entriesUploaded: 0,
|
|
16978
17219
|
entriesSkipped: filteredOut,
|
|
@@ -17115,33 +17356,48 @@ var claudeCodeProcessHookHandler = async () => {
|
|
|
17115
17356
|
}
|
|
17116
17357
|
}
|
|
17117
17358
|
process.on("uncaughtException", (error) => {
|
|
17118
|
-
hookLog.error(
|
|
17119
|
-
error: String(error),
|
|
17120
|
-
|
|
17121
|
-
|
|
17359
|
+
hookLog.error(
|
|
17360
|
+
{ data: { error: String(error), stack: error.stack } },
|
|
17361
|
+
"Uncaught exception in hook"
|
|
17362
|
+
);
|
|
17122
17363
|
void flushAndExit(1);
|
|
17123
17364
|
});
|
|
17124
17365
|
process.on("unhandledRejection", (reason) => {
|
|
17125
|
-
hookLog.error(
|
|
17126
|
-
|
|
17127
|
-
|
|
17128
|
-
|
|
17366
|
+
hookLog.error(
|
|
17367
|
+
{
|
|
17368
|
+
data: {
|
|
17369
|
+
error: String(reason),
|
|
17370
|
+
stack: reason instanceof Error ? reason.stack : void 0
|
|
17371
|
+
}
|
|
17372
|
+
},
|
|
17373
|
+
"Unhandled rejection in hook"
|
|
17374
|
+
);
|
|
17129
17375
|
void flushAndExit(1);
|
|
17130
17376
|
});
|
|
17131
17377
|
let exitCode = 0;
|
|
17132
17378
|
try {
|
|
17133
17379
|
const result = await processAndUploadTranscriptEntries();
|
|
17134
|
-
hookLog.info(
|
|
17135
|
-
|
|
17136
|
-
|
|
17137
|
-
|
|
17138
|
-
|
|
17380
|
+
hookLog.info(
|
|
17381
|
+
{
|
|
17382
|
+
data: {
|
|
17383
|
+
entriesUploaded: result.entriesUploaded,
|
|
17384
|
+
entriesSkipped: result.entriesSkipped,
|
|
17385
|
+
errors: result.errors
|
|
17386
|
+
}
|
|
17387
|
+
},
|
|
17388
|
+
"Claude Code upload complete"
|
|
17389
|
+
);
|
|
17139
17390
|
} catch (error) {
|
|
17140
17391
|
exitCode = 1;
|
|
17141
|
-
hookLog.error(
|
|
17142
|
-
|
|
17143
|
-
|
|
17144
|
-
|
|
17392
|
+
hookLog.error(
|
|
17393
|
+
{
|
|
17394
|
+
data: {
|
|
17395
|
+
error: String(error),
|
|
17396
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
17397
|
+
}
|
|
17398
|
+
},
|
|
17399
|
+
"Failed to process Claude Code hook"
|
|
17400
|
+
);
|
|
17145
17401
|
}
|
|
17146
17402
|
await flushAndExit(exitCode);
|
|
17147
17403
|
};
|