@mrkaran/hodor 0.7.0 → 0.7.1
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/{chunk-Q5AQKNSE.js → chunk-VERO4XYJ.js} +129 -32
- package/dist/chunk-VERO4XYJ.js.map +1 -0
- package/dist/cli.js +34 -14
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/review-task.md +1 -7
- package/dist/chunk-Q5AQKNSE.js.map +0 -1
|
@@ -42,7 +42,8 @@ function buildPrReviewPrompt(opts) {
|
|
|
42
42
|
previousReviewSha,
|
|
43
43
|
reviewDiffMode,
|
|
44
44
|
changedFiles = [],
|
|
45
|
-
localMode = false
|
|
45
|
+
localMode = false,
|
|
46
|
+
singleTurn = false
|
|
46
47
|
} = opts;
|
|
47
48
|
let templateText;
|
|
48
49
|
try {
|
|
@@ -92,22 +93,24 @@ function buildPrReviewPrompt(opts) {
|
|
|
92
93
|
diffExplanation = `**Three-dot syntax** shows ONLY changes introduced on the source branch, excluding changes already on \`${targetBranch}\`.`;
|
|
93
94
|
}
|
|
94
95
|
const { contextSection, notesSection, reminderSection } = buildMrSections(mrMetadata);
|
|
96
|
+
const oneTurn = singleTurn && Boolean(embeddedDiff);
|
|
95
97
|
let incrementalSection = "";
|
|
96
98
|
if (previousReviewSha) {
|
|
97
99
|
incrementalSection = `## ${reviewDiffMode === "snapshot" ? "Snapshot Delta" : "Incremental Review"} Mode
|
|
98
100
|
|
|
99
|
-
This is a follow-up review. A previous hodor review was done at commit \`${previousReviewSha.slice(0, 8)}\`. ` + (reviewDiffMode === "snapshot" ? "The branch history was rewritten, so the diff below compares that reviewed snapshot directly with the current HEAD. " : "The diff below shows ONLY changes since that review. ") + "Your job is to review that delta, not the whole MR again.\n\nRules for incremental reviews:\n1. Only report findings introduced or still affected by the new delta.\n2. Do not re-report issues that are already mentioned in existing notes unless the new delta changes the same code and the issue remains newly relevant.\n3. If the delta is small and self-contained, decide from the embedded diff and submit the review without broad repository exploration.\
|
|
101
|
+
This is a follow-up review. A previous hodor review was done at commit \`${previousReviewSha.slice(0, 8)}\`. ` + (reviewDiffMode === "snapshot" ? "The branch history was rewritten, so the diff below compares that reviewed snapshot directly with the current HEAD. " : "The diff below shows ONLY changes since that review. ") + "Your job is to review that delta, not the whole MR again.\n\nRules for incremental reviews:\n1. Only report findings introduced or still affected by the new delta.\n2. Do not re-report issues that are already mentioned in existing notes unless the new delta changes the same code and the issue remains newly relevant.\n3. If the delta is small and self-contained, decide from the embedded diff and submit the review without broad repository exploration.\n" + (oneTurn ? "4. No file-inspection tools are available; if a mechanical change like a route/path/string rename leaves a compatibility question you cannot settle from the diff, do not report it.\n" : "4. For mechanical changes like route/path/string renames, verify the direct call sites or tests only when the diff itself leaves a concrete compatibility question.\n") + "5. If the delta does not produce a qualifying finding under the selected review instructions, submit no findings.\n\n";
|
|
100
102
|
}
|
|
101
103
|
let embeddedDiffSection;
|
|
102
104
|
let diffFetchInstructions;
|
|
103
105
|
let reviewProcessSection;
|
|
104
106
|
let startInstruction;
|
|
107
|
+
let runtimeToolsSection;
|
|
105
108
|
if (embeddedDiff) {
|
|
106
109
|
const changedFileManifest = changedFiles.length > 0 ? `
|
|
107
110
|
Changed files (${changedFiles.length}):
|
|
108
111
|
${changedFiles.map((file) => `- \`${file}\``).join("\n")}
|
|
109
112
|
` : "";
|
|
110
|
-
embeddedDiffSection = "## Full Diff (Pre-fetched)\n\nThe complete diff for this PR is provided below. Analyze it directly. Do not run another command to list changed files. Use `read` or `grep` only if you need additional file context beyond what the diff shows.\n" + changedFileManifest + "\n````diff\n" + embeddedDiff + "\n````\n";
|
|
113
|
+
embeddedDiffSection = "## Full Diff (Pre-fetched)\n\nThe complete diff for this PR is provided below. Analyze it directly. Do not run another command to list changed files. " + (oneTurn ? "This diff is small and self-contained; it is the complete basis for your review.\n" : "Use `read` or `grep` only if you need additional file context beyond what the diff shows.\n") + changedFileManifest + "\n````diff\n" + embeddedDiff + "\n````\n";
|
|
111
114
|
diffFetchInstructions = `## Review the Diff Above
|
|
112
115
|
|
|
113
116
|
### Critical Rules
|
|
@@ -118,8 +121,13 @@ ${changedFiles.map((file) => `- \`${file}\``).join("\n")}
|
|
|
118
121
|
- NEVER flag "dependency version downgrade" (branch not rebased)
|
|
119
122
|
- NEVER compare entire codebase to ${targetBranch} - DIFF ONLY
|
|
120
123
|
`;
|
|
121
|
-
|
|
122
|
-
|
|
124
|
+
if (oneTurn) {
|
|
125
|
+
reviewProcessSection = "## Review Process\n\n1. Analyze the embedded diff above thoroughly\n2. Call `submit_review` in this same turn with your findings\n\nNo file-inspection tools are available for this review. Base every finding on the diff above. If a potential issue cannot be established from the diff alone, do not report it.\n";
|
|
126
|
+
startInstruction = "Analyze the diff above and call `submit_review` now, in this turn.";
|
|
127
|
+
} else {
|
|
128
|
+
reviewProcessSection = "## Review Process\n\n1. Analyze the embedded diff above thoroughly\n2. Use `grep` to search for patterns when needed\n3. Use bounded line-range reads when surrounding context is essential; avoid reading entire large files\n4. Do not repeat a diff, grep, or read operation whose result is already in context\n5. Submit your review using `submit_review`\n";
|
|
129
|
+
startInstruction = previousReviewSha ? "Analyze only the incremental diff provided above. If it is self-contained, submit your review without extra tool calls." : "Analyze the diff provided above, then submit your review using `submit_review`.";
|
|
130
|
+
}
|
|
123
131
|
} else {
|
|
124
132
|
embeddedDiffSection = "";
|
|
125
133
|
diffFetchInstructions = "## Step 1: List Changed Files (MANDATORY FIRST STEP)\n\n**Run this command FIRST to get the list of changed files:**\n```bash\n" + prDiffCmd + "\n```\n\nThis lists ONLY the filenames changed in this PR. **Do NOT dump the entire diff here** - you'll inspect each file individually in Step 2. Only review files that appear in this output.\n\n## Step 2: Review Changed Files Only\n\n### Critical Rules\n- ONLY review files that appear in the diff from Step 1\n- ONLY analyze actual code changes (+ and - lines in the diff)\n- Use the most reliable diff command: `" + gitDiffCmd + `\`
|
|
@@ -146,7 +154,15 @@ ${changedFiles.map((file) => `- \`${file}\``).join("\n")}
|
|
|
146
154
|
`;
|
|
147
155
|
startInstruction = `Start by running \`${prDiffCmd}\` to list the changed files, then analyze each file individually using \`${gitDiffCmd} -- path/to/file\`.`;
|
|
148
156
|
}
|
|
149
|
-
|
|
157
|
+
runtimeToolsSection = oneTurn ? "## Runtime Tools\n\n- `submit_review` submits the completed review. It is the only tool available for this review.\n" : `## Runtime Tools
|
|
158
|
+
|
|
159
|
+
- \`${prDiffCmd}\` lists the changed files when a diff is not embedded.
|
|
160
|
+
- \`${gitDiffCmd} -- path/to/file\` shows the delta for one changed file.
|
|
161
|
+
- \`read\` provides bounded surrounding context.
|
|
162
|
+
- \`grep\` searches for directly relevant code and contracts.
|
|
163
|
+
- \`submit_review\` submits the completed review.
|
|
164
|
+
`;
|
|
165
|
+
return templateText.replace(/\{pr_url\}/g, prUrl).replace(/\{pr_diff_cmd\}/g, prDiffCmd).replace(/\{git_diff_cmd\}/g, gitDiffCmd).replace(/\{mr_context_section\}/g, contextSection).replace(/\{mr_notes_section\}/g, notesSection).replace(/\{mr_reminder_section\}/g, reminderSection).replace(/\{incremental_section\}/g, incrementalSection).replace(/\{embedded_diff_section\}/g, embeddedDiffSection).replace(/\{diff_fetch_instructions\}/g, diffFetchInstructions).replace(/\{runtime_tools_section\}/g, runtimeToolsSection).replace(/\{review_process_section\}/g, reviewProcessSection).replace(/\{start_instruction\}/g, startInstruction);
|
|
150
166
|
}
|
|
151
167
|
function buildMrSections(mrMetadata) {
|
|
152
168
|
if (!mrMetadata) {
|
|
@@ -265,6 +281,17 @@ function parseModelString(model) {
|
|
|
265
281
|
if (modelId.startsWith("converse/")) {
|
|
266
282
|
modelId = modelId.slice("converse/".length);
|
|
267
283
|
}
|
|
284
|
+
const separator = modelId.indexOf("@");
|
|
285
|
+
if (separator !== -1) {
|
|
286
|
+
const baseModelId = modelId.slice(separator + 1).trim();
|
|
287
|
+
modelId = modelId.slice(0, separator).trim();
|
|
288
|
+
if (!modelId || !baseModelId) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
`Invalid bedrock model "${trimmed}". Use "bedrock/<arn>@<base-model-id>", for example "bedrock/arn:aws:bedrock:ap-south-1:123:application-inference-profile/abc@global.anthropic.claude-opus-5".`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return { provider, modelId, baseModelId };
|
|
294
|
+
}
|
|
268
295
|
return { provider, modelId };
|
|
269
296
|
}
|
|
270
297
|
if (knownProviders.has(provider)) {
|
|
@@ -286,6 +313,36 @@ function parseModelString(model) {
|
|
|
286
313
|
}
|
|
287
314
|
return { provider: "anthropic", modelId: trimmed };
|
|
288
315
|
}
|
|
316
|
+
function extractBedrockArnRegion(arn) {
|
|
317
|
+
const parts = arn.split(":");
|
|
318
|
+
return parts.length >= 4 && parts[3] ? parts[3] : "us-east-1";
|
|
319
|
+
}
|
|
320
|
+
function buildBedrockArnModel(opts) {
|
|
321
|
+
const region = opts.region ?? extractBedrockArnRegion(opts.arn);
|
|
322
|
+
const baseUrl = `https://bedrock-runtime.${region}.amazonaws.com`;
|
|
323
|
+
if (opts.baseModel) {
|
|
324
|
+
return {
|
|
325
|
+
...opts.baseModel,
|
|
326
|
+
id: opts.arn,
|
|
327
|
+
// Keep the registry id as the name so pi-ai's capability matchers still
|
|
328
|
+
// see the vendor and model version behind the ARN.
|
|
329
|
+
name: opts.baseModel.id,
|
|
330
|
+
baseUrl
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
id: opts.arn,
|
|
335
|
+
name: opts.arn,
|
|
336
|
+
api: "bedrock-converse-stream",
|
|
337
|
+
provider: "amazon-bedrock",
|
|
338
|
+
baseUrl,
|
|
339
|
+
reasoning: false,
|
|
340
|
+
input: ["text"],
|
|
341
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
342
|
+
contextWindow: 2e5,
|
|
343
|
+
maxTokens: 16384
|
|
344
|
+
};
|
|
345
|
+
}
|
|
289
346
|
function mapReasoningEffort(effort) {
|
|
290
347
|
if (!effort) return void 0;
|
|
291
348
|
switch (effort.toLowerCase()) {
|
|
@@ -315,16 +372,26 @@ function getDefaultReasoningEffortForModel(model) {
|
|
|
315
372
|
}
|
|
316
373
|
var HIGH_RISK_PATH_RE = /(?:^|\/)(?:migrations?|schema|auth|security|permissions?|crypto|iam)(?:\/|\.|$)|\.tf$/im;
|
|
317
374
|
var HIGH_RISK_CHANGE_RE = /^\+.*\b(?:authorization|authentication|permission|transaction|mutex|semaphore|encrypt|decrypt|credential|secret)\b/im;
|
|
375
|
+
function isHighRiskDiff(diff) {
|
|
376
|
+
return HIGH_RISK_PATH_RE.test(diff) || HIGH_RISK_CHANGE_RE.test(diff);
|
|
377
|
+
}
|
|
378
|
+
var SINGLE_TURN_MAX_FILES = 5;
|
|
379
|
+
var SINGLE_TURN_MAX_CHANGED_LINES = 50;
|
|
380
|
+
function qualifiesForSingleTurnReview(opts) {
|
|
381
|
+
if (!opts.embedded || !opts.stats) return false;
|
|
382
|
+
const { files, additions, deletions } = opts.stats;
|
|
383
|
+
if (files === 0 || files > SINGLE_TURN_MAX_FILES) return false;
|
|
384
|
+
if (additions + deletions > SINGLE_TURN_MAX_CHANGED_LINES) return false;
|
|
385
|
+
if (opts.diff && isHighRiskDiff(opts.diff)) return false;
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
318
388
|
function selectReasoningEffort(opts) {
|
|
319
389
|
const requested = mapReasoningEffort(opts.requested);
|
|
320
390
|
if (requested) return requested;
|
|
321
391
|
const modelDefault = opts.modelDefault;
|
|
322
392
|
if (modelDefault !== "xhigh") return modelDefault;
|
|
323
393
|
if (opts.forcedFull) return modelDefault;
|
|
324
|
-
|
|
325
|
-
opts.diff && (HIGH_RISK_PATH_RE.test(opts.diff) || HIGH_RISK_CHANGE_RE.test(opts.diff))
|
|
326
|
-
);
|
|
327
|
-
if (risky) return modelDefault;
|
|
394
|
+
if (opts.diff && isHighRiskDiff(opts.diff)) return modelDefault;
|
|
328
395
|
if (opts.mode === "incremental" || opts.mode === "snapshot") return "high";
|
|
329
396
|
const changedLines = (opts.stats?.additions ?? 0) + (opts.stats?.deletions ?? 0);
|
|
330
397
|
if (opts.stats && opts.stats.files <= 10 && changedLines <= 500) return "high";
|
|
@@ -466,6 +533,9 @@ async function pushMetrics(opts) {
|
|
|
466
533
|
`# HELP hodor_review_reused Whether an existing identical-HEAD review was reused`,
|
|
467
534
|
`# TYPE hodor_review_reused gauge`,
|
|
468
535
|
`hodor_review_reused${labelSuffix} ${metrics.reused ? 1 : 0}`,
|
|
536
|
+
`# HELP hodor_review_fast_path Whether the single-turn tiny-diff fast path gated tools to submit_review`,
|
|
537
|
+
`# TYPE hodor_review_fast_path gauge`,
|
|
538
|
+
`hodor_review_fast_path${labelSuffix} ${metrics.fastPath ? 1 : 0}`,
|
|
469
539
|
""
|
|
470
540
|
];
|
|
471
541
|
const body = lines.join("\n");
|
|
@@ -2102,7 +2172,8 @@ async function reviewPr(opts) {
|
|
|
2102
2172
|
localMode = false,
|
|
2103
2173
|
diffAgainst,
|
|
2104
2174
|
full = false,
|
|
2105
|
-
targetBranchOverride
|
|
2175
|
+
targetBranchOverride,
|
|
2176
|
+
tinyDiffFastPath = false
|
|
2106
2177
|
} = opts;
|
|
2107
2178
|
const effectiveReviewInstructions = reviewInstructions == null ? loadDefaultReviewInstructions() : validateReviewInstructions(reviewInstructions, "review instructions");
|
|
2108
2179
|
const effectiveAdditionalInstructions = additionalInstructions == null ? null : validateReviewInstructions(additionalInstructions, "additional instructions");
|
|
@@ -2137,24 +2208,26 @@ async function reviewPr(opts) {
|
|
|
2137
2208
|
}
|
|
2138
2209
|
let piModel = modelRuntime.getModel(parsed.provider, parsed.modelId);
|
|
2139
2210
|
if (parsed.modelId.startsWith("arn:")) {
|
|
2140
|
-
const
|
|
2141
|
-
const region = arnParts.length >= 4 ? arnParts[3] : "us-east-1";
|
|
2211
|
+
const region = extractBedrockArnRegion(parsed.modelId);
|
|
2142
2212
|
if (!process.env.AWS_REGION && !process.env.AWS_DEFAULT_REGION) {
|
|
2143
2213
|
process.env.AWS_REGION = region;
|
|
2144
2214
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2215
|
+
const baseModel = parsed.baseModelId ? modelRuntime.getModel(parsed.provider, parsed.baseModelId) : void 0;
|
|
2216
|
+
if (parsed.baseModelId && !baseModel) {
|
|
2217
|
+
throw new Error(
|
|
2218
|
+
`Base model "${parsed.baseModelId}" for Bedrock ARN "${parsed.modelId}" was not found in the installed pi-ai registry.`
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
piModel = buildBedrockArnModel({ arn: parsed.modelId, baseModel, region });
|
|
2222
|
+
if (baseModel) {
|
|
2223
|
+
logger.info(
|
|
2224
|
+
`Custom bedrock ARN model \u2014 region: ${region}, capabilities from ${baseModel.id}`
|
|
2225
|
+
);
|
|
2226
|
+
} else {
|
|
2227
|
+
logger.warn(
|
|
2228
|
+
`Custom bedrock ARN model \u2014 region: ${region}, no base model given. Prompt caching, reasoning, and cost reporting are disabled. Append "@<base-model-id>" to the model string (e.g. "${model}@global.anthropic.claude-opus-5") to restore them.`
|
|
2229
|
+
);
|
|
2230
|
+
}
|
|
2158
2231
|
} else if (!piModel) {
|
|
2159
2232
|
if (parsed.provider === "openrouter") {
|
|
2160
2233
|
piModel = {
|
|
@@ -2315,13 +2388,19 @@ async function reviewPr(opts) {
|
|
|
2315
2388
|
diffAdditions: 0,
|
|
2316
2389
|
diffDeletions: 0,
|
|
2317
2390
|
diffBytes: 0,
|
|
2318
|
-
reused: true
|
|
2391
|
+
reused: true,
|
|
2392
|
+
fastPath: false
|
|
2319
2393
|
};
|
|
2320
2394
|
logger.info(`Review telemetry: ${JSON.stringify({
|
|
2395
|
+
project: `${owner}/${repo}`,
|
|
2396
|
+
mr: prNumber,
|
|
2397
|
+
headSha: headSha.slice(0, 12),
|
|
2398
|
+
model,
|
|
2399
|
+
outcome: "reused",
|
|
2321
2400
|
reviewMode: metrics2.reviewMode,
|
|
2322
2401
|
reasoningEffort: metrics2.reasoningEffort,
|
|
2402
|
+
fastPath: false,
|
|
2323
2403
|
reused: true,
|
|
2324
|
-
headSha: headSha.slice(0, 12),
|
|
2325
2404
|
findings: cachedReview.findings.length
|
|
2326
2405
|
})}`);
|
|
2327
2406
|
printMetrics(metrics2);
|
|
@@ -2380,6 +2459,16 @@ async function reviewPr(opts) {
|
|
|
2380
2459
|
if (thinkingLevel) {
|
|
2381
2460
|
logger.info(`Reasoning effort for ${piModel.name}: ${thinkingLevel}${reasoningEffort ? " (explicit)" : " (adaptive)"}`);
|
|
2382
2461
|
}
|
|
2462
|
+
const singleTurn = tinyDiffFastPath && qualifiesForSingleTurnReview({
|
|
2463
|
+
diff: reviewDiff,
|
|
2464
|
+
stats: diffStats,
|
|
2465
|
+
embedded: embeddedDiff != null
|
|
2466
|
+
});
|
|
2467
|
+
if (singleTurn) {
|
|
2468
|
+
logger.info(
|
|
2469
|
+
`Single-turn fast path: tiny low-risk diff (${diffStats?.files} file(s), ${(diffStats?.additions ?? 0) + (diffStats?.deletions ?? 0)} changed line(s)); exposing only submit_review`
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2383
2472
|
const prompt = buildPrReviewPrompt({
|
|
2384
2473
|
prUrl: prUrl ?? `local diff (against ${targetBranch})`,
|
|
2385
2474
|
platform,
|
|
@@ -2390,7 +2479,8 @@ async function reviewPr(opts) {
|
|
|
2390
2479
|
previousReviewSha,
|
|
2391
2480
|
reviewDiffMode: reviewMode,
|
|
2392
2481
|
changedFiles,
|
|
2393
|
-
localMode
|
|
2482
|
+
localMode,
|
|
2483
|
+
singleTurn
|
|
2394
2484
|
});
|
|
2395
2485
|
const startTime = Date.now();
|
|
2396
2486
|
const settingsManager = SettingsManager.inMemory({
|
|
@@ -2469,7 +2559,7 @@ async function reviewPr(opts) {
|
|
|
2469
2559
|
// (see _refreshToolRegistry in @earendil-works/pi-coding-agent's
|
|
2470
2560
|
// agent-session.ts). The submit_review custom tool must be named here
|
|
2471
2561
|
// or the LLM never sees it and the agent loop exits without calling it.
|
|
2472
|
-
tools: ["read", "bash", "grep", "find", "ls", "submit_review"],
|
|
2562
|
+
tools: singleTurn ? ["submit_review"] : ["read", "bash", "grep", "find", "ls", "submit_review"],
|
|
2473
2563
|
customTools: [submitReviewTool],
|
|
2474
2564
|
modelRuntime,
|
|
2475
2565
|
sessionManager: SessionManager.inMemory(),
|
|
@@ -2627,11 +2717,18 @@ async function reviewPr(opts) {
|
|
|
2627
2717
|
diffAdditions: diffStats?.additions ?? 0,
|
|
2628
2718
|
diffDeletions: diffStats?.deletions ?? 0,
|
|
2629
2719
|
diffBytes: diffStats?.bytes ?? 0,
|
|
2630
|
-
reused: false
|
|
2720
|
+
reused: false,
|
|
2721
|
+
fastPath: singleTurn
|
|
2631
2722
|
};
|
|
2632
2723
|
logger.info(`Review telemetry: ${JSON.stringify({
|
|
2724
|
+
project: localMode ? null : `${owner}/${repo}`,
|
|
2725
|
+
mr: localMode ? null : prNumber,
|
|
2726
|
+
headSha: headSha?.slice(0, 12) ?? null,
|
|
2727
|
+
model,
|
|
2728
|
+
outcome: "reviewed",
|
|
2633
2729
|
reviewMode: metrics.reviewMode,
|
|
2634
2730
|
reasoningEffort: metrics.reasoningEffort,
|
|
2731
|
+
fastPath: singleTurn,
|
|
2635
2732
|
reused: false,
|
|
2636
2733
|
diffFiles: metrics.diffFiles,
|
|
2637
2734
|
diffAdditions: metrics.diffAdditions,
|
|
@@ -2699,4 +2796,4 @@ export {
|
|
|
2699
2796
|
postReviewStructured,
|
|
2700
2797
|
reviewPr
|
|
2701
2798
|
};
|
|
2702
|
-
//# sourceMappingURL=chunk-
|
|
2799
|
+
//# sourceMappingURL=chunk-VERO4XYJ.js.map
|