Skip to content

Commit d61dd7b

Browse files
authored
refactor: extract shared scoring + consolidate time utils (#823)
* refactor: extract shared scoring logic and consolidate time format utils - Extract applyUrlScoreAdjustments() and scoreArrayResponse() to analysis.ts, eliminating duplicated endpoint scoring between explore.ts and record.ts - Consolidate formatDuration/formatUptime into a single formatDuration(ms) in download/progress.ts, reused by commands/daemon.ts * fix: preserve explore scoring semantics and round daemon uptime - Revert explore.ts scoreEndpoint to original inline /api/ /x/ bonus without record's tracking/analytics penalty (blocker from review) - Math.round uptime*1000 to avoid floating-point noise in daemon status
1 parent e9867dc commit d61dd7b

File tree

5 files changed

+39
-31
lines changed

5 files changed

+39
-31
lines changed

src/analysis.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,32 @@ export function detectAuthFromContent(url: string, body: unknown): string[] {
150150
return indicators;
151151
}
152152

153+
// ── Shared scoring helpers ───────────────────────────────────────────────────
154+
155+
/** URL-based score adjustments shared by explore and record scoring. */
156+
export function applyUrlScoreAdjustments(url: string, score: number): number {
157+
let s = score;
158+
if (url.includes('/api/') || url.includes('/x/')) s += 3;
159+
if (url.match(/\/(track|log|analytics|beacon|pixel|stats|metric)/i)) s -= 10;
160+
if (url.match(/\/(ping|heartbeat|keep.?alive)/i)) s -= 10;
161+
return s;
162+
}
163+
164+
/** Score an array response based on item count and detected field roles. */
165+
export function scoreArrayResponse(arrayResult: ArrayDiscovery | null): number {
166+
if (!arrayResult) return 0;
167+
let s = 10;
168+
s += Math.min(arrayResult.items.length, 10);
169+
const sample = arrayResult.items[0];
170+
if (sample && typeof sample === 'object') {
171+
const keys = Object.keys(sample as object).map(k => k.toLowerCase());
172+
for (const aliases of Object.values(FIELD_ROLES)) {
173+
if (aliases.some(a => keys.includes(a))) s += 2;
174+
}
175+
}
176+
return s;
177+
}
178+
153179
// ── Query param classification ──────────────────────────────────────────────
154180

155181
/** Extract non-volatile query params and classify them. */

src/commands/daemon.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,7 @@
77

88
import chalk from 'chalk';
99
import { fetchDaemonStatus, requestDaemonShutdown } from '../browser/daemon-client.js';
10-
11-
function formatUptime(seconds: number): string {
12-
const h = Math.floor(seconds / 3600);
13-
const m = Math.floor((seconds % 3600) / 60);
14-
if (h > 0) return `${h}h ${m}m`;
15-
if (m > 0) return `${m}m`;
16-
return `${Math.floor(seconds)}s`;
17-
}
10+
import { formatDuration } from '../download/progress.js';
1811

1912
function formatTimeSince(timestampMs: number): string {
2013
const seconds = (Date.now() - timestampMs) / 1000;
@@ -33,7 +26,7 @@ export async function daemonStatus(): Promise<void> {
3326
}
3427

3528
console.log(`Daemon: ${chalk.green('running')} (PID ${status.pid})`);
36-
console.log(`Uptime: ${formatUptime(status.uptime)}`);
29+
console.log(`Uptime: ${formatDuration(Math.round(status.uptime * 1000))}`);
3730
console.log(`Extension: ${status.extensionConnected ? chalk.green('connected') : chalk.yellow('disconnected')}`);
3831
console.log(`Last CLI request: ${formatTimeSince(status.lastCliRequestTime)}`);
3932
console.log(`Memory: ${status.memoryMB} MB`);

src/download/progress.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ export function formatDuration(ms: number): string {
2929
const seconds = Math.floor(ms / 1000);
3030
if (seconds < 60) return `${seconds}s`;
3131
const minutes = Math.floor(seconds / 60);
32-
const remainingSeconds = seconds % 60;
33-
return `${minutes}m ${remainingSeconds}s`;
32+
if (minutes < 60) {
33+
const remainingSeconds = seconds % 60;
34+
return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
35+
}
36+
const hours = Math.floor(minutes / 60);
37+
const remainingMinutes = minutes % 60;
38+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
3439
}
3540

3641
/**

src/explore.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ function scoreEndpoint(ep: { contentType: string; responseAnalysis: AnalyzedEndp
203203
if (ep.hasPaginationParam) s += 2;
204204
if (ep.hasLimitParam) s += 2;
205205
if (ep.status === 200) s += 2;
206-
// Anti-Bot Empty Value Detection: penalize JSON endpoints returning empty data
207206
if (ep.responseAnalysis && ep.responseAnalysis.itemCount === 0 && ep.contentType.includes('json')) s -= 3;
208207
return s;
209208
}

src/record.ts

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import {
2727
inferStrategy,
2828
detectAuthFromContent,
2929
classifyQueryParams,
30+
applyUrlScoreAdjustments,
31+
scoreArrayResponse,
3032
} from './analysis.js';
3133

3234
// ── Types ──────────────────────────────────────────────────────────────────
@@ -82,11 +84,7 @@ function preferRecordedCandidate(current: RecordedCandidate, next: RecordedCandi
8284

8385
/** Apply shared endpoint score tweaks. */
8486
function applyCommonEndpointScoreAdjustments(req: RecordedRequest, score: number): number {
85-
let adjusted = score;
86-
if (req.url.includes('/api/')) adjusted += 3;
87-
if (req.url.match(/\/(track|log|analytics|beacon|pixel|stats|metric)/i)) adjusted -= 10;
88-
if (req.url.match(/\/(ping|heartbeat|keep.?alive)/i)) adjusted -= 10;
89-
return adjusted;
87+
return applyUrlScoreAdjustments(req.url, score);
9088
}
9189

9290
/** Build a candidate-level dedupe key. */
@@ -330,20 +328,7 @@ function generateReadRecordedJs(): string {
330328
// ── Analysis helpers ───────────────────────────────────────────────────────
331329

332330
function scoreRequest(req: RecordedRequest, arrayResult: ReturnType<typeof findArrayPath> | null): number {
333-
let s = 0;
334-
if (arrayResult) {
335-
s += 10;
336-
s += Math.min(arrayResult.items.length, 10);
337-
// Bonus for detected semantic fields
338-
const sample = arrayResult.items[0];
339-
if (sample && typeof sample === 'object') {
340-
const keys = Object.keys(sample as object).map(k => k.toLowerCase());
341-
for (const aliases of Object.values(FIELD_ROLES)) {
342-
if (aliases.some(a => keys.includes(a))) s += 2;
343-
}
344-
}
345-
}
346-
return applyCommonEndpointScoreAdjustments(req, s);
331+
return applyCommonEndpointScoreAdjustments(req, scoreArrayResponse(arrayResult));
347332
}
348333

349334
/** Check whether one recorded request is safe to treat as a write candidate. */

0 commit comments

Comments
 (0)